Algorio 개발 노트
📅 22-03-24
📌 Cannot resolve symbol 에러 해결
- 잘 돌아가다가 어느날 갑자기 모든 import에 빨간줄 그어져서 당황
- Refresh Gradle Dependencies로 해결
📅 22-03-25
📌 젠킨스 설치 삽질 끝
- 링크: http://qriositylog.com:8081
- 기록: https://til.qriositylog.com/featured/devops/jenkins/setting-jenkins-server-with-oracle-cloud
📌 알게된 것
- 포트가 바뀌지 않았던 이유가 포트 충돌이 아니라 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 사용 사례 더 찾아보고 무난한 조합법으로 결정하기
📅 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
다시 설정해줌
- 서버 마비돼서 reboot 하고
- 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
- 테스트 CLI 로그 이쁘게 보여주는 플러그인:
- 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 구축
- 도커 이미지를 위한 GH 레지스트리도 있더라.. Dockerfile
LABEL
에 내 repo 넣고 GH 레지스트리에 push하면 package에 뜬대서 release 때마다 트리거되는 CI 구축
- 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
설치
- create-react-app을 현재 경로에 하고 싶으면
create-react-app .
하면 됨 - 프로젝트 생성시 기본으로 주어지는 App.css에 center로 강제하는 CSS가 있으므로 제거하든가 해야 함
- 아주 좋은 뉴비 튜토리얼
- 근데 최신버전 아니라서 조금씩 수정해서 써야 함
- justify → justifyContent
- spacing은 예전 버전에서 40 적용 안됨. 스펙에 없음.
- 부모 하위에
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 연동 시 기본url 값으로 testdb 가 아니라 랜덤으로 생성되어 testdb를 찾을 수 없는 것이었음.
- application.properties 에
spring.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 서로 거꾸로 쓴거 아님)
- error 페이지에서 status가 500이면 파라미터를 잘못 넣은 것 (아예 불가능한 url)
- status가 406이면 파라미터가 이상한건 아니고 내부에서 처리하다가 어떤 오류로 인해 response를 줄 수 없는 것 (어노테이션같은거 잘 보기)
📌 추가로 공부한 것
- Service와 Controller가 하는게 비슷한 것 같은데 왜 따로 두는지 의문이 들어 이론글을 읽어봄.
📅 22-04-09
📌 백엔드 API 추가
- findAll():
posts/all
- 좋은 스프링 공짜 레퍼런스 발견함
📌 백엔드 CORS 설정
- localhost:3000 에 대하여 허용
📌 프론트엔드 axios로 REST API 호출
- 빠르게 요청 날려두기 위해 useEffect 안에서 호출
- rendering block 하지 않기 위해 async로 호출: 나머지 컴포넌트는 렌더링 ㄱㄱ
- response 오면 setState로 re-render 시킴: posts 영역만 다시 렌더링
📌 백엔드 게시글 metadata 연동
- 백엔드 H2에서 SQL로 테스트 데이터 넣은 후 프론트에서 posts/all로 API 호출하여 받은 데이터를 뿌려주는지 확인
- tag를
List<String>
으로 하려 했는데 DB에서 실제 Array형으로 들어갈 수 없기 때문에 두 가지 방법 중에 고민.- 엔티티는 separator와 다같이 String으로 저장하게끔 모델링하고, Service에서 이걸 파싱하여
List<String>
으로 재가공해서 Controller에 넘겨주기. 그리고 tag를 누르면 그 tag에 해당하는 게시글만 따로 다 불러와야 하기 때문에, 각 tag마다 테이블을 만들어서 게시글 id를 저장. - M:N 다대다 관계에선 중간에 매핑 테이블 두고 관계를 해소해준다는데 해소되는게 맞는...지...?
- 결국 1번 선택
- 제 1정규형 깨진다는데 현재 태그 시스템이 간단해서 그냥 String 묶음으로 처리하는게 훨씬 성능적으로 가벼워보임..
- 엔티티는 separator와 다같이 String으로 저장하게끔 모델링하고, Service에서 이걸 파싱하여
📌 진행 상황
📅 22-04-10
📌 프론트엔드 로그인 페이지 제작
- 비로그인 상태 아바타 이미지 바꿈
- 이 글의
useNavigate
이용하여 Login 메뉴 클릭 시/login
으로 이동- 같은 패키지로 path 별 컴포넌트 렌더링을 App.js에서 제어 - v6에서 스펙 변경 있으니 주의
- 메뉴 구분은
event.target.textContent
로 함
📌 구글 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 받아오기
프론트는 최대한 쉽고 빠르게 끝내도록 패키지 적극 활용..해서 만들었다. [참고글]
react-google-login
패키지 사용- style 안먹어서 걍 render, renderProps로 mui 컴포넌트 사용함
📌 SPA + 스프링부트 + 소셜로그인 관련 공부
👉 [보안동향] 인증, '토큰' 방식 새롭게 뜬다! 안전한 사용법은?
📌 구글 로그인 관련 마저 구현해야 하는 것
- accessToken 백엔드에 넘기는 부분(axios) - request A
- 백엔드가 구글서버에서 accessToken 유효성 검사 및 유저 정보 받는 부분
- 백엔드 유저 정보(google_id, email, name, role...) DB 저장
- JWT token 발급 및 프론트의 request A에 대하여 response
- 프론트 JWT token 저장
- 프론트 로그인 state 관리(redux)
📅 22-04-13
📌 console.log(...) is not a function 에러 해결
- https://stackoverflow.com/questions/31013221/typeerror-console-log-is-not-a-function
- 비동기 함수 IIFE가 console.log 밑에 있어서 파싱 에러가 났다. 자스 파서 개구려
📌 AuthController 작성 및 request 테스트
- login/oauth2/code/google 은
spring-boot-starter-oauth2-client
디펜던시 추가했으면 컨트롤러가 필요 없는데 왜 안먹나 했더니 내가 gradle 빌드 안하고 도커빌드만 하고 있었다...............................................😠😡 ..................................................... - 다시 빌드 제대로 하니 CORS가 터졌다. 책에선 설정 안했는데 뭔가 이상하다.
login/**
을 CORS 허용해줬더니 404가 뜬다. 컨트롤러가 없다는 뜻이다... 이 부분은 상당히 의문이다. 디폴트로 컨트롤러 만들어준다며? 하지만 돌려보니 없었다. 비록 책의 의도와 다른 방식으로 쓰려고 한거라 없는지 100% 확신은 못하지만 CORS도 그렇고 내 케이스에서 컨트롤러는 안만들어준걸로 생각하련다.
- 그래서 때려치고 컨트롤러를 내가 작성하기로 한다. 프론트에서 구글의 response를 그대로 토스했다. 서비스는 책 따라서 작성했던걸 그대로 연결한거라 호환이 안될것이다. 결과는 당근 500이고 어차피 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
JacksonFactory
가 deprecated 라서 GsonFactory 쓰라는데 호환이 될지 모르겠다.-
나중에 알아보자. - 👉 호환된다. 하단 링크 코드 참고.
-
- 서비스까지 고쳐놓으니 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}