그로쓰11_열한시십일분_졸업프로젝트_기술_정리본

김서영·2021년 5월 18일
0
post-thumbnail
post-custom-banner

CurVelio

프로젝트

Github https://github.com/graduateprojectA/take1

개요

Curvelio 커벨리오 = curriculum 커리큘럼 + revelio 리벨리오(숨겨져 있는 것을 보여주는 마법 주문)를 의미합니다.

CurVelio는 시간표 자동 생성 웹사이트 입니다. 사용자의 학번, 전공, 학기에 맞는 커리큘럼을 기반으로 시간표 자동 조합 및 생성, 교양 추천 기능이 가장 핵심적입니다. 사용자에게 맞춤형 시간표를 제공함으로써, 기존에 시간표 설계의 불편함을 해결해 줍니다. 예를 들어, 수강 신청 전 시간표 짜는 데 평균적으로 하루가 소요되었다면, 이건 클릭 몇 번만으로 10분 내로 시간표를 짤 수 있습니다.

사용자로부터 학번/전공/여태 들은 전공 및 필수교양 수업/원하지 않는 시간대, 원하지 않는 분반, 수업 방식에 해당하는 가중치값을 입력받습니다. 이를 기반으로 시간표가 자동으로 생성됩니다. 그리고 해당 수업 외에도 가중치 값을 적용하여 사용자에게 여러 교양 과목을 추천해줍니다.

핵심 알고리즘

개발 환경


구현 방법

기술구현1. [React][SpringBoot] React & SpringBoot 소개 및 연동

원래 리액트(React)와 노드js(NodeJs), MySQL을 사용하기로 예정되어 있었지만, 시간표 조합 알고리즘 연동 문제로 인하여, 노드js(NodeJs) 대신 스프링부트(SpringBoot)를 이용하게 되었습니다. 그래서 올해 1월 초부터 다시 연동 과정을 빡세게 진행해야 했습니다. 거의 한 달을 연동에만 쏟아부은 것 같습니다. 아마 리액트와 스프링부트 연동을 하시고 싶으시다면 이에 관련된 다양한 블로그 글들을 참고하여야 할 것 같습니다. 각 프로젝트에 맞는 버전이 있기 때문에 인내심을 가지고...!

React는 자바스크립트 라이브러리의 하나로,

사용자 인터페이스

를 만들기 위해 사용됩니다. MVC 중 V(View)의 역할이라고 볼 수 있습니다.

SpringBoot는 Java를 기반으로 한 웹 어플리케이션 프레임워크입니다. Spring의 초기 설정에 시간을 줄이고자 Spring Boot를 이용하기로 했습니다.

서버 사이드

개발에 사용하기 위해 사용할 소프트웨어 플랫폼이고, MVC 중 M(Model)와 C(Controller)의 역할을 할 것입니다.


연동 참고 사이트
1. https://sundries-in-myidea.tistory.com/71
2. https://dsc-sookmyung.tistory.com/21
3. https://hjjooace.tistory.com/entry/React-Spring-Gradle-Project-%EC%97%B0%EB%8F%99
를 참고했고, (이것만 참고한 것은 아닙니다!) 저희의 프로젝트는 maven이 아닌 gradle로 설정했습니다.

!) Maven과 Gradle의 차이 Maven을 사용하게 되면 pom.xml을, gradle을 사용하게 되면 build.gradle 파일을 통해 스프링과 스프링부트의 프로젝트를 관리하게 됩니다. Maven은 자바용 프로젝트 관리 툴이기 때문에 자바에서만 사용 가능하지만, Gradle은 Java 이외에도 C++, Python 등 다양한 언어를 지원합니다.

저희 팀 프로젝트는 알고리즘을 연동하고 이 과정 중 여러 언어들의 사용될 수 있어, 미리 gradle로 구현하였습니다.

/build.gradle
프로젝트가 마무리 되는 쯤에 이 파일 캡처를 올려서 새로 생성되는 파일과는 조금 다를 수 있습니다.

/src/main/resources/application.properties
DB에 관련된 정보가 있는 곳입니다. Curvelio 프로젝트 같은 경우는 이런 식으로 설정했습니다. username은 db user name을, password는 db password를 의미하는 것입니다. 저희는 Mysql.jdbc.Driver에 해당하는 작업이 있어서 위와 같이 다른 부분도 따로 설정을 해줘야했습니다.


기술구현2. [SpringBoot][RestAPI] 백엔드 초짜의 도전

Curvelio 프로젝트에서 저의 포지션은 백엔드 개발!!
백엔드 도전은 처음이라...사실 저희 팀 조장에게 거의 다 배웠습니다...(일명 지니지니찬스...!)

우선 기술구현1을 통해 진행한 연동을 기반으로 SpringBoot에 대한 기본적인 이해를 해보도록 하겠습니다.

IntelliJ에서 이 프로젝트 파일을 연 모습!! 이게 큰 틀이라고 볼 수 있는데, 이 큰 틀이 없다면, 아마 파일 위치를 다시 설정하고 프로젝트를 열어야 할 수 있습니다. (은근 자주 하는 실수일 수 있으니, 놀라지 마세용~) 그리고 intellij가 늦게 열리는 편이라, 가끔 다 안 뜰 수도 있으니 기다리면 됩니당!

Step1. SpringBoot 프로젝트 실행

저희 프로젝트가 끝나가는 시점에 쓰는 글이기 때문에 파일들의 존재가 굉장히 다를 수 있습니다! 다만, src/main/java 아래 표시된 (파일명관련)Application.java 파일이 있다면 클릭! 저희 팀은 BoardBack 관련 파일이라 이름이 저거...


그런 후에 저 왼쪽 초록색 버튼 클릭하면 실행을 시킬 수 있습니다.

React와 달리 SpringBoot는 자동 렌더링(?)이 되지 않습니다. 따라서, 코드에 수정이 있을 때마다 저장(Ctrl+s) 후, 재실행 시켜주는 것이 좋습니다. 그리고 가끔 또 이상한 에러가 날 때는 캐시를 한 번 지워주는 것도 좋습니다. Intellij와 같은 개발툴에서 위에 검색하세요! "invalidate Cashes/restart"

Step2. SpringBoot 프로젝트 중요 파일들

이 부분을 설명하기 전 MVC에 대한 개념과 관련 파일들을 알아야 합니다.
출처 : https://velog.io/@ljinsk3/MVC-%ED%8C%A8%ED%84%B4
MVC는 Model View Controller의 약자입니다. 하나의 디자인 패턴인데, 스프링 MVC라고 하면 스프링이 제공하는 웹 어플리케이션 구축 전용 MVC 프레임워크입니다.

  1. 모델(Model) : 비즈니스 규칙을 표현
  2. 뷰(View) : 프레젠테이션을 표현
  3. 컨트롤러(Controller) : 위 두가지를 분리하기 위하여 양측 사이에 배치된 인터페이스

우리는 React와 SpringBoot를 통해 사용자의 데이터를 주고 받고, SpringBoot와 MySQL에서 데이터를 가져오거나 업데이트하는 등의 작업들을 할 것입니다.

예를 들어, React를 이용해 디자인한 웹사이트 화면에서 사용자가 user id와 user pw 등을 입력했다고 합시다. 그럼 그 정보들을 그대로 서버인 SpringBoot는 받아서 다시 MySQL DB에 넣어야 하겠죠??? 지금부터 그 과정을 대략 살펴봅시다.

(사용자에게 보여지는 화면 View는 React를 통해 진행하므로, 여기선 생략하겠습니다.) SpringBoot에서 Model과 Controller에 대한 관리를 해줄 것입니다. 마찬가지로 MySQL에서 database에 대한 것을 지정해주므로, SpringBoot에서의 model 관리도 어렵지 않습니다.

먼저 저희 프로젝트에서는 src/main/java 아래에 크게 Controller, Model, Repository, Service라는 중요 폴더들을 두었습니다.

이제 그 폴더들에 해당하는 여러 파일들을 넣을 것입니다.

  • SpringBoot/Controller
    React에서 정보를 받는 곳입니다. Controller 부분은 "RestApi"를 이용합니다.

  • SpringBoot/Model
    MySQL에 저장된 database의 model들을 정의합니다. 우리는 아마 MySQL DB를 이용하여 database, table, column, row 들을 만들었을 것입니다. 그것들에 대한 정의를 하는 곳이라 생각하면 편합니다.

  • SpringBoot/Repository
    DB 속 데이터를 이용할 예정한다면 이 Repository가 중요합니다. DB에서 정보를 받는 곳입니다. 쿼리문을 사용 할 수 있습니다. /Service에서 이용할 쿼리문 함수를 정의합니다.

  • SpringBoot/Service
    React와 DB 간의 연결 활동 서비스 진행하는 부분입니다.

Step3. RestApi의 적용

Curvelio 프로젝트는 RestApi를 통해 Springboot와 React간의 작업을 진행합니다.
RestApi에 대한 관련 내용이 굉장히 인터넷에 많이 있지만,
쉽게 말해서,
REACT -> SpringBoot로 정보를 보낼 때 : POST
SpringBoot -> REACT로 정보를 보낼 때 : GET
라고 생각하고 진행했습니다.

그럼 어떤 식으로 정보를 보내고 받는지 저희 서비스의 회원가입 과정을 봅시다.

Schema/User

create table User(
user_no int auto_increment, #인덱스
user_id int not null, #학번
user_pw varchar(100) not null, #비밀번호
user_major int not null, 
user_grade int,
primary key(user_no),
foreign key(user_major) references Majors(major_no) on update cascade on delete cascade 
);

/model/User

package com.board.back.model;

import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

//유저테이블
@Entity
@Table(name = "User")
@DynamicInsert
@DynamicUpdate
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_no")
    private Integer user_no;
    // 아이디(학번)
    @Column(name = "user_id")
    private Integer user_id;

    // 비밀번호
    @Column(name = "user_pw")
    private String user_pw;

    // 전공
    @Column(name = "user_major")
    private Integer user_major;

    // 학년
    @Column(name = "user_grade")
    private Integer user_grade;

    public Integer getNo() {
        return user_no;
    }

    public void setNo(Integer user_no) {
        this.user_no = user_no;
    }

    public Integer getId() {
        return user_id;
    }

    public void setId(Integer user_id) {
        this.user_id = user_id;
    }

    public String getPassword() {
        return user_pw;
    }

    public void setPassword(String user_pw) {
        this.user_pw = user_pw;
    }

    public Integer getMajor() {
        return user_major;
    }

    public void setMajor(Integer user_major) {
        this.user_major = user_major;
    }

    public Integer getGrade() {
        return user_grade;
    }

    public void setGrade(Integer user_grade) {
        this.user_grade = user_grade;
    }

    public User(Integer user_id, String user_pw, Integer user_major, Integer user_grade) {
        super();

        this.user_id = user_id;
        this.user_pw = user_pw;
        this.user_major = user_major;
        this.user_grade = user_grade;
    }

    @Override
    public String toString() {
        return "User [user_no=" + user_no + ", id=" + user_id + ", password=" + user_pw + ", major=" + user_major
                + ", grade=" + user_grade + "]";
    }
}

/controller/UserController

package com.board.back.controller;
import com.board.back.model.User;
import com.board.back.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequestMapping("/api")
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    @PostMapping("/user")
    public void createUser(@RequestBody User user) {
        System.out.println("@PostMapping(\"/user\")");
        System.out.println(user.toString());
        userService.createUser(user);
    }
    }

React에서 회원가입 폼이 채워진 채로, 그 정보들을 Springboot로 넘겨주는 과정입니다. @PostMapping을 통해 React로부터 정보를 받는 다는 것을 알 수 있습니다. 그 뒤의 ("/user")은 서버로 보내는 주소라고 보면 됩니다. 그럼 Springboot에서는 인자로 User type의 user 정보를 받습니다. 이때 @RequestBody를 사용해야 JSON raw 형태로 받을 수 있습니다. 그럼 이 정보들을 인자로 받았으니, userService 코드 에 있는 createUser 함수에게 넘겨줍시다.

/service/UserService

package com.board.back.service;

import com.board.back.exception.ResourceNotFoundException;
import com.board.back.model.*;
import com.board.back.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private CourseRepository courseRepository;
    @Autowired
    private CheckFieldRepository checkFieldRepository;
    @Autowired
    private FieldRepository fieldRepository;
    @Autowired
    private UserCourseRepository UserCourseRepository;
    @Autowired
    private UserCheckFieldRepository userCheckFieldRepository;
    @Autowired
    private UserFieldRepository userFieldRepository;

//회원가입
    public void createUser(User user) {
        //User 정보 저장
        userRepository.save(user);
}
}

아까 받은 User user 인자를 받았으니, 이것을 repository로 가서 저장을 해줍니다.

/repository/UserRepository

package com.board.back.repository;
import java.util.List;
import com.board.back.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface UserRepository extends JpaRepository<User, Integer> {
}

UserRepository 의 코드가 굉장히 뭐가 없죠...? 그 이유는 interface 형태로 JpaRepository를 상속받았고, JpaRepository 내에 save라는 함수가 있는데 이 함수를 이용했기 때문입니다. 그냥 일정한 모델 형태로 정보를 그대로 저장하고 싶다면 이런 식으로, JpaRepository에서 상속을 받고, save함수를 이용하면 됩니다.

그럼 만약에 쿼리문을 써야하면 어떻게 해야하는 지 궁금하죠?? Controller와 Service는 계속 인자값을 받거나 리턴값을 통해 react와의 작업을 진행하거나, springboot 내에서 작업을 진행하면 됩니다. 다만, repository의 경우라면, 쿼리문을 쓰는 경우를 보겠습니다. 아마 MySQL 쿼리문만을 잘 안다면 쉽게 따라할 수 있습니다.

이것은 저희 ClassRepository의 일부입니다.
@Query value 옆에 흔히 우리가 아는 쿼리문 문장을 적어주면 됩니다! 그 밑에는

(Return값) (함수명) (인자값)

이런 식으로 적어주면 됩니다. 중요한 것은 인자값을 표현할 때는 ":(인자명)" 이런 식으로 표현해줘야한다는 점과 테이블명을 "s"처럼 지정을 해줘야한다는 점, 마지막으로 @Param표현에 인자명을 맞춰야한다는 점이 있습니다.

참고 블로그 : https://goddaehee.tistory.com/203?category=367461


기술구현3. [React] 디자인

사실 이번에 Backend뿐만 아니라, React 디자인 부분을 맡았습니다. React는 javascript로 코드를 짜기 때문에 html css와 꽤나 비슷하게 디자인 할 수 있습니다. 다만, 리액트에서 특히 체크박스(Checkbox)를 디자인하는 것은 꽤나 까다롭습니다. 그렇기 때문에 Checkbox를 중점적으로 설명을 하겠습니다.

1) Checkbox의 크기 조절 (Checkbox resize)

큰 틀을 보자면 어떤 형태에 className을 다는 형식으로 할 것입니다.
예를 들어 div 내에 checkbox가 있으면, div에 className을 붙여서 한 번에 적용하는 것이라고 볼 수 있습니다.

저희 코드를 보면 cservice/Checkbox.js라는 코드가 있습니다.
컴포넌트를 분리해서 여기다가 Checkbox의 기능을 담고 있습니다.

보면 li 태그를 이용합니다. li 태그 속 checkbox를 만들었습니다.
그래서 저희는 li 태그 속 className을 이용할 것입니다. className에서 적용하고자 하는 이름을 생성하고,
css/style.css 코드에서 style을 지정해줍니다.

이런 식으로 Checkbox 옆 label의 폰트 크기를 조절하고, Check1 input[type="checkbox"] {} 를 이용해서 체크박스의 크기를 조절할 수 있습니다.

2) Checkbox의 숨기기 지정(checkbox invisible)
저희 프로젝트에서는 "원하지 않는 시간대"를 설정하는 부분이 있습니다. 이 부분을 시간표 형태를 이용하여 시간대를 설정하도록 했습니다.

이런 식으로 말이죠. 이 부분에 대한 디자인 코드를 공유하겠습니다. 아까 1)에서의 과정과 유사하지만 조금 더 설정해줘야합니다.

table 중 한 행을 떼어서 보도록 하겠습니다.

className을 통해 TimeDiv, DayDiv, CheckDiv 라는 이름을 각각 달았습니다. 그렇지만 여기서도 중요한 코드는 checkbox를 포함하고 있는 DayDiv입니다.

조금 전과 유사하게 input[type="checkbox"]의 형태를 달아, checkbox의 디자인을 진행하면 됩니다. background:none; appearance:none;을 통해 형태를 아예 없애줍니다. 그리고 input[type="checkbox"]:checked 인 상태에서 색깔을 지정해주고, 크기를 설정하는 형식으로 진행했습니다.

3) Checkbox의 디자인(Checkbox) design
가장 무난한 코드의 형태는 아래처럼 볼 수 있습니다.

특히나, span 태그는 바꾸고자 하는 checkbox 에 해당합니다.
Curvelio 체크박스 디자인은 대부분 크기 조정이나, 색깔 조정, 숨기기 등의 디자인을 적용했으나, 만약 체크박스 안의 자체 디자인을 바꾸고 싶다면, span 태그와 input 태그의 css작업을 통해 디자인 변경을 진행할 수 있습니다.

기술구현4. [MySQL] 데이터베이스

정말 데베만 몇번을 바꿨는 지를 모릅니다...원래 회사 들어가서도 이렇게 데베를 많이 수정하게 될까요??ㅠㅠㅠ회사에 가면 제발 데베 똑순이/똑돌이가 있기를 바랍니다...

사실 저희 Curvelio 프로젝트는 굉장히 대학생인 저희와 밀접하고, "생각만" 하면 쉬운 프로젝트입니다. 그렇지만, 아래를 보시다시피 스키마가 굉장히 복잡합니다. "왜 시간표를 자동으로 만들어주는 프로그램은 없을까?" 하면 답은 저 스키마와 넣어야 하는 데이터 양과 복잡한 알고리즘 구현에 있습니다....

E-R diagram

전체적인 스키마 구조입니다. 오른쪽 마우스 클릭해서 새 탭 열기하면 아마 더 자세히 볼 수 있을 겁니다.

변영만(팀원)이 정말 만날 때마다 "야 우리 데베 또 바꿔야할 지도 모름"라고 했습니다.

Schema

아래 깃 허브 주소에서 Schema.txt 에 자세한 스키마를 확인할 수 있습니다.

https://github.com/graduateprojectA/take1


열한시 십일분...우리 팀 화이팅....!

프론트, 백, 호스팅, 연동 과정에 조금 더 구체적인 정보를 원한다면....
우리 자랑스러운 조장...베티 장의 블로그 : https://velog.io/@betty214/%EC%BB%A4%EB%B2%A8%EB%A6%AC%EC%98%A4Curvelio-%EC%8B%9C%EA%B0%84%ED%91%9C-%EC%83%9D%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8#1-java%EB%B0%B1

시간표 조합 알고리즘, 교양 추천 알고리즘에 대한 설명을 원한다면....
우리 자랑스러운 고집왕...변영만의 블로그 :
https://velog.io/@dudtls11444

profile
하지만 저는 이겨냅니다. 김서영이죠?
post-custom-banner

0개의 댓글