먼저 나는 컴퓨터공학 복수전공자인데 2020년 전기 대학 졸업을 앞두고 처음 싸피 과정을 알게 되어 지원했다가 ct문제를 준비도 없이 본 결과로 탈락했다.
그 후로 운이 좋게도 일본 회사에 취직하여 일본으로 넘어갔다가 2년 반 경력을 끝으로 다시 귀국하게 되었는데 한국에 와서 다시 취업을 하려니 경력직인데도 불구하고 서류 합격률은 대졸 신입 시절보다 낮았고 면접을 보면 볼수록 부족하다는 생각만 하게 되었으며 한 번은 연봉이 맞지 않아 못가게 된 곳이 있는가 하면...
아무튼 참담했다.
와중에 인스타에 싸피 10기 광고가 떴고 2022년 9월 퇴직자인 나는 슬슬 면접에서 "공백기가 있네요?"라는 질문을 받기 시작했으므로 정리하자면 다음과 같은 이유로 싸피에 지원하게 되었다.
- '공백기에 공부했다'를 증명할 수 있음 - 자소서, 프로젝트 전반 취업 컨설팅 - 개발자 커뮤니티 구축
지원
지원은 정말 쉬웠다. 요구하는 것도 학력, 자격증, 경력사항 정도였다.
하나 추가되는 게 있다면 전국에 5가지 캠퍼스가 있는 만큼 1, 2, 3순위 지망 캠퍼스와 각 캠퍼스에서의 특화 트랙을 1, 2순위로 결정해야한다는 점이다.
나는 일단 경력직이고 짧아도 혼자서 웹 서비스를 운영해본 경험이 있기 때문에 아무리 공백기 증명하려고 참여하는 거래도 웹 기초 과정을 또 수강하는 건 무의미하다고 판단해서 (생각없이) 서울의 임베디드를 1순위로 선택하고 나머진 무관으로 처리했다.
다만 SW 적성 진단을 보기 전에
- 서울 집값 어떡하지! 월세 감당 못하는데 - 코틀린 경험이 있는데 모바일을 하는 편이 낫지 않나?
란 생각에 구미 캠퍼스로 변경할 수 있겠냐 질문했으나 얄짤없이 "불가"하단 답을 받았다..
혹시 11기에 지원하는 사람이 있다면 지원서 작성할 때 캠퍼스를 잘 고르시기를..
에세이 작성
500자라 딱히 어려울 건 없었던 것 같다.
교육 과정인지라 취업할 때처럼 '이것도 저것도 해본 적이 있다' 보다는 '이런 걸 해보고 있는데 이런 부분에서 어려움을 느끼고 있다'의 느낌으로 작성했다.
PR 체크할 때 이런 에러 로그가 나오면서 danger가 될 땐 workflow yml을 살펴보자
로그 전체
/opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/scm_source/git_repo.rb:139:in `find_merge_base': Cannot find a merge base between danger_base and danger_head. If you are using shallow clone/fetch, try increasing the --depth (RuntimeError)
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/scm_source/git_repo.rb:22:in `diff_for_folder'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/danger_core/dangerfile.rb:274:in `setup_for_running'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/danger_core/dangerfile.rb:284:in `run'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/danger_core/executor.rb:29:in `run'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/lib/danger/commands/runner.rb:73:in `run'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/claide-1.1.0/lib/claide/command.rb:334:in `run'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/lib/ruby/gems/2.6.0/gems/danger-8.6.1/bin/danger:5:in `<top (required)>'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/bin/danger:23:in `load'
from /opt/hostedtoolcache/Ruby/2.6.10/x64/bin/danger:23:in `<main>'
Error: The process '/opt/hostedtoolcache/Ruby/2.6.10/x64/bin/danger' failed with exit code 1
원인
로그 두 번째 줄에 있는
If you are using shallow clone/fetch, try increasing the --depth (RuntimeError)
로 알 수 있는데, danger가 체크용으로 해당 브런치를 clone하려는데 shallow clone을 사용하고 있어 가장 마지막의 1 커밋만 가져와 이력을 전부 확인할 수 없는 것이 원인인 듯싶다.
shallow clone?
일반적인 git clone 은 tag, commit 을 모두 checkout 하지만 시간이 더 걸리기 때문에, 모든 커밋을 가져올 필요가 없는 경우 shallow clone 을 사용해 checkout 시의 볼륨의 줄일 수 있다고 한다.
해결
https://github.com/actions/checkout 에서 checkout workflow의 사양을 확인해보면 depth의 기본값은 1이다. 찾아보니 수를 늘리는 것보단 오히려 아예 커밋을 가져오지 않도록 하는 게 방법인 것 같아서 일단은 그렇게 해결.
td태그에 클래스도 아이디도 지정되어있지 않아서, 메이플이 랭킹 페이지의 html구조를 바꿔버리면 쓸 수가 없는 코드이지만...
이 코드를 짜는 도중에 하나 더 깨달은 게 있는데, 바로 어제 만든 API(/v1/maplestory/representative-character/:accountId)를 사용하기 위해선 개인 정보인 accountID가 반드시 필요한데, 그럼에도 대표캐릭터 관련 정보만 가져올 수 있는 데 반해 이번에 만드는 건 캐릭터 이름만으로도 충분하다는 거였다. 내가 내 블로그에 글 올릴 때도 accoundID를 가렸는데, 누가 개인이 만든 신뢰 못할 사이트에 개인 정보를 넣을까... 싶어졌기 때문에, TUser table에 정의한 accountId 필드는 삭제..ㅎ 만든 API는 soap api공부한 셈 치고 냅두기로 했다.
API정리
api는 이미 서비스중인 '메이플지지'라는 사이트에서 확인할 수 있는 '메애기 한눈에 보기'와 유사하다. 순서도 똑같을 정도로 말이다.
메이플스토리 공식 홈페이지에 로그인
대표 캐릭터 변경 페이지에 접속
월드/캐릭터 선택 영역의 우측 서버 선택
대표 캐릭터 변경 페이지를 전체선택/복사
복사한 내용 그대로 request body에 실어 보내기
여기서 메이플지지에서는 뭔가 프론트 로직...? 을 통해서 캐릭터 이름을 골라내는 듯한데, 살짝 오류가 있어보인다. 메이플이 html구조를 바꿨는데 메이플지지 쪽에서 아직 대응을 안 한 걸수도 있지만. 어쨌든, 홈페이지에서 복사한 내용을 그대로 텍스트 에디터에 붙여넣기하면 이렇게 되는데
NEXON
메뉴
메이플스토리
OFF
15
로그아웃
helloproud25
주의
본문 바로가기
주 메뉴 바로가기
뉴스가이드랭킹커뮤니티미디어고객지원
마이메이플백에리부트해적마이메이플내가 쓴 글
내정보 관리아이템 이용내역테스트월드회원탈퇴
내정보 관리대표캐릭터 변경메이플스토리 홈페이지 이용 시 사용할 대표캐릭터를 설정해주세요.01메이플ID 선택
helloproud
02월드/캐릭터 선택리부트
리부트
진동온돌리부트진동온돌룬지속더하기리부트룬지속더하기평균의법칙리부트평균의법칙온돌인데리부트온돌인데백에리부트백에온돌링리부트온돌링
대표캐릭터는 10레벨 이상이어야 지정할 수 있습니다.대표 캐릭터 저장
마이메이플 메인
내정보 관리
대표캐릭터 변경
비활성ID 전환/해제
제한 내역
캐릭터정보 공개설정
Family Site회사소개채용안내이용약관게임이용등급안내개인정보처리방침청소년보호정책운영정책넥슨PC방사이트맵(주)넥슨코리아 대표이사 이정헌 경기도 성남시 분당구판교로 256번길 7 전화: 1588-7701 팩스:0502-258-7322
E-mail:contact-us@nexon.co.kr 사업자등록번호 : 220-87-17483호 통신판매업 신고번호 : 제2013-경기성남-1659호 사업자정보확인ⓒ NEXON Korea Corporation All Rights Reserved.TOP
여기서 나의 캐릭터 이름만 쏙쏙 골라내기 위해선, 우선 중간쯤에 있는 마이메이플, [대표캐릭터 이름 + 월드명]직업명, 마이메이플, 내가쓴 글로 이루어진 곳에서 알 수 있는 [대표캐릭터명 + 월드명]을 통해 진동온돌리부트진동온돌룬지속더하기~~~ 로 된... 줄을 뽑아잡아야한다. (아무리 일기라도 그렇지 이렇게 막 써서야 나중에 기억도 못할 거 같아서 약간 두려워졌다)
마이페이지 [대표캐릭터 + 월드명] 마이페이지 로 이루어진 줄에서 [대표캐릭터 + 월드명]을 알아낸다
그 다음 줄부터 [대표캐릭터 + 월드명 + 대표캐릭터] 를 포함한 줄을 찾는다
그 줄은 [캐릭터명 + 월드명 + 캐릭터명] 이 한세트로 이루어져 있다
[캐릭터명]을 추출한다
API 정리
method
get
endpoint
/v1/maplestory/character/list
request
NEXON 메뉴 메이플스토리 OFF 15 로그아웃 helloproud25 주의
본문 바로가기 주 메뉴 바로가기
뉴스가이드랭킹커뮤니티미디어고객지원 마이메이플백에리부트해적마이메이플내가 쓴 글 내정보 관리아이템 이용내역테스트월드회원탈퇴 내정보 관리대표캐릭터 변경메이플스토리 홈페이지 이용 시 사용할 대표캐릭터를 설정해주세요.01메이플ID 선택 helloproud 02월드/캐릭터 선택리부트 리부트 진동온돌리부트진동온돌룬지속더하기리부트룬지속더하기평균의법칙리부트평균의법칙온돌인데리부트온돌인데백에리부트백에온돌링리부트온돌링 대표캐릭터는 10레벨 이상이어야 지정할 수 있습니다.대표 캐릭터 저장 마이메이플 메인 내정보 관리 대표캐릭터 변경 비활성ID 전환/해제 제한 내역 캐릭터정보 공개설정 Family Site회사소개채용안내이용약관게임이용등급안내개인정보처리방침청소년보호정책운영정책넥슨PC방사이트맵(주)넥슨코리아 대표이사 이정헌 경기도 성남시 분당구판교로 256번길 7 전화: 1588-7701 팩스:0502-258-7322 E-mail:contact-us@nexon.co.kr 사업자등록번호 : 220-87-17483호 통신판매업 신고번호 : 제2013-경기성남-1659호 사업자정보확인ⓒ NEXON Korea Corporation All Rights Reserved.TOP
대표캐릭터 정보를 xml로 전달하는 메이플스토리 API에 직접 요청하지 않고 중간에 끼워넣어 json으로 받고싶을 때 쓸 수 있다.
`t_character` 테이블 정의를 할 때 굳이 Job, JobDetail을 넣어야하나... 싶어서 직업은 JobInfo enum으로 뺐기 때문에, 여러 라이브러리의 힘을 빌려서 이런 느낌으로 만들어봤다.
참고로 dol-maplestory은 `t_character`의 값을 json 객체로 직렬화해서 반환한다.
`t_character`의 구조는 이렇게 되어있는데, avatar_img_url, character_name, exp, lev, pop, tot_rank, world_name, world_rank는 xml response의 key값을 파스칼에서 스네이크 표기법으로 바꾼 것이다.
그래도 뭘 만들려면 회원가입은 있어야하지 않겠나... 싶어서 만든 `t_user` 테이블의 primary key
nexon maplestory accountID랑은 다른 값이다
메이플스토리는 거의 필수로 유저 한 명이 캐릭터 45개쯤은 키워야하는 게임이기에 캐릭터마다의 할일을 유저에게 보여줄 수 있으면 좋겠다 싶어서 추가했다
알게된 것
1.
@JsonProperty("name")
을 사용해서 직렬화/역직렬화시의 키값을 지정할 수 있다
2.
@JsonCreator
public TCharacter(
@JsonProperty("AvatarImgURL") String avatarImgUrl,
@JsonProperty("WorldName") String worldName,
@JsonProperty("CharacterName") String characterName,
@JsonProperty("Lev") Integer lev,
@JsonProperty("Exp") Long exp,
@JsonProperty("JobDetail") String jobDetail,
@JsonProperty("Pop") Integer pop,
@JsonProperty("TotRank") Integer totRank,
@JsonProperty("WorldRank") Integer worldRank
){
this.avatarImgUrl = avatarImgUrl;
this.worldName = worldName;
this.characterName = characterName;
this.lev = lev;
this.exp = exp;
this.jobId = JobInfo.getJobInfoByJobDetail(jobDetail).getId();
this.pop = pop;
this.totRank = totRank;
this.worldRank = worldRank;
}
생성자 위에 붙여 역직렬화할 때 사용할 수 있다.
3.
@JsonRootName("t_character")
는 Json객체의 주체를 알 수 있도록 할 때 사용한다. entity class위에 정의.
사용하기 위해선 application.propertes에 이하의 설정을 추가할 필요가 있다.
spring.jackson.serialization.wrap-root-value=true
4.
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ObjectMapper를 인스턴스화할 때 `configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)`
를 추가함으로 Mapper class에 없는 properties가 있을 경우, 에러를 발생시키지 않고 스킵할 수 있다.