📢 오늘의 목표 📢
✔️ 알고리즘, SQL 문제 풀이
✔️ 알고리즘 코드카타
✔️ SQL 코드카타
✔️ 프로그래머스 Level 2
✔️ Java 팀 프로젝트
✔️ main이 너무 더러움. 데이터 관리할 곳 만들기.
✔️코드 리뷰
✔️ Java 보충반 수업
✔️ 스프링 공부: 3-08 까지
⏱️ 오늘의 일정 ⏱️
9:00 ~ 10:00 - 알고리즘 코드카타
10:00 ~ 10:30 - 팀 프로젝트 회의
10:30 ~ 11:30 - 팀 프로젝트 개선 - main의 책임 분리
11:30 ~ 13:00 - 스프링 공부 (점프 투 스프링 부트)
13:00 ~ 14:00 - 점심시간
14:00 ~ 15:00 - 팀 프로젝트 진행
15:00 ~ 16:00 - Java 보충반 수업
16:00 ~ 18:00 - 팀 프로젝트 진행
18:00 ~ 19:00 - 저녁 시간
19:00 ~ 20:00 - 스프링 공부 (점프 투 스프링 부트)
20:00 ~ 21:00 - TIL 작성
📜 Chapter 1. 알고리즘 문제 풀기
✔️ 알고리즘 코드카타
배열의 평균값
CodingTest_AutoSave/프로그래머스/0/120817. 배열의 평균값 at main · MetroDefro/CodingTest_AutoSave
모든 코딩 테스트 자동 저장. Contribute to MetroDefro/CodingTest_AutoSave development by creating an account on GitHub.
github.com
✔️ SQL 코드카타
이름에 el이 들어가는 동물 찾기
CodingTest_AutoSave/프로그래머스/2/59047. 이름에 el이 들어가는 동물 찾기 at main · MetroDefro/Codin
모든 코딩 테스트 자동 저장. Contribute to MetroDefro/CodingTest_AutoSave development by creating an account on GitHub.
github.com
✔️ 프로그래머스 Level 2
카펫
CodingTest_AutoSave/프로그래머스/2/42842. 카펫 at main · MetroDefro/CodingTest_AutoSave
모든 코딩 테스트 자동 저장. Contribute to MetroDefro/CodingTest_AutoSave development by creating an account on GitHub.
github.com
N개의 최소공배수
CodingTest_AutoSave/프로그래머스/2/12953. N개의 최소공배수 at main · MetroDefro/CodingTest_AutoSave
모든 코딩 테스트 자동 저장. Contribute to MetroDefro/CodingTest_AutoSave development by creating an account on GitHub.
github.com
📜 Chapter 2. Java 팀 프로젝트
✔️ main이 너무 더러움. 데이터 관리할 곳 만들기.
public class DataManager {
// 데이터 저장소
private static List<Student> students;
private static List<Subject> subjects;
private static List<Score> scores;
// getter
// 데이터 추가
// 데이터 삭제
// 데이터 초기화 기능
// 서치 기능
}
내 할 일은 다 끝냈지만 팀원들의 편의와 객체지향을 위해 메인 클래스(app 클래스)의 기능을 좀 분담하였다.
우선 데이터를 가지고 있고, 초기화하고, 추가하고, 삭제하고 검색하는 기능을 가진 DataManager를 따로 분리했다.
📜 Chapter 3. 스프링 찍먹
✔️ 3-08 글쓴이 항목 추가하기
우선 질문, 답변 엔티티에 글쓴이 속성을 추가한다. 한 글쓴이가 여러 글을 쓸 수 있으므로 ManyToOne이 된다.
이제 실제 저장 될 때도 글쓴이도 함께 저장해야 한다.
public class AnswerController {
//....
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
Question question = this.questionService.getQuestion(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
if(bindingResult.hasErrors()) {
model.addAttribute("question", question);
return "question_detail";
}
this.answerService.create(question, answerForm.getContent(), siteUser);
return String.format("redirect:/question/detail/%s", id);
}
}
스프링 시큐리티가 제공하는 Principal 객체를 사용하면 현재 로그인한 사용자의 정보를 알 수 있다.
principal.getName()으로 사용자명 또한 얻어올 수 있다.
public class UserService {
//...
public SiteUser getUser(String username) {
Optional<SiteUser> siteUser = this.userRepository.findByusername(username);
if (siteUser.isPresent()) {
return siteUser.get();
} else {
throw new DataNotFoundException("siteuser not found");
}
}
}
그래서 사용자명으로 User객체를 받을 수 있는 getUser 메서드를 만들었다.
AnswerService의 create 메서드도 함께 수정해줘야 함.
질문도 동일하게 수정해준다.
@PreAuthorize("isAuthenticated()") 애너테이션을 사용하면 해당 메서드는 로그인한 경우에만 실행된다.
로그아웃 상태에서 호출할 경우 로그인 페이지로 강제 이동된다.
public class AnswerController {
@PreAuthorize("isAuthenticated()")
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
// ...
}
}
로그인 후 사용해야 하는 모든 곳에 붙여주자.
//...
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
//...
}
그리고 반드시 SecurityConfig에도 @EnableMethodSecurity(prePostEnabled = true) 애너테이션을 붙여주어야 @PreAuthorize 애너테이션을 사용할 수 있다.
아직 문제가 있다. 답변등록을 누를 경우 로그인 화면으로 이동이 되기야 하지만 입력이 활성화 되어있다는 것.
그래서 여기 글자 입력 자체를 막는 것이 좋을 것이다.
<textarea sec:authorize="isAnonymous()" disabled th:field="*{content}" class="form-control" rows="10"></textarea>
<textarea sec:authorize="isAuthenticated()" th:field="*{content}" class="form-control" rows="10"></textarea>
sec:authorize="isAnonymous()", sec:authorize="isAuthenticated()"는 현재 사용자의 로그인 상태를 체크하는 속성이다.
이를 이용하여 템플릿을 수정했다. (로그아웃일 경우 disable 처리)
그럼 이제 글쓴이가 누군지 표시되도록 템플릿을 수정해야한다. 딱히 새로운 기능을 쓴 건 아니므로 생략.
✔️ 3-09 수정과 삭제 기능 추가하기
[수정] 버튼이 로그인한 사용자와 글쓴이가 동일할 경우에만 노출되어야 한다.
템플릿에서 #authentication.getPrincipal().getUsername() == question.author.username 를 사용해 알 수 있으며 #authentication.getPrincipal()은 타임리프에서 스프링 시큐리티와 함께 사용하는 표현식이다.
public class QuestionController {
// ...
@PreAuthorize("isAuthenticated()")
@GetMapping("/modify/{id}")
public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
Question question = this.questionService.getQuestion(id);
if(!question.getAuthor().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
questionForm.setSubject(question.getSubject());
questionForm.setContent(question.getContent());
return "question_form";
}
}
수정 기능이 작동하도록 Get api를 추가.로그인한 사용자와 질문의 작성자가 동일하지 않을 경우 예외처리를 해주고 있다.그리고 원래 질문의 제목과 내용을 id로 조회해 가져가 form에 셋해줬다.
<form th:object="${questionForm}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
질문 등록 form을 수정할 때도 함께 사용하기 위해 action 속성을 제거했다. action 속성 없이 폼을 전송하면 자동으로 현재 URL을 기준으로 전송되는 규칙이 있다. 다만 CSRF값이 자동으로 생성되지는 않기 때문에 hidden 형태로 추가한 것
public class QuestionController {
//...
@PreAuthorize("isAuthenticated()")
@PostMapping("/modify/{id}")
public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal
, @PathVariable("id") Integer id) {
if(bindingResult.hasErrors()) {
return "question_form";
}
Question question = this.questionService.getQuestion(id);
if(!question.getAuthor().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
this.questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
return String.format("redirect:/question/detail/%d", id);
}
}
수정 기능은 질문을 등록할 때와 별 차이점이 없다.
질문 삭제 템플릿도 질문 수정이랑 다를 게 거의 없다.
href="javascript:void(0);" th:data-uri="@{|/question/delte/${question.id}|}
href 속성값을 javascript:void(0)로 설정하고 삭제를 실행할 URL을 얻기 위해 th:data-uri 속성을 추가한 뒤, [삭제] 버튼을 클릭하는 이벤트를 확인하기 위해 class 속성에 delete 항목을 추가했다. href에 삭제를 위한 URL을 직접 사용하지 않고 이러한 방식을 사용한 이유는 [삭제] 버튼을 클릭했을 때 ‘정말로 삭제하시겠습니까?’와 같은 메시지와 함께 별도의 확인 절차를 중간에 끼워 넣기 위해서이다.
이 부분은 아직 잘 모르겠어서 그냥 넘어갔는데 책에서도 이해 안돼도 다음 실습으로 가자고 써져있었다...
뭐냐면 삭제 경고 문구를 띄우기 위해 자바스크립트 코드를 작성해야 한다는 것...나는 자바스크립트가 싫다.
<script type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
element.addEventListener('click', function() {
if(confirm("정말로 삭제하시겠습니까?")) {
location.href = this.dataset.uri;
};
});
});
</script>
delete라는 클래스를 포함하는 컴포넌트를 클릭하면 ‘정말로 삭제하시겠습니까?’라고 질문하고 [확인]을 클릭했을 때 해당 컴포넌트에 속성으로 지정된 data-uri값으로 URL을 호출하라는 의미
📜 Chapter 4. Java 보충반 수업
자바의 기본 문법에 대한 강의였으나 도입에서 깨끗한 코드에 대해 말해주셔서 좋았다.
그리고 특수문자 $도 변수 명에 사용할 수 있다는 걸 정말 잊어먹고 있었음...
✔️ Java의 네이밍 컨벤션
패키지 이름: 전부 소문자로 구성(카멜X 스네이크X)
클래스 이름: 명사, 파스칼 표기법
인터페이스 이름: 명사/형용사
메서드 이름: 동사/전치사로 시작, 카멜 표기법
변수 이름: 카멜표기법
상수: 대문자 + 언더스코어
그리고 Java에서 괄호를 사용할 때는 주로 아래로 내리지 않고 코드 옆에 붙여쓴다고 한다.
🌙 오늘을 마치며 🌙
팀 프로젝트를 하려니 개인 공부 시간이 안 났다... 그나마 내 구현은 어제 다 했어서 어제보단 나았지만 내가 팀장이고 어쨌든 팀과 계속 이야기를 나누어야 하다 보니 바빴다.
부트캠프 시작하고 오늘 제일 말을 많이한 듯.
개인 과제는 잘 했다고 피드백 받았다!
'공부 기록 > 내배캠Java_5기' 카테고리의 다른 글
[내배캠][TIL] 17일 차 - 수요일, 팀프 개선하기 (0) | 2024.05.08 |
---|---|
[내배캠][TIL] 16일 차 - 화요일, 이번 주는 주4일 (0) | 2024.05.07 |
[내배캠][TIL] 14일 차 - 목요일, 팀 프로젝트 시작 (1) | 2024.05.02 |
[내배캠][TIL] 12일 차 - 화요일, 빡세게 공부한 날 (1) | 2024.04.30 |
[내배캠][TIL] 11일 차 - 월요일, 쉬면서 하자. (0) | 2024.04.29 |