요약 정보기술의 급속한 발전에 따라 교육 분야의 디지털 전환은 필수적인 흐름이 되었다. 기존의 과정 평가 방식은 처리 효율이 낮고 데이터 관리가 복잡하여 현대 대학의 정밀한 교육 평가 요구를 충족시키지 못한다. 이에 따라 응용 지향적 학부 교육을 실시하는 특정 대학에서는 학생 학습 과정을 동적으로 추적하고 과학적으로 평가할 수 있는 효과적인 시스템이 절실히 필요하다. 본 시스템은 증가성 평가 개념을 도입하여 학생들의 성장 궤적을 보다 객관적으로 반영하고, 교수자에게 데이터 기반 지원을 제공하며 수업 전략을 최적화한다. 주요 키워드: 증가성 평가, 과정 평가, 디지털 전환, 동적 추적, 학생 성장.
시스템은 프론트엔드와 백엔드를 분리한 아키텍처로 구성되며, SpringBoot, Vue.js, MyBatis, MySQL 기술 스택을 기반으로 개발되었다. 프론트엔드는 Vue.js를 통해 반응형 인터페이스를 구현하고, 백엔드는 SpringBoot를 통해 RESTful API를 제공하며, MyBatis를 영속성 계층 프레임워크로 사용하고 MySQL을 데이터 저장소로 채택하였다. 주요 기능에는 학생 정보 관리, 과정 평가 과제 배포, 성적 통계 분석, 증가성 평가 리포트 생성 등이 포함된다. 다양한 차원의 데이터 분석과 시각화를 통해 교수자는 학생의 학습 상태를 빠르게 파악할 수 있으며, 학생들에게도 맞춤형 피드백을 제공한다. 또한 다중 역할 권한 관리를 지원하여 데이터 보안과 작업 표준을 보장한다. 주요 키워드: SpringBoot, Vue.js, MyBatis, MySQL, 증가성 평가 리포트, 권한 관리.
데이터베이스 테이블 설계 학생 성장 기록 테이블 학생 성장 기록 테이블은 학생 개인 정보, 학습 궤적 및 증가성 평가 결과를 저장하기 위해 설계되었다. 레코드 생성 시간은 자동 함수를 통해 설정되며, 기록 ID가 기본 키로 사용된다. 학생의 성장 과정 중 핵심 데이터를 저장하는 구조로, 아래 표와 같다.
| 필드명 | 데이터 타입 | 설명 |
|---|---|---|
| record_id | INT | 성장 기록 ID (기본 키) |
| student_num | VARCHAR(20) | 학번 |
| student_name | VARCHAR(50) | 학생 이름 |
| class_num | INT | 반 번호 |
| entry_score | FLOAT | 입학 시점 성적 |
| current_score | FLOAT | 현재 성적 |
| progress_rate | FLOAT | 성장률 |
| assessment_date | DATETIME | 평가 일자 |
| creation_time | TIMESTAMP | 생성 시간 (자동 생성) |
과제 평가 테이블 과제 평가 테이블은 교사가 게시한 평가 과제 정보를 저장한다. 과제 ID가 기본 키이며, 생성 시간은 자동으로 기록된다. 과제의 기본 속성과 상태 정보를 담고 있다.
| 필드명 | 데이터 타입 | 설명 |
|---|---|---|
| assignment_id | INT | 과제 ID (기본 키) |
| subject_id | INT | 과목 ID |
| instructor_id | INT | 교수 ID |
| assignment_title | VARCHAR(100) | 과제 제목 |
| assignment_desc | TEXT | 과제 설명 |
| start_date | DATETIME | 시작 일자 |
| end_date | DATETIME | 종료 일자 |
| status_flag | VARCHAR(20) | 상태 (대기/진행 중/종료) |
| creation_time | TIMESTAMP | 생성 시간 (자동 생성) |
증가성 평가 리포트 테이블 증가성 평가 리포트 테이블은 시스템에서 생성된 평가 리포트를 저장한다. 리포트 ID가 기본 키이며, 생성 시간은 자동으로 기록된다. 학생 성장 분석 결과를 상세하게 저장한다.
| 필드명 | 데이터 타입 | 설명 |
|---|---|---|
| report_key | INT | 리포트 ID (기본 키) |
| growth_record_id | INT | 성장 기록 ID |
| student_number | VARCHAR(20) | 학번 |
| report_subject | VARCHAR(100) | 리포트 제목 |
| analysis_content | TEXT | 분석 내용 |
| suggestion_text | TEXT | 개선 제안 |
| generation_time | TIMESTAMP | 생성 시간 (자동 생성) |
| view_status | BOOLEAN | 열람 여부 |
기술 아키텍처 백엔드 기술: Spring Boot Spring Boot는 현대 자바 기업급 개발의 핵심 프레임워크로서 '규약 우선'의 설계 철학으로 애플리케이션 개발 패러다임을 재정의한다. 주요 특징은 다음과 같다:
- 자동 구성: XML 설정 파일 작성을 대폭 줄이는 자동 구성 메커니즘 제공
- 내장 서버: Tomcat/Jetty/Undertow를 내장하여 독립 실행 가능한 JAR 배포 지원
- 운영 준비: 헬스 체크, 메트릭 수집 등의 엔터프라이즈 기능 포함
- 마이크로서비스 친화: 분산 아키텍처를 자연스럽게 지원하며 Spring Cloud와 무결합
개발 장점: Starter 의존성 시스템과 스마트 자동 조립을 통해 개발자는 인프라 구축이 아닌 비즈니스 로직 구현에 집중할 수 있다. 단일 실행 가능한 JAR 형태의 배포는 운영 관리를 크게 간소화한다.
프론트엔드 기술: Vue.js Vue.js는 점진적 프레임워크 설계와 우수한 개발 경험으로 현대 프론트엔드 개발의 선호 솔루션이 되었다. 기술 특징은 다음과 같다:
- 반응형 데이터 흐름: 의존성 추적 기반의 반응형 시스템으로 효율적인 뷰 갱신 구현
- 컴포넌트 기반 아키텍처: 단일 파일 컴포넌트(SFC) 설계로 스타일, 로직, 템플릿의 완벽한 캡슐화
- 유연한 점진적 설계: 단순 뷰 라이브러리에서 완전한 SPA 솔루션까지 확장 가능
- 풍부한 생태계: Vue Router, Vuex/Pinia, Vue CLI 등 공식 도구 체계 완비
개발 효율: 직관적인 템플릿 문법과 강력한 지시문 시스템으로 복잡한 사용자 인터랙션을 간단하게 표현할 수 있다. 훌륭한 TypeScript 지원과 개발자 도구는 대규모 프로젝트에 안정적인 개발 보장을 제공한다.
핵심 코드 구현
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
@MapperScan(basePackages = {"com.repository"})
public class ApplicationStarter extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationStarter.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ApplicationStarter.class);
}
}
package com.controllers;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.utilities.ValidationHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.security.AnonymousAccess;
import com.entities.StudentEntity;
import com.entities.view.StudentView;
import com.services.StudentService;
import com.services.AuthTokenService;
import com.utilities.PaginationUtils;
import com.utilities.ResponseBuilder;
import com.utilities.QueryHelper;
import com.utilities.DataConverter;
import com.utilities.GeneralUtility;
import java.io.IOException;
@RestController
@RequestMapping("/students")
public class StudentController {
@Autowired
private StudentService studentService;
@Autowired
private AuthTokenService tokenManager;
@AnonymousAccess
@RequestMapping(value = "/authenticate")
public ResponseBuilder authenticate(String userId, String password, String verificationCode, HttpServletRequest request) {
StudentEntity student = studentService.selectOne(new EntityWrapper<StudentEntity>().eq("student_identifier", userId));
if(student == null || !student.getPassword().equals(password)) {
return ResponseBuilder.error("아이디 또는 비밀번호 오류");
}
String token = tokenManager.createToken(student.getId(), userId, "student", "학생");
return ResponseBuilder.success().put("token", token);
}
@AnonymousAccess
@RequestMapping("/enroll")
public ResponseBuilder register(@RequestBody StudentEntity newStudent) {
StudentEntity existing = studentService.selectOne(new EntityWrapper<StudentEntity>().eq("student_identifier", newStudent.getStudentIdentifier()));
if(existing != null) {
return ResponseBuilder.error("이미 존재하는 계정입니다");
}
Long studentId = new Date().getTime();
newStudent.setId(studentId);
studentService.insert(newStudent);
return ResponseBuilder.success();
}
@RequestMapping("/signout")
public ResponseBuilder logout(HttpServletRequest request) {
request.getSession().invalidate();
return ResponseBuilder.success("로그아웃 성공");
}
@RequestMapping("/profile")
public ResponseBuilder getCurrentProfile(HttpServletRequest request) {
Long userId = (Long)request.getSession().getAttribute("userId");
StudentEntity user = studentService.selectById(userId);
return ResponseBuilder.success().put("data", user);
}
@AnonymousAccess
@RequestMapping(value = "/passwordReset")
public ResponseBuilder resetPassword(String userId, HttpServletRequest request) {
StudentEntity student = studentService.selectOne(new EntityWrapper<StudentEntity>().eq("student_identifier", userId));
if(student == null) {
return ResponseBuilder.error("계정이 존재하지 않음");
}
student.setPassword("123456");
studentService.updateById(student);
return ResponseBuilder.success("비밀번호가 123456으로 초기화됨");
}
@RequestMapping("/dashboard")
public ResponseBuilder dashboard(@RequestParam Map<String, Object> parameters, StudentEntity filter,
HttpServletRequest request) {
EntityWrapper<StudentEntity> wrapper = new EntityWrapper<StudentEntity>();
PaginationUtils pagination = studentService.searchPage(parameters, QueryHelper.sort(QueryHelper.between(QueryHelper.likeOrEqual(wrapper, filter), parameters), parameters));
return ResponseBuilder.success().put("data", pagination);
}
@AnonymousAccess
@RequestMapping("/catalog")
public ResponseBuilder catalog(@RequestParam Map<String, Object> parameters, StudentEntity filter,
HttpServletRequest request) {
EntityWrapper<StudentEntity> wrapper = new EntityWrapper<StudentEntity>();
PaginationUtils pagination = studentService.searchPage(parameters, QueryHelper.sort(QueryHelper.between(QueryHelper.likeOrEqual(wrapper, filter), parameters), parameters));
return ResponseBuilder.success().put("data", pagination);
}
@RequestMapping("/collection")
public ResponseBuilder collection(StudentEntity filter) {
EntityWrapper<StudentEntity> wrapper = new EntityWrapper<StudentEntity>();
wrapper.allEq(QueryHelper.allEqualMapPrefix(filter, "student"));
return ResponseBuilder.success().put("data", studentService.selectListView(wrapper));
}
@RequestMapping("/search")
public ResponseBuilder search(StudentEntity filter) {
EntityWrapper<StudentEntity> wrapper = new EntityWrapper<StudentEntity>();
wrapper.allEq(QueryHelper.allEqualMapPrefix(filter, "student"));
StudentView result = studentService.selectView(wrapper);
return ResponseBuilder.success("학생 검색 성공").put("data", result);
}
@RequestMapping("/details/{id}")
public ResponseBuilder details(@PathVariable("id") Long id) {
StudentEntity student = studentService.selectById(id);
return ResponseBuilder.success().put("data", student);
}
@AnonymousAccess
@RequestMapping("/information/{id}")
public ResponseBuilder information(@PathVariable("id") Long id) {
StudentEntity student = studentService.selectById(id);
return ResponseBuilder.success().put("data", student);
}
@RequestMapping("/store")
public ResponseBuilder store(@RequestBody StudentEntity entity, HttpServletRequest request) {
if(studentService.selectCount(new EntityWrapper<StudentEntity>().eq("student_identifier", entity.getStudentIdentifier())) > 0) {
return ResponseBuilder.error("계정이 이미 존재함");
}
entity.setId(new Date().getTime() + new Double(Math.floor(Math.random() * 1000)).longValue());
StudentEntity check = studentService.selectOne(new EntityWrapper<StudentEntity>().eq("student_identifier", entity.getStudentIdentifier()));
if(check != null) {
return ResponseBuilder.error("이미 등록된 사용자");
}
entity.setId(new Date().getTime());
studentService.insert(entity);
return ResponseBuilder.success();
}
@RequestMapping("/create")
public ResponseBuilder create(@RequestBody StudentEntity entity, HttpServletRequest request) {
if(studentService.selectCount(new EntityWrapper<StudentEntity>().eq("student_identifier", entity.getStudentIdentifier())) > 0) {
return ResponseBuilder.error("계정이 이미 존재함");
}
entity.setId(new Date().getTime() + new Double(Math.floor(Math.random() * 1000)).longValue());
StudentEntity check = studentService.selectOne(new EntityWrapper<StudentEntity>().eq("student_identifier", entity.getStudentIdentifier()));
if(check != null) {
return ResponseBuilder.error("이미 등록된 사용자");
}
entity.setId(new Date().getTime());
studentService.insert(entity);
return ResponseBuilder.success();
}
@RequestMapping("/modify")
@Transactional
public ResponseBuilder modify(@RequestBody StudentEntity entity, HttpServletRequest request) {
if(studentService.selectCount(new EntityWrapper<StudentEntity>().ne("id", entity.getId()).eq("student_identifier", entity.getStudentIdentifier())) > 0) {
return ResponseBuilder.error("계정이 이미 존재함");
}
studentService.updateById(entity);
return ResponseBuilder.success();
}
@RequestMapping("/remove")
public ResponseBuilder remove(@RequestBody Long[] ids) {
studentService.deleteBatchIds(Arrays.asList(ids));
return ResponseBuilder.success();
}
}