동작 과정
1. Unit 1 테스트 페이지 접속
i) "http://localhost:8080/test/unit/1"에 접속한다.
@RequestMapping("/unit/{test_num}")
public String unit(@PathVariable("test_num") String test_num, Model model) {
model.addAttribute("test_num", test_num);
return "test/unit/test"+test_num;
}
- TestController의 unit() 컨트롤러 메소드에 매핑되어 resources/templates/test/unit/test1.mustache 뷰로 이동한다.
ii) 3문제를 다 풀고 답안 제출을 누른다.
2-1. submit 이벤트 처리
- Ajax를 사용하기 때문에 제출 버튼을 눌러도 <form>이 다른 페이지로 넘어가지 않고, resources/static/js/test.js의 리스너가 submit 이벤트를 감지해서 아래 리스너가 실행된다.
$('#answer-form').submit(function() {
startTimeout();
$('#result-container').show();
$('html, body').animate({scrollTop: $('#result-container').offset().top}, 200);
var serializedFormData = $('#answer-form').serializeObject();
console.log(serializedFormData)
$.ajax({
url:"http://localhost:8080/test/grading",
data: JSON.stringify(serializedFormData),
type: "POST",
dataType: "json",
contentType : "application/json"
})
.done(function(json) {
console.log(json);
var questionList = json.questionList;
$('#correct-count').text("맞은 개수 : "+json.correctCount+"/"+json.questionList.length);
for(var i=0;i<questionList.length;i++) {
var question = questionList[i];
var result = question.sqlResult;
if(question.isCorrect) {
$('#result'+(i+1)+' .correct').show();
$('#result'+(i+1)+' .incorrect').hide();
}
else {
$('#result'+(i+1)+' .incorrect').show();
$('#result'+(i+1)+' .correct').hide();
}
$('#user-answer-'+(i+1)).text(question.userAnswer);
$('#result'+(i+1)+' .result-table > tbody').empty();
if(question.errorMsg!=null) $('#result'+(i+1)+' .result-table > tbody:last').append('<tr><td style="border:none">'+question.errorMsg+'</td></tr>');
if(result!=null) {
for(var r=0;r<result.length;r++) {
var rowStr="<tr>";
if(r==0) rowStr="<tr style=\"background-color:#EEEEEE\">";
for(var c=0;c<result[r].length;c++) rowStr+="<td>"+result[r][c]+"</td>";
rowStr+="</tr>"
$('#result'+(i+1)+' .result-table > tbody:last').append(rowStr);
console.log(rowStr);
}
}
}
})
.fail(function(xhr, status, errorThrown){
alert("요청 실패");
})
return false;
});
위의 리스너는 아래와 같은 절차로 동작한다.
부분1
startTimeout(); // 과도한 Ajax 호출을 방지하기 위해 버튼을 5초간 비활성화 (아래 이미지의 빨간 상자)
$('#result-container').show(); // 빈 결과 컨테이너를 표시(아래 이미지는 최종 결과 화면이므로 결과가 나오는 것임)(아래 이미지의 초록 상자)
부분2
$('html, body').animate({scrollTop: $('#result-container').offset().top}, 200); // 결과 컨테이너로 스크롤 다운
var serializedFormData = $('#answer-form').serializeObject(); // 사용자가 입력한 form을 json으로 변환
부분3
- Ajax 처리 과정은 [2-2. 채점 로직]에서 설명할 것임
$.ajax({ // JSON으로 변환한 답안을 애플리케이션의 채점 컨트롤러로 Ajax 전송
url:"http://localhost:8080/test/grading",
data: JSON.stringify(serializedFormData),
type: "POST",
dataType: "json",
contentType : "application/json"
})
.done(function(json) { // 채점에 성공했다면 결과 컨트롤러에 결과 출력
console.log(json);
var questionList = json.questionList;
$('#correct-count').text("맞은 개수 : "+json.correctCount+"/"+json.questionList.length);
for(var i=0;i<questionList.length;i++) {
var question = questionList[i];
var result = question.sqlResult;
if(question.isCorrect) {
$('#result'+(i+1)+' .correct').show();
$('#result'+(i+1)+' .incorrect').hide();
}
else {
$('#result'+(i+1)+' .incorrect').show();
$('#result'+(i+1)+' .correct').hide();
}
$('#user-answer-'+(i+1)).text(question.userAnswer);
$('#result'+(i+1)+' .result-table > tbody').empty();
if(question.errorMsg!=null) $('#result'+(i+1)+' .result-table > tbody:last').append('<tr><td style="border:none">'+question.errorMsg+'</td></tr>');
if(result!=null) {
for(var r=0;r<result.length;r++) {
var rowStr="<tr>";
if(r==0) rowStr="<tr style=\"background-color:#EEEEEE\">";
for(var c=0;c<result[r].length;c++) rowStr+="<td>"+result[r][c]+"</td>";
rowStr+="</tr>"
$('#result'+(i+1)+' .result-table > tbody:last').append(rowStr);
console.log(rowStr);
}
}
}
})
.fail(function(xhr, status, errorThrown){ // 채점에 실패했다면 경고창 표시
alert("요청 실패");
})
부분4
return false;
// 리턴값을 true로 한다면 submit 버튼이 정상동작하여 다른 페이지로 이동하게됨
// 다시 한번 언급하지만, 우리는 Ajax를 사용하기 때문에 다른 페이지로 이동하지 않고 채점 결과를 보여줌
2-2. 채점 로직
i) JSON으로 변환한 답안을 애플리케이션의 채점 컨트롤러로 Ajax 전송
- $.ajax의 url에 의해 GradingController 컨트롤러로 요청 전송
url:"http://localhost:8080/test/grading"
@RestController
@RequestMapping("/test/grading")
public class GradingController {
@Autowired GradingService gradingService;
@PostMapping
public TestResult gradingUnit(@RequestBody Map<String, Object> userAnswer, @ModelAttribute SQLData sqlData, BindingResult bindingResult) { // {"1": 4, "2": "select * from member;"}
return gradingService.grade(userAnswer, sqlData, bindingResult);
}
}
ii) GradingService로 이동
<GradingService에 있는 각 메소드의 역할 설명>
- grade() : 채점할 유닛을 결정하고 채점 메소드를 호출한다.
- validateAndGetResult() : 사용자가 입력한 SQL 답안에 DB 금지어(UPDATE, DROP, DELETE 등)가 포함되어 있거나 문법에 오류가 있다면 Question의 errorMsg에 에러 메시지를 담고 SQL 결과는 null로 나타낸다. SQL 답안에 문제가 없다면 SQL 결과를 반환한다.
- getSqlResultForShow() : 뷰 화면에(사용자에게) 표시할 SQL 결과를 생성한다.
- showSqlResult() : 개발자 확인용 메소드로, SQL 답안의 결과를 콘솔창에서 확인해보고 싶을 때 사용하면 된다.
showSqlResult(question3.getSqlResult());
- gradeUnit1() : Unit 1의 채점 로직을 담은 메소드다. Unit x에 대한 채점 로직을 담은 메소드의 이름은 gradeUnitx로 작명하면 된다.
iii) 채점
- 채점의 결과는 gradeUnitx() 메소드 내부에서 TestResult 객체에 담으면 된다. TestResult 객체는 grade()를 거쳐 GradingController의 gradingUnit()로 전달된다.
- GradingController에 @RestController를 붙였기 때문에 TestResult 오브젝트를 리턴할 때 JSON으로 변환된다. 즉, Ajax는 TestResult를 json 형태로 받게 되는 것이다.
<Ajax 결과>
- 객관식 채점 예시
Question question1 = new Question();
question1.setNum(1); // 문제 번호는 1
question1.setUserAnswer(String.valueOf(answer1)); // 사용자의 답안 저장
if(answer1==5) { // 답이 5인 경우
question1.setIsCorrect(true); // 정답 처리
testResult.setCorrectCount(testResult.getCorrectCount()+1); // 총 맞은 개수를 1 카운트
}
- SQL문 채점 예시
Question question3 = new Question();
question3.setNum(3); // 문제 번호는 3
question3.setUserAnswer(answer3); // 사용자의 답안 저장
List<LinkedHashMap<String, Object>> sqlResult = validateAndGetSqlResult(answer3, sqlData, bindingResult, question3); // 사용자의 답안을 검증하고 sql 결과를 가져온다.
question3.setSqlResult(getSqlResultForShow(question3, sqlResult)); // 사용자에게 보여줄 sql 결과를 List<List<String>> 타입으로 생성 후 저장
// showSqlResult(question3.getSqlResult()); // 유저가 생성한 sql 결과를 확인
List<Employee> employeeList = employeeRepository.findAll(); // 실제 답안을 레포지토리에서 가져옴
// ★★★이 부분부터는 여러분들의 채점 방식에 따라 크게 차이가 있을 수 있습니다.★★★
int correctCount=0;
if(sqlResult!=null&&sqlResult.size()==5&&sqlResult.get(0).size()==3) { // 결과 튜플은 5개이고, 결과 필드는 3개여야 함
for(int i=0;i<5;i++) {
if(sqlResult.get(i).containsKey("NAME")&&sqlResult.get(i).get("NAME").toString().equals(employeeList.get(i).getName())) correctCount++;
if(sqlResult.get(i).containsKey("POSITION")&&sqlResult.get(i).get("POSITION").toString().equals(employeeList.get(i).getPosition())) correctCount++;
if(sqlResult.get(i).containsKey("SALARY")&&sqlResult.get(i).get("SALARY").toString().equals(String.valueOf(employeeList.get(i).getSalary()))) correctCount++;
}
}
if(correctCount==15) { // 결과 테이블의 모든 셀이 실제 답안과 동일한 경우
question3.setIsCorrect(true); // 정답 처리
testResult.setCorrectCount(testResult.getCorrectCount()+1); // 총 맞은 개수를 1 카운트
}
gradeUnit1() 메소드 전체 코드
public TestResult gradeUnit1(Map<String, Object> userAnswer, SQLData sqlData, BindingResult bindingResult) {
TestResult testResult = new TestResult();
int answer1 = Integer.parseInt(userAnswer.get("question1").toString());
int answer2 = Integer.parseInt(userAnswer.get("question2").toString());
String answer3 = userAnswer.get("question3").toString();
List<Question> questionList = new ArrayList<>();
// 문제 1 채점
Question question1 = new Question();
question1.setNum(1);
question1.setUserAnswer(String.valueOf(answer1));
if(answer1==5) {
question1.setIsCorrect(true);
testResult.setCorrectCount(testResult.getCorrectCount()+1);
}
// 문제 2 채점
Question question2 = new Question();
question2.setNum(2);
question2.setUserAnswer(String.valueOf(answer2));
if(answer2==2) {
question2.setIsCorrect(true);
testResult.setCorrectCount(testResult.getCorrectCount()+1);
}
// 문제 3 채점
Question question3 = new Question();
question3.setNum(3);
question3.setUserAnswer(answer3);
List<LinkedHashMap<String, Object>> sqlResult = validateAndGetSqlResult(answer3, sqlData, bindingResult, question3); // 사용자의 답안을 검증하고 sql 결과를 가져온다.
question3.setSqlResult(getSqlResultForShow(question3, sqlResult)); // 사용자에게 보여줄 sql 결과를 List<List<String>> 타입으로 생성 후 저장
// showSqlResult(question3.getSqlResult()); // 유저가 생성한 sql 결과를 확인
List<Employee> employeeList = employeeRepository.findAll(); // 답안
int correctCount=0;
if(sqlResult!=null&&sqlResult.size()==5&&sqlResult.get(0).size()==3) {
for(int i=0;i<5;i++) {
if(sqlResult.get(i).containsKey("NAME")&&sqlResult.get(i).get("NAME").toString().equals(employeeList.get(i).getName())) correctCount++;
if(sqlResult.get(i).containsKey("POSITION")&&sqlResult.get(i).get("POSITION").toString().equals(employeeList.get(i).getPosition())) correctCount++;
if(sqlResult.get(i).containsKey("SALARY")&&sqlResult.get(i).get("SALARY").toString().equals(String.valueOf(employeeList.get(i).getSalary()))) correctCount++;
}
}
if(correctCount==15) {
question3.setIsCorrect(true);
testResult.setCorrectCount(testResult.getCorrectCount()+1);
}
questionList.add(question1);
questionList.add(question2);
questionList.add(question3);
testResult.setQuestionList(questionList);
return testResult;
}
코드를 모두 최적화했기 때문에 여러분들은 딱 2가지만 하면 됩니다.
- 문제 출제 : testx.mustache 파일을 생성해서 작성 (기본적으로 작성된 틀을 제외하면 웬만하면 70~80라인 정도 작업하면 될 것으로 예상)
- 문제 채점 : GradingService의 gradeUnitx() 메소드에서 채점을 진행하고, 채점 결과는 TestResult에 담아서 반환
'개발 프로젝트 > [팀] Web - SQL 교육 사이트' 카테고리의 다른 글
[팀 프로젝트] SQL 교육 사이트 - 테스트 코드 강의 정리 (1) | 2022.12.27 |
---|---|
[팀 프로젝트] SQL 교육 사이트 - 20221212 후반 작업 안내 (0) | 2022.12.12 |
[팀 프로젝트] SQL 교육 사이트 - 20221009 중반 작업 안내 (0) | 2022.10.07 |
[팀 프로젝트] SQL 교육 사이트 - 20220925 안내 사항 (0) | 2022.09.25 |
[팀 프로젝트] SQL 교육 사이트 - 20220917 안내 사항 (0) | 2022.09.17 |
댓글