본문으로 건너뛰기

Algorio 개발 노트

📅 22-03-24

📌 Cannot resolve symbol 에러 해결

  • 잘 돌아가다가 어느날 갑자기 모든 import에 빨간줄 그어져서 당황
  • Refresh Gradle Dependencies로 해결

📅 22-03-25

📌 젠킨스 설치 삽질 끝

📌 알게된 것

  • 포트가 바뀌지 않았던 이유가 포트 충돌이 아니라 conf 수정 후 jenkins start 대신 install jenkins를 또 해서 버그가 터졌던 것 같음
    • 이 경우 무조건 재설치 해야 함. 백날천날 jenkins restart 해도 버그 안풀림
  • GitHub에 있는 jenkins App은 jenkins 프로젝트 ci 전용임: https://ci.jenkins.io/
    • 내 프로젝트에 같은 방식 쓰고 싶으면 App을 따로 만들어야 함 [튜토리얼]
  • jenkins.qriositylog.com 과 같은 서브도메인을 원한다면 리버스 프록시 설정해야 함
  • ‘docker는 왜 쓰는걸까’에 대한 결론
    • jar 말고 도커 이미지로 배포하면 편함
    • 컨테이너 단위 백업이 편함
    • Ops의 편의성 때문이고 없어도 문제 생기진 않음

📌 추가로 생각해본 것

  • docker ci를 사용하면 얘가 다 빌드할건데 그때 jenkins는 무엇을 하는가
    • 아마 unit test...?
    • 젠킨스 + docker 사용 사례 더 찾아보고 무난한 조합법으로 결정하기

architecture_0

architecture_1

📅 22-03-27

📌 JPA 적용 및 CRUD 테스트 연습

📅 22-03-30

📌 등록/수정/조회 API 작성 연습

📅 22-04-02

📌 등록/수정/조회 API 테스트 코드 작성 및 push

  • 기록: 작성예정

📅 22-04-03

📌 젠킨스 GH App 생성 및 연결

  • 아주 좋은 글
    • App credential config에서 owner 명시해주어야 500 internal error 풀림
  • pem 변환은 WSL로 함: 경로 찾기
  • gradle test 돌리기에 인스턴스가 너무 협소해서 하루종일 돌아감...
    • 서버 마비돼서 reboot 하고 iptables 다시 설정해줌
  • App을 써도 WebHook으로 트리거 시키는건 똑같음. App은 그냥 Credential 용도같음. WebHook으로 할 때 commit status는 어떻게 돌아오는지 나중에 확인해보기
  • 젠킨스 UI가 너무 구려서 Blue Ocean 플러그인이 필수인듯,,
  • 더 좋은 무료 서버 구할 시간 없어서 일단 GH Action으로 테스트 CI 구축함.

📌 GH Action Test, Build & Deploy CI 구축

  • Test는 간단히 템플릿에서 꺼내다 씀
    • 테스트 CLI 로그 이쁘게 보여주는 플러그인: com.adarshr.test-logger
  • Build는 아직 Dockerfile 없어서 도커 배포까진 안됨. 나중에 작성하기
  • workflow_run 트리거가 default branch(main) 에서만 작동된다는 점은 괜찮은데, commit status 안나오는건 좀 치명적이라 WebHook으로 status 보여주는 workflow를 하나 더 추가함.
  • GH Action 커스텀 뱃지 가능한거 오늘 처음 알음. url에 workflow filename이 아니라 workflow name이 들어가는게 특이함.
  • 처음에 도커허브에서 build 시켜서 이쁘게 commit status가 App으로 나오는 걸 의도했으나, 최근 도커허브 빌드 기능이 유료화되어서 빠른 손절.
  • branch 전략 시간날때 정리하기
  • 배포 전략 고민하기 (프론트, 백 분리 관련)

📅 22-04-05

📌 Docker 배포 CI 에러 해결

  • Dockerfile 작성했더니 다음의 에러 발생
Step 3/4 : COPY ${JAR_FILE} app.jar
When using COPY with more than one source file, the destination must be a directory and end with a /
  • 로그 읽어보면 여러개 jar이라 단일 jar에 copy가 안되니 path 형식으로 arg를 넘기라는 건데, 다른 사람들은 똑같은 명령어로도 잘만 돌리니까 의문.
    • 확인 결과 내가 gradle build 시키면 build/libs경로에 plain이 붙는 jar이 하나 더 생겼음
    • 이유 찾아보니 스프링부트 2.5 부터 plain도 생기도록 바뀌었다고 함. [참고]
    • plain 없이 그냥 executable jar만 빌드하려면 gradle bootJar 을 사용해야 함
    • Dockerfile은 그대로 둔 채로 yml에 gradle 명령어만 바꿔주고 에러 해결

📌 Package Publish CI 구축

reference

  • GH에서 release 할때 태그 추가하면 ref가 refs/heads/main이 아닌 ~~ 이다. [정답은 여기에]

📌 1.0.0-beta.1 릴리즈 👏

  • GH에서 prerelease 말고 그냥 release로 작성해야 제목이 뜸

📌 Branch 전략

  • main: 푸시되면 도커로 자동 배포됨. 나중에 EC2 SSH까지 연결할거니 주의..
    • docs 수정은 대체 어디서 할지 모르겠음.
  • develop: 각 릴리즈 별로 feature merge 한 뒤 버전 수정같은거 해놓는 브랜치
    • CI 수정은 가급적 develop에서 하기
  • feature: 필요할때마다 develop에서 분기하고 merge되면 자유롭게 삭제 가능한 브랜치
  • release 브랜치는 개인 프로젝트 운영하는데 너무 과하다 싶어서 안 씀.

📌 Frontend, Backend repo 분리

  • 합치면 다루기도 불편하고 폴더 구조도 마음에 안들 것 같아서 결국 분리하기로 결정
  • 여태까지 작업한 repo는 Algorio에서 Algorio-Backend로 리포명 수정
  • Frontend는 AWS말고 Vercel 같은거 쓰면 좋을듯. commit status도 이쁘게 써줌.

📅 22-04-06

📌 GitHub 설정 관련

  • Environments 설정 very EZ..
  • organization 멤버인거 People에서 Public으로 맞춰야 프로필에 보여짐;

📌 프론트엔드 초안 완성

  • 책의 @material-ui/core 가 예전 버전이어서 최신 버전인 @mui/material 설치

Installation - MUI


  • create-react-app을 현재 경로에 하고 싶으면 create-react-app . 하면 됨
  • 프로젝트 생성시 기본으로 주어지는 App.css에 center로 강제하는 CSS가 있으므로 제거하든가 해야 함
  • 아주 좋은 뉴비 튜토리얼
    • 근데 최신버전 아니라서 조금씩 수정해서 써야 함
    • justify → justifyContent
    • spacing은 예전 버전에서 40 적용 안됨. 스펙에 없음.

React App


  • 부모 하위에 flex-grow: 1 자식과 flex-grow: 0 자식이 있으면 0 자식이 우측으로 정렬됨
  • excerpt 영역 왼쪽을 날짜, 오른쪽을 티어표로 바꾸기

📌 Deploy 관련

  • Vercel은 organization repo의 경우 유료 플랜 필요함.....
  • Netlify 쓰는데 commit status 안와서 webhook 날려야 할 것 같음 (CI repo 파서 테스트 해보기)

📅 22-04-08

📌 Database “mem:testdb” not found 오류 해결

h2_console

  • 최신 스프링 버전부터 h2 연동 시 기본url 값으로 testdb 가 아니라 랜덤으로 생성되어 testdb를 찾을 수 없는 것이었음.
  • application.propertiesspring.datasource.generate-unique-name=false 를 추가해서 testdb로 고정시켜주니 해결

📌 /api/v1/posts/1 으로 조회 시 406 Whitelabel error 버그 해결

  • h2에서 insert into .. values .. 해서 데이터 넣은 뒤 /api/v1/posts/1 로 브라우저 접속했는데 에러 페이지 (status는 406)

    • PostsResponseDto에 getter 빼먹었더라...........추가해서 해결
    • 이건 에러 원인이 아니지만 원인 찾다가 PostMapping url이 /api/... 가 아닌 api/... 으로 되어있는걸 발견. 작동 차이는 없는데 앞에 slash 붙는게 시멘틱이라고 함.
  • error 코드 별 특징을 발견함 (500이랑 406 서로 거꾸로 쓴거 아님)

wlabel_500

  • error 페이지에서 status가 500이면 파라미터를 잘못 넣은 것 (아예 불가능한 url)

wlabel_406

  • status가 406이면 파라미터가 이상한건 아니고 내부에서 처리하다가 어떤 오류로 인해 response를 줄 수 없는 것 (어노테이션같은거 잘 보기)

📌 추가로 공부한 것

📅 22-04-09

📌 백엔드 API 추가

  • findAll(): posts/all
  • 좋은 스프링 공짜 레퍼런스 발견함

점프 투 스프링부트


📌 백엔드 CORS 설정


React 프로젝트에서 api 호출하기


📌 프론트엔드 axios로 REST API 호출

  • 빠르게 요청 날려두기 위해 useEffect 안에서 호출
  • rendering block 하지 않기 위해 async로 호출: 나머지 컴포넌트는 렌더링 ㄱㄱ
  • response 오면 setState로 re-render 시킴: posts 영역만 다시 렌더링

React Hooks 이해하기 (1)


📌 백엔드 게시글 metadata 연동

  • 백엔드 H2에서 SQL로 테스트 데이터 넣은 후 프론트에서 posts/all로 API 호출하여 받은 데이터를 뿌려주는지 확인
  • tag를 List<String>으로 하려 했는데 DB에서 실제 Array형으로 들어갈 수 없기 때문에 두 가지 방법 중에 고민.
    1. 엔티티는 separator와 다같이 String으로 저장하게끔 모델링하고, Service에서 이걸 파싱하여 List<String>으로 재가공해서 Controller에 넘겨주기. 그리고 tag를 누르면 그 tag에 해당하는 게시글만 따로 다 불러와야 하기 때문에, 각 tag마다 테이블을 만들어서 게시글 id를 저장.
    2. M:N 다대다 관계에선 중간에 매핑 테이블 두고 관계를 해소해준다는데 해소되는게 맞는...지...?
      • 결국 1번 선택
      • 제 1정규형 깨진다는데 현재 태그 시스템이 간단해서 그냥 String 묶음으로 처리하는게 훨씬 성능적으로 가벼워보임..

📌 진행 상황

h2_table

response

render

📅 22-04-10

📌 프론트엔드 로그인 페이지 제작

login_page

  • 비로그인 상태 아바타 이미지 바꿈
  • 이 글useNavigate 이용하여 Login 메뉴 클릭 시 /login 으로 이동

📌 구글 OAuth 클라이언트 생성

gcp_oauth

  • UI가 너무 바뀌어있어서 책 말고 인터넷 자료 찾아보는게 더 편하다.

📌 docker run 시점에서의 스프링 property 설정

  • client id랑 secret을 GH에 안올릴건데 CD 때문에 고민
    • 고민 결과 어차피 CD에서 EC2 SSH까지 담당할거니까, GH Secret에 id랑 secret 저장하고 yml에서 docker run 할때 넘겨주면 되겠다고 생각함. 그리고 구세주같은 글(Case 3 참고함)
    • 이 방법이 가능한지 알아보기 위해 Dockerfile ENV 설정 및 로컬의 docker로 테스트해봄
    • 되긴하는데 로그에 넘겨준 파라미터 뜨진 않는지 double check 해보기!!!!!!!!!!!!!!!!!

📅 22-04-11

📌 구글 OAuth 클라이언트 재설정

  • 책이랑 내가 하려는 스택이랑 일치하지 않아서 그냥 인터넷 보고 다시 정확히 해야겠다.😑
  • 승인된 자바스크립트 원본 = 내 웹사이트의 루트 경로
  • 승인된 리디렉션 URI = 유저가 구글 로그인을 했을 때, 구글에서 내 서비스에게 유저 정보를 보내주는 경로.. 라는데 프론트에서 날려보니까 accessToken이랑 기본 프로필 정보가 다 프론트로 돌아오는데? 패키지 써서 그런가? 일단 이건 사용 보류.

📌 구글 로그인 버튼 구현 및 accessToken 받아오기

google_login_btn

프론트는 최대한 쉽고 빠르게 끝내도록 패키지 적극 활용..해서 만들었다. [참고글]

  • react-google-login 패키지 사용
  • style 안먹어서 걍 render, renderProps로 mui 컴포넌트 사용함

response

📌 SPA + 스프링부트 + 소셜로그인 관련 공부

👉 [보안동향] 인증, '토큰' 방식 새롭게 뜬다! 안전한 사용법은?

👉 OAuth2 인증 방식에 대해 알아보자.

👉 OKKY | 리액트 세션이나 쿠키가 필요한가요??

📌 구글 로그인 관련 마저 구현해야 하는 것

  • accessToken 백엔드에 넘기는 부분(axios) - request A
  • 백엔드가 구글서버에서 accessToken 유효성 검사 및 유저 정보 받는 부분
  • 백엔드 유저 정보(google_id, email, name, role...) DB 저장
  • JWT token 발급 및 프론트의 request A에 대하여 response
  • 프론트 JWT token 저장
  • 프론트 로그인 state 관리(redux)

login_flow

📅 22-04-13

📌 console.log(...) is not a function 에러 해결

📌 AuthController 작성 및 request 테스트

  • login/oauth2/code/google 은 spring-boot-starter-oauth2-client 디펜던시 추가했으면 컨트롤러가 필요 없는데 왜 안먹나 했더니 내가 gradle 빌드 안하고 도커빌드만 하고 있었다...............................................😠😡 .....................................................
  • 다시 빌드 제대로 하니 CORS가 터졌다. 책에선 설정 안했는데 뭔가 이상하다.

result_cors

  • login/** 을 CORS 허용해줬더니 404가 뜬다. 컨트롤러가 없다는 뜻이다... 이 부분은 상당히 의문이다. 디폴트로 컨트롤러 만들어준다며? 하지만 돌려보니 없었다. 비록 책의 의도와 다른 방식으로 쓰려고 한거라 없는지 100% 확신은 못하지만 CORS도 그렇고 내 케이스에서 컨트롤러는 안만들어준걸로 생각하련다.

result_404

  • 그래서 때려치고 컨트롤러를 내가 작성하기로 한다. 프론트에서 구글의 response를 그대로 토스했다. 서비스는 책 따라서 작성했던걸 그대로 연결한거라 호환이 안될것이다. 결과는 당근 500이고 어차피 500이어도 컨트롤러는 제대로 작동했단 소리니 상관없다.

result_500

📌 새로운 CustomOAuth2UserService 작성 및 id_token 검증/파싱

👉 Authenticate with a backend server | Google Sign-In for Websites | Google Developers

  • 왜 access token이 아닌 id token이냐면 구글이 그렇게 하라고 해서. 문제는 얘가 방법을 반쪼가리만 적어놨다.

👉 Securing your APIs when building Web Applications

  • 나머지 반쪽은 여기서 찾았다. 하단의 레퍼런스를 참고해서 구글 api 클라이언트 디펜던시를 추가했다.

👉 API Client Library for Java | Google Developers

post_result

google_userinfo

  • 서비스까지 고쳐놓으니 200으로 응답하고 원하는 유저 정보도 잘 파싱한다.

📌 property 변수 클래스에서 사용하기

  • id token validate할 때 내 클라이언트(algorio)의 id가 필요한데, 난 id도 노출시키기 싫기 때문에 @Value 로 런타임에 주입한다.

👉 Spring @Value annotation tricks

  • 런타임 initialization 이기 때문에 final은 붙을 수 없다.

📌 프론트 로그인 버튼 렌더 직후 클릭하면 팝업 안뜨는 문제

👉 https://github.com/anthonyjgrove/react-google-login/issues/16

  • 원인은 script 받아오기도 전에 렌더링이 빠르게 다 되어서 그렇다.
  • 이미 관련 이슈가 2016년에 있었고 수정되었다.
  • 이슈의 커밋 내역과 repo의 README.md를 다시 참고한 후에 하단의 옵션을 추가하여 해결함
    • disabled={renderProps.disabled}