
Dae Young, Lee
199X.11.24
010-4343-1354
serenity90s@naver.com
isLoad.
Frontend Developer's portfolio
199X.11.24
010-4343-1354
serenity90s@naver.com
트랜스코스모스코리아 UI디자이너&퍼블리셔
(2020.04~2021.04)
신한투자증권 디지털플랫폼팀 웹퍼플리셔
(2021.05~2022.10)
하나금융 핀크 개발2팀 프론트엔드
(2022.10~2023.07)
대기업 S사 클라우드플랫폼팀 프론트엔드
(2023.08~현재)
총 경력
웹사이트 구축 및 운영
완벽한 반응형 웹개발
일본어 자유 의사소통
영어 메일 의사소통
국민대학교 임산생명공학과 졸업 2018.08
이젠컴퓨터아카데미 UI/UX 과정 2020.02
더조은컴퓨터아카데미 PHP/MySQL 프로그래밍 2021.07
이젠컴퓨터아카데미 React.js 자바스크립트 2021.11
이젠컴퓨터아카데미 React Native 앱개발 2022.04
패스트캠퍼스 Java&Spring 웹 개발 종합반 2023.08
웹디자인 기능사
GTQ Photoshop 1급
JLPT N1
TOEIC 825
TOEIC Speaking Lv6 (140)
ITQ Internet A급
HTML5 / CSS3 / JS(ES6) / React
PHP / MySQL
Photoshop / Illustrator / Xd
Premiere Pro
다정다감하고 누구와도 잘 어울립니다.
내가 맡은 임무는 극한의 책임감을 가지고
끝까지 완수하여 팀원으로서 모범이 됩니다.
![]() HTML5 | 웹표준 및 웹접근성을 준수하며, 최상의 SEO를 위한 태그를 작성합니다. 검색노출을 위한 적절한 타이틀 사용과 최적의 HTML구조, 스크린리더를 위한 정확한 태그사용 등 모든 면에 대응합니다. | ![]() CSS3 | 거의 모든 스타일을 자유자재로 다룰 수 있습니다. 주로 BEM 방법으로 클래스명을 작성합니다. |
---|---|---|---|
![]() SASS | 반복문과 변수사용 및 코드 중복 방지를 위한 모듈입니다. 체계적으로 SASS를 작성할 경우 다크모드나 기타 상황에서 쉽게 color를 변경 가능합니다. SCSS 문법을 사용합니다. | ![]() Bootstrap | 스타일시트를 처음부터 제작하기 부담스러운 토이 프로젝트에 가끔 사용합니다. |
![]() ES6 | 화살표함수, 스프레드연산자, const, let 등을 사용하며, this의 바인딩과 TDZ에 유의하며 스크립트를 작성합니다. ES5 및 ES6 스펙의 스크립트에 대응합니다. 바벨을 사용하지 않는 환경의 경우, 또한 ie까지 대응하는 경우 ES5 스펙으로 작성합니다. 이벤트 루프를 이해하며, 순서에 맞는 스크립트 작성에 유의합니다. | ![]() React.js | 함수형 컴포넌트를 사용합니다. 주로 관심사별로 묶는 스크립트를 작성하며, 개발팀의 스타일에 따라서 hook끼리 묶는 경우도 있습니다. 생명주기를 이해하여, 기능이 정확하게, 또 알맞는 타이밍에 작동하도록 스크립트를 작성합니다. 모든 동작은 state로 제어합니다. |
![]() Vue.js | composition api 및 options api를 사용합니다. 주로 후자로 개발이 진행되나, 새 서버 및 새 프로젝트에서 관심사별로 기능을 묶는 요청이 내려올 경우 전자로 진행할 때도 있습니다. react와 마찬가지로, state로 모든 동작을 제어합니다. vue2 및 vue3 모두 대응합니다. | ![]() Next.js | SSG의 장점을 살려, 최대한 Server side에서 많은 정보를 제공하도록 스크립트를 작성합니다. 예를 들어, 게시판의 경우, getServerSideProps로 간단한 작성 내용을 넣어둡니다. 구글 외 검색로봇은 게시글 하나하나의 내용을 server side에서 이해하도록 조치합니다. 클라이언트에 모든 스크립트가 다운로드 되어 hydrate 되었을 때, 달라지는 내용은 style밖에 없도록 노력합니다. |
![]() Typescript | 엄격한 타입 적용을 위해, 컴파일 단계에서 에러를 잡기 위해 사용합니다. 되도록 any를 사용하지 않도록 노력하고 있습니다. | ![]() Redux | 전역 상태 관리를 위해 사용하고 있습니다. props가 전달되는 범위가 넓어질 때, redux로 상태관리를 진행합니다. |
![]() React-query | 이제는 tanstack으로 알려진 React query를 서버상태 관리 및 관찰용으로 넣었습니다. fetch의 성공/실패/로딩중 등 관측이 가능하고, 언제 시도했는지, 몇 번째 시도인지 등 서버와의 통신상태를 점검할 수 있습니다. 근데 제 생각에는 생긴 것 자체가 가독성이 좋지는 않은 것 같아요. | ![]() JQuery | legacy 프로젝트 외에는 크게 사용할 일이 없으나, 종종 jsp로 제작된 프로젝트에 jquery가 사용되고 있어 이 때 사용합니다. 기본적인 ui메소드와 ajax를 사용합니다. |
![]() PHP | 기본적인 CRUD를 알고있습니다. 게시판 제작 경험이 있으며, 실제 자체 제작 블로그에 PHP가 사용되고 있습니다. 로그인의 경우 쿠키와 세션으로 관리합니다. | ![]() Node.js | express를 사용하여, 클라이언트에서 오는 각종 요청에 대해 처리합니다. 기본적으로는 PHP와 같이 간단한 CRUD를 처리하며, 클라이언트사이드 언어인 자바스크립트로 서버사이드까지 다룰 수 있는 점에 주목하고 있습니다. |
---|---|---|---|
![]() MySQL | SELECT, UPDATE, WHERE 외 간단한 CRUD를 할 수 있습니다. | ![]() mongoDB | 데이터가 json 그 자체이며, nodejs서버 상에서 객체를 자유롭게 다룰 수 있기 때문에 SQL과 마찬가지로 간단한 CRUD가 가능합니다. |
![]() Spring | Spring 기반 Thymeleaf에도 간단한 대응이 가능합니다. spring framework의 대략적인 구조를 알고 있습니다. 또한 서블릿 기반 JSP, JSTL에도 간단한 대응이 가능합니다. |
![]() React Native | React Native 컴포넌트를 사용하여 간단한 UI제작이 가능합니다. |
---|
![]() Git | github앱이나 git bash, 혹은 fork로 작업물을 관리합니다. 되도록 conflict가 일어나지 않도록 최신 소스를 주기적으로 pull 합니다. | ![]() Jira | 모든 작업에 대해 Jira에 내용을 작성 후 진행합니다. 전사적으로 HR이 어떻게 관리되는지 알기 쉽게 하기 위해 되도록 자세한 내용을 작성해 넣어둡니다. |
---|---|---|---|
![]() Github | ![]() Confluence | 프론트 소스의 관리를 위해, 중요 내용은 Confluence에 작성해둡니다. | |
![]() Gitlab |
![]() Photoshop | 사진사, 디자이너 업무 등으로 포토샵은 10년 이상 다루어 전문가 수준입니다. | ![]() Illustrator | 벡터그래픽 제작 시 사용합니다. |
---|---|---|---|
![]() XD | 주로 웹사이트/앱 UI제작 시 사용합니다. 금융권 프로젝트에서 UI제작 시 사용했습니다. | ![]() Zeplin | 디자이너들이 작업해놓은 UI를 보고 개발에 옮깁니다. |
![]() Figma | 기능과 UI가 XD와 흡사하여, 어렵지 않게 대부분의 기능을 다룰 수 있습니다. |
![]() 개인서버 | 본 포트폴리오 및 모든 작업물은 개인서버에 띄워져 있습니다. nginx로 리버스 프록시를 적용하여, domain.com/ 은 프론트 서버에, domain.com/api/는 api 서버에 연결되도록 포트번호를 지정합니다. |
---|
Concept | webRTC 영상통화 + websocket채팅 + OAuth 로그인 |
---|---|
Frontend | Next.js 13 |
Backend | Node.js(express, socket.io) |
Database | mongoDB |
Deployment | 자택 개인서버(NAS) |
제작참여도 | 기획(100%), 디자인(100%), FE개발(100%), BE개발(10%) |
사용방법 | 한 명은 id: admin, pw: admin으로 로그인, 한 명은 id: admin2, pw: admin2으로 로그인, 서로 영상통화와 채팅을 즐겨봅시다. 원한다면 google이나 github 소셜 로그인도 가능합니다. |
채팅: 채팅 기능의 핵심은 웹소켓입니다. 소켓에 메세지를 emit하면, 웹소켓 서버에서 해당 방에 참여한 모든 이들에게 broadcast해주고, broadcast 받은 클라이언트들은 모두 새로운 메세지를 받게되는 형태입니다.
화상통화: 2개의 PEER, 시그널 서버, TURN서버가 주 핵심요소입니다. 최초 동작시, 웹소켓으로 만들어진 시그널 서버에 sdp정보를 보내어 PEER끼리 서로 정보를 주고받습니다. 이후 서로 주고받은 sdp를 토대로 STUN서버 (stun:stun.google.com:19302)에 중계요청을 보내며, 중계기로부터 받은 영상데이터 혹은 PEER에게 직접 받은 영상데이터를 video태그에 재생시킵니다.
채팅: 서버에서는 웹소켓 connection이 일어나면 클라이언트에서 받은 uuid를 토대로 방을 생성합니다. 이제 그 uuid로 전달된 메세지를 해당 방에 참가한 사람들에게 broadcast해줍니다.
화상통화: 시그널 서버는 클라이언트로부터 받은 sdp 정보를 상대방 peer에게 전송하여, 서로 연결을 잘 수립할 수 있도록 합니다. 이후 STUN서버나 TURN서버를 통해 Interaction Connection Establishment 후보를 찾고, 서로의 PEER간 연결이 수립되면, 그 이후부터는 서버의 역할이 매우 작아집니다.
단, TURN서버의 경우 직접 구축하는 것이 어려웠기 때문에 google 공개도메인을 사용했습니다.
OAuth: NextAuth 라이브러리를 통해 구현합니다. 사용자(Resource Owner)는 현 프로젝트의 인증서버에 인증 요청을 보냅니다. 그후 인증서버는 구글의 Resource 서버에 요청을 보내게 되고, Resource Owner의 인증을 위임받은 구글서버에 의해 인증서버에는 사용자의 정보(이름, 메일)이 도착하여 토큰화되어 사용할 수 있게 됩니다.
Concept | PWA(Progressive Web App), react query, mysql, nextjs13, 쿠키, 카카오 공유 |
---|---|
Frontend | Next.js 13, React-query(서버상태관리) |
Backend | Next.js 13 |
Database | MariaDB (MySQL) |
Deployment | 자택 개인서버(NAS) |
제작참여도 | 기획(100%), 디자인(100%), FE개발(100%), BE개발(100%)이 프로젝트는 개발중입니다. |
사용방법 | 사용자의 정보는 접속해서 쿠키를 허용하는 순간부터 고유한 아이디로 저장됩니다. 이제 투표를 만들고, 카카오톡으로 공유해보세요! 정말 쉽습니다. 그냥 투표를 제작하고, 링크를 뿌려보세요. 자유로운 익명투표의 시작입니다. |
우선 getServerSideProps로 해당 투표의 uuid를 토대로, 투표 데이터가 있는지 확인 후 없으면 custom 404페이지로 안내합니다.
존재한다면 실제 투표페이지로 안내합니다.
투표 데이터는 React Query를 통해서 fetch 하며, 데이터가 정상적으로 송/수신 되고 있는지 React-query devtool을 통해 관찰할 수 있습니다.
이 프로젝트는 Build하지 않고, React Query devtool을 보여주기 위해 pm2 - npm run dev로 .env.development 환경으로 돌리고 있는 상태입니다. (꽃 이미지)투표를 간편하게 생성 가능하며, 언제든지, 자유롭게 생성 가능합니다.
투표를 하면, 바로 DB에 내용이 업데이트 되고, react query에 설정된 의존성 배열에 의해 한 번 더 fetch를 거치게 됩니다. 이후 투표 결과 화면이 송출됩니다.
나의 데이터는 쿠키에 저장되어있으며, (랜덤으로 배정된 고유한 uuid를 마치 id처럼 사용) 내가 이미 투표를 했으면 투표 결과를 보여줍니다.복사하기 및 카카오 공유가 가능합니다.
모바일, PC 어느쪽이든 쉽게 투표 URL을 공유할 수 있습니다.
DB: 비록 백엔드 전문가는 아니나, 나름대로 DB를 설계도 해보고 효율적인 환경을 구축하기 위해 연구했습니다.
vote table: 투표의 개요를 저장합니다.
votemenu table: 해당 투표의 보기를 저장합니다. parentid로 vote table의 uuid를 추적합니다.
voter table: 투표자들이 어떤 보기에 투표했는지 저장합니다. 몇 번 보기의 index를 선택했는지만 저장하면 됩니다.
백엔드에서는 이를 정제하지 않고 전부 클라이언트로 보내서, 클라이언트에서 알아서 데이터를 정제해서 map으로 뿌려줍니다.
Nextjs API: 크게 네 가지 기능으로 나뉩니다.
1. 단순히 투표 DB내용을 클라이언트로 쏴주는 기능
2. 쿠키 데이터가 있는 사용자에 한해서, 내가 만든 투표방을 보여주고, 내가 선택한 투표 보기에 [선택함]을 노출시켜 줍니다.
다만 쿠키가 제거되면 이는 모두 사용할 수 없게 됩니다.
3. 투표 삭제하기 기능: bcrypt를 사용해서 암호화 및 verify를 실행합니다. 올바른 비밀번호를 입력하면, 투표가 실제로 삭제됩니다.
특정 Flag를 만들어서 투표를 비노출 시키는 방법도 있으나, 투표 row가 무한이 늘어나는 것을 방지하기 위해 실제 row를 삭제하는 방법을 채택했습니다. 집에 비싼 서버가 있고 전기세 감당이 된다면 전자의 방법을 택할 수도 있습니다.
4. 투표 관측기간이 지나면, scheduler가 24시간에 한 번씩 실제 row를 삭제하여 더이상 투표를 볼 수 없게 합니다.
node-cron vs node-schedule 둘 중에 고민을 많이 했으나, 사용자가 많은 후자를 택했습니다.
풀무원 지점을 운영하고 있는 어머니를 위한 쇼핑몰입니다. 상품 목록페이지, 상품 상세페이지에만 데이터가 들어가기 때문에, 해당페이지에만 데이터를 바인딩 시켜서 보여줍니다. ajax로 데이터를 받아온 뒤, promise와 await로 동기적으로 state를 변경하여 각 상품의 상세 정보를 렌더합니다.
모든 서비스를 javascirpt로 구현하기 위해 MERN스택으로 쇼핑몰을 구현했습니다. 실 결제를 위한 모듈은 아임포트를 사용하여, 아임포트 이외에는 전부 스스로의 힘으로 구현했습니다.
프론트에서 상품 고유넘버를 전송하면, mongoDB에서 해당 상품이 실제로 존재하는지 조회한 후, 결제 솔루션 서버로 결제정보를 전송합니다.
결제정보가 전송되어 response값을 성공적으로 전송받으면, 구매내역 DB에 구매정보를 저장한 뒤 네이버 클라우드 서버에 SMS를 전송합니다. 이 때 클라우드 서버로 전송되는 header정보는 sha256으로 암호화 되어 전송됩니다.
최종적으로, PHP서버로 json을 전송하면 PHP mailer가 구매자에게 메일을 전송해줍니다.
몇 년전 일본에 가족여행을 갔을 때, 너무나도 훌륭한 서비스와 분위기에 비해 숙소 웹사이트는 90년대에 머물러있어서 안타까움을 느꼈습니다. 이에 해당 료칸 웹사이트만 봐도 고객들이 오고싶게 만들도록 토이프로젝트로 리뉴얼했습니다.
이 사이트는 실제 예약이 가능합니다. 라이브러리 없이 달력을 구현하고, 그 달력에 예약기능을 추가하여 날짜를 선택하면 날짜에 pointing이 됩니다.
실제로 제가 호텔을 운영하고 있는 것이 아니기 때문에 결제 부분만 아무 동작없이 바로 결제완료로 처리합니다.
객실의 등급에 따라, 숙박 인원 수에 따라 자동으로 요금을 계산합니다.
게시판, 회원가입, 로그인, 예약 등이 전부 구현되어 있습니다.
또한 고객 문의를 구현해두어 form 내용이 자동으로 관리자와 고객에게 전송됩니다.
이 내용은 전부 PHP와 mariaDB로 구현되어 있습니다.
Concept | 손쉬운 날짜계산 |
---|---|
Frontend | ES Module |
Deployment | NPM, 자택 개인서버(NAS) |
제작참여도 | 100% |
사용하는 법 | 로 설치합니다. react에서는 해당 예시와 같이 사용할 수 있습니다. |
게시판, 댓글 등에서 일일이 날짜를 계산하기 어려웠을 경우, string을 gloomyDate.date(str)에 담는 것만으로 이 게시글이 며칠전에 작성됐는지 return합니다.
개발자는 간편하게 날짜를 bind할 수 있습니다!
function App() {
const [data,setData] = useState([{ title: 'title1', date: '2022-05-10 10:55:40'}, { title: 'title2', date: '2023-02-11 15:50:30'}])
useEffect(()=>{
const newData = [...data]
newData.forEach(e=> e.date = gloomyDate.date(e.date))
setData(newData)
},[])
return (
<div className="list">
{
data.map((elm,idx)=>
<div key={idx}>
<p className="title">{e.title}</p>
<p className="date">{e.date}</p> {/* expected: 1년 전 */}
</div>
)
}
</div>
);
}
예상 출력결과
title1
1년 전
title2
2달 전
npm publish로 배포합니다.
npm i gloomydate
로 설치할 수 있습니다.
모듈 번들러(웹팩 등)에 대응하기 위해 es module형태로 제작했으며, tree shaking에 대응하기위해 추후 모듈의 기능이 늘어날 경우 named export로 제작할 예정입니다.
(현재는 모듈 기능이 하나뿐이라 tree shaking이 크게 의미를 가지지 않음)
일반 HTML 프로젝트의 경우
<script src="https://cdn.gloomy-store.com/gloomyDate/gloomyDate.js"></script>
로 commonJS에 대응합니다.
react의 예시와 동일하게 사용가능합니다.
3개 국어에 대응하며, 3가지 타입의 데이터 input을 지원합니다.
gloomyDate.date('2023-04-26 08:10:22','en') // formatted string
gloomyDate.date('20230426081022','jp') // string
gloomyDate.date(20230426081022,'ko') // number
'ko', 'jp', 'en'
3개 국어에 대응합니다.
예상되는 결과:
2days ago
2日前
2일 전
아무 인자도 넣지 않을 경우 'ko'가 default로 작동합니다.
14 length의 string, number에 대응하며,
'YY-mm-dd H:i:s' 형태의 string에도 대응합니다.
Concept | Next.js Hydrating |
---|---|
Frontend | Next.js 13, Typescript |
Backend | Next.js 13, Typescript, MySQL |
Deployment | 자택 개인서버(NAS) |
제작참여도 | 기획(100%), 디자인(100%), FE개발(100%) |
기존의 CSR형식의 SPA는 검색노출이 어렵고, 설령 구글 검색로봇이라 하더라도 웹서버상에서 index.html을 받았을 때 빈문서 이외에는 내용이 나오지 않습니다.
이러한 점을 해결하기 위해 Next.js의 SSG에 주목했으며, JSX가 서버사이드상에서 이미 html태그로 렌더된 상태로 전송이 되기 때문에 현 포트폴리오의 검색 노출이 더 잘될 것으로 기대하고 있습니다.
최하단 Today, Total은 pages/api 폴더 내에 제작해둔 api를 page에서 GetServerSideProps로 받아오며, DB에 오늘날짜의 테이블이 없다면 오늘 방문자 0명으로 INSERT합니다.
이미지, 폰트 등 모든 컨텐츠가 로딩되고, ajax에서 return을 받아오면 비로소 로딩화면이 사라집니다.
(개발자 도구에서 네트워크를 3G로 하고 강력 새로고침을 하면 로딩화면을 볼 수 있습니다.)
서버에서 사용하는 것은 오늘 날짜의 today hit, total hit을 구하는 것입니다.
시간 기준은 클라이언트 기준이 되어야하기 때문에, 클라이언트에서 한국시간을 구해줍니다.
(new Date()에서 9 * 60 * 60 * 1000를 더함)
그리고 이 시간을 getServerSideProps를 사용하여 api로 보내줍니다.
시간은 보안정보가 아니기 때문에 get요청으로 parameter에 붙여서 보내줍니다.
const [rows] = await pool.query<RowDataPacket[]>("SELECT * FROM visitor_today WHERE REGDATE='${'todayDate'}'");
const [rows2] = await pool.query<RowDataPacket[]>("SELECT * FROM visitor_total WHERE REGDATE='${'todayDate'}'");
// const count = rows;
res.status(200).json([...rows,...rows2]);
api에서는 DB에 오늘날짜의 테이블이 없다면 오늘 방문자 0명으로 INSERT합니다. 만약 존재한다면 오늘 방문자의 배열을 가져와서 클라이언트로 return합니다.