Donga1125 / Celebee

Celebee๐Ÿ ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์—…ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Home Page:https://celebee.kro.kr

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Celebee: ํ•˜๋ฃจ๋‹จ์œ„ ๋ชจ์ž„์„ ํ•จ๊ป˜ํ•  ์‚ฌ๋žŒ์„ ๋ชจ์ง‘ํ•˜๋Š” ์„œ๋น„์Šค

๋‹ด๋‹นํ•˜์—ฌ ์ž‘์—…ํ•œ ๋ถ€๋ถ„๋งŒ ๊ฐ„์ถ”๋ฆฐ ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ์ „์ฒด ์ฝ”๋“œ๊ฐ€ ๊ถ๊ธˆํ•˜์‹œ๋ฉด ์•„๋ž˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!
ํ”„๋กœ์ ํŠธ ๋ฉ”์ธ ์ €์žฅ์†Œ ์ฃผ์†Œ: https://github.com/codestates-seb/seb45_main_004

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

์ด๋ฒˆ์ฃผ ํ† ์š”์ผ ํŒ๊ต ๋ง›์ง‘ ํˆฌ์–ด๋ฅผ ํ•จ๊ป˜ํ•  ์‚ฌ๋žŒ ๋ชจ์—ฌ๋ผ! ํ•˜๋ฃจ ๋‹จ์œ„์˜ ๊ฐ„๋‹จํ•œ ๋ชจ์ž„์„ ํ•จ๊ป˜ํ•  ์ธ์—ฐ์„ ๋ชจ์ง‘ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
ํ˜„์žฌ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ๊ธฐ๊ฐ„ ๋™์•ˆ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ๊ฑฐ๋‚˜, ์ž‘์—… ์‹œ๊ฐ„์ด ๋ถ€์กฑํ–ˆ๋˜ ํ•ญ๋ชฉ๋“ค์„ ํ”„๋กœ์ ํŠธ ๋ฐ๋ชจ๋ฐ์ด ์ดํ›„์— ์ž‘์—…ํ–ˆ์œผ๋ฉฐ ํ˜„์žฌ๋„ ๊พธ์ค€ํ•˜๊ฒŒ ๋ฆฌํŒฉํ† ๋ง ์ž‘์—… ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ ํ”„๋กœ์ ํŠธ์—์„œ ํŒ€์›๋“ค์ด ๊ตฌํ˜„ํ•œ ๋‚ด์šฉ์„ ํ•™์Šตํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๊ตฌํ˜„ ์ž‘์—… ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2023๋…„ 8์›” 24์ผ(๊ธˆ) ~ 2023๋…„ 9์›” 22์ผ(์ˆ˜)

  • ๊ฐœ๋ฐœ ์ธ์› : ๋ฐฑ์—”๋“œ 3๋ช…, ํ”„๋ก ํŠธ์—”๋“œ 3๋ช… (์ด 6๋ช…)

  • ๋ฐฐํฌ ์ฃผ์†Œ: https://celebee.kro.kr

       ํ…Œ์ŠคํŠธ ์•„์ด๋””: test@gmail.com ๋น„๋ฐ€๋ฒˆํ˜ธ: 1234567Days
      โ›”๏ธโ›”๏ธ ํ˜„์žฌ AWS ELB๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด์„œ ์ƒ๊ธด CORS ์˜ค๋ฅ˜ ํ•ด๊ฒฐ ์ค‘์œผ๋กœ ์‚ฌ์ดํŠธ ์ ‘์†์ด ์›ํ™œํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (2024.04.30 ~) โ›”๏ธโ›”๏ธ
    
  • ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ: ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

  • ERD:
    2023-09-18_14 45 50

  • API ๋ช…์„ธ์„œ: Postman API Documentation

  • ํ”„๋กœ์ ํŠธ ๋…ธ์…˜ ๋งํฌ : Team Notion Link


ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•œ ๊ธฐ์ˆ 

Back-end

Spring Spring Boot Spring Security Mysql Apache Tomcat Ngrok Amazon AWS Amazon S3 Amazon rds Amazon route53 amazonec2 docker githubactions

Front-end

HTML styledcomponents JavaScript NodeJS React React Router Redux axios

ํ˜‘์—…ํˆด

Github Discord Notion Zoom

์„œ๋น„์Šค ์„ค๋ช…

๋ฉ”์ธํŽ˜์ด์ง€ ๊ธ€ ์ƒ์„ธ ํŽ˜์ด์ง€
๊ธ€ ์ž‘์„ฑ ํŽ˜์ด์ง€ ๋งˆ์ดํŽ˜์ด์ง€

๊ฐ ํŽ˜์ด์ง€ ๋ณ„ ๊ธฐํš ๋‚ด์šฉ
๋ฉ”์ธํŽ˜์ด์ง€
  • ์ตœ์‹ ์ˆœ > ๋งˆ๊ฐ ์ „ > ๋งˆ๊ฐ ์™„๋ฃŒ ์ˆœ์„œ๋กœ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ฉ”์ธํ™”๋ฉด ๋ชจ์ž„ ์ •๋ ฌ
  • 'ALL' ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ํ•˜๋ฉฐ, ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹นํ•˜๋Š” ๋ชจ์ž„ ์ถœ๋ ฅ
  • ๋ฌดํ•œ ์Šค์ฝ”๋กค์œผ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ๋งŽ์€ ๋ชจ์ž„์„ ๋…ธ์ถœ์‹œํ‚ด
  • ๋ชจ์ž„ ์ด๋ฏธ์ง€์— ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ•ด ์ƒ๋™๊ฐ์žˆ๋Š” ๋ฉ”์ธ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•จ
  • ๋ชจ์ž„ ๊ธ€ ์ œ๋ชฉ๊ณผ ์ข‹์•„์š” ์ˆ˜ ๋…ธ์ถœ
๊ธ€ ์ƒ์„ธ ํŽ˜์ด์ง€
  • ๋ชจ์ž„์— ๋Œ€ํ•œ [๋‚ ์งœ, ์žฅ์†Œ, ๊ธˆ์•ก, ์ฃผ์ตœ์ž ์ •๋ณด, ์ฐธ์—ฌ ์ธ์›, ์ƒ์„ธ ๋‚ด์šฉ] ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • 'Join' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ๋ชจ์ž„์— ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์Œ
  • ์›ํ™œํ•œ ๋ชจ์ž„ ์ง„ํ–‰์„ ์œ„ํ•ด ๋ชจ์ž„ ์‚ญ์ œ, ์ˆ˜์ •, ์ฐธ์—ฌ ์ทจ์†Œ ๊ธฐ๋Šฅ์€ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ธฐํšํ•จ
  • ์œ ์ € ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ์œ ์ €์˜ ๋งˆ์ดํŽ˜์ด์ง€๋กœ ํ™”๋ฉด ์ „ํ™˜๋จ
๊ธ€ ์ž‘์„ฑ ํŽ˜์ด์ง€
  • ๋ชจ์ž„ ์ง„ํ–‰ ์ „ ์ธ์›์„ ํ™•์ •ํ•˜๊ณ ์ž ์›ํ•˜๋Š” ๋ชจ์ž„ ๋‚ ์งœ 2์ผ ์ „์— ์ž๋™ ๋งˆ๊ฐ๋˜๋„๋ก ๊ธฐํš
  • [์ œ๋ชฉ, ๋ชจ์ž„ ๋‚ ์งœ, ์ธ์›, ์ƒ์„ธ ์„ค๋ช…, ์นดํ…Œ๊ณ ๋ฆฌ, ๋ชจ์ž„ ์žฅ์†Œ, ์ด๋ฏธ์ง€] ๋ฅผ ์ž‘์„ฑ์ž๊ฐ€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Œ
  • ์œ„ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ž‘์„ฑํ•ด์•ผ ๋ชจ์ž„ ๊ธ€ ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ•จ
  • ์„ ํƒํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋”ฐ๋ผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ๋ณ€๊ฒฝ๋จ
๋งˆ์ดํŽ˜์ด์ง€
  • [ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ, ์†Œ๊ฐœ ๊ธ€ ๋ณ€๊ฒฝ, ์ข‹์•„ํ•œ ๋ชจ์ž„ ํ™•์ธ, ์ฐธ์—ฌํ•œ ๋ชจ์ž„ ํ™•์ธ] ๊ธฐ๋Šฅ์„ ํ•  ์ˆ˜ ์žˆ์Œ
  • ๋ชจ์ž„ ์ด๋ฏธ์ง€ ํด๋ฆญ ์‹œ ํ•ด๋‹น ๋ชจ์ž„์˜ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ํ™”๋ฉด ์ „ํ™˜๋จ
  • ํƒ€์ธ์˜ ๋งˆ์ดํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น ์œ ์ €๊ฐ€ ์ข‹์•„ํ•œ ๋ชจ์ž„๊ณผ ์ฐธ์—ฌํ•œ ๋ชจ์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • ์œ ์ € ํŒ”๋กœ์šฐ ๊ธฐ๋Šฅ์€ ์ถ”ํ›„ ์—…๋ฐ์ดํŠธ ์˜ˆ์ •์ž„

๊ตฌํ˜„ ์„ค๋ช…

ํ”„๋กœ์ ํŠธ์—์„œ ์—…๋ฌด

ํ”„๋กœ์ ํŠธ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. ๋ชจ์ž„ ์ฐธ์—ฌ ๊ธฐ๋Šฅ ๊ตฌํ˜„
  2. ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๋ชจ์ž„ ๋งˆ๊ฐ ์ฒ˜๋ฆฌ
  3. SSE ๋ฐฉ์‹์œผ๋กœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ ๋น„๋™๊ธฐ ๊ตฌํ˜„
  4. ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•
    a. AWS๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„ ํ™˜๊ฒฝ ๊ตฌ์ถ•
    b. Github Action์„ ์ด์šฉํ•˜์—ฌ ์ˆ˜๋™ ๋ฐฐํฌ ์‹œ ๊ฐ€์กŒ๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ
    c. ์šด์˜ ์„œ๋ฒ„ ์•ˆ์ •ํ™”

๊ตฌํ˜„ ์„ค๋ช…

  • ์‚ฌ์šฉ ๊ธฐ์ˆ 
๊ตฌ๋ถ„ ๋‚ด์šฉ
Backend Java 11 ,Spring Boot 2.7.14 , SpringDataJPA , Spring Cloud, Gradle
DB H2 , MySQL, AWS RDS
Depoly AWS EC2, AWS S3, Github Action, Docker
API Postman
1. ๋ชจ์ž„ ์ฐธ์—ฌ ๊ธฐ๋Šฅ ๊ตฌํ˜„
Spring Data JPA ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•ด ์ฃผ๋Š” ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ์ž„ ์ฐธ์—ฌ, ๋ชจ์ž„์— ์ฐธ์—ฌํ•œ ์ธ์› ์กฐํšŒ, ๋‚ด๊ฐ€ ์ฐธ์—ฌํ•œ ๋ชจ์ž„ ์กฐํšŒ๋ฅผ ์œ„ํ•œ REST API๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ์ž„ ์ฐธ์—ฌ ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด ์ฐธ์—ฌ ์ค‘๋ณต ์—ฌ๋ถ€์™€ ๋ชจ์ž„ ์ธ์›์ˆ˜๋ฅผ ํ™•์ธํ•œ ๋’ค ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ๋ชจ์ž„ ์ฐธ์—ฌ ๊ธฐ๋ก์„ ์ €์žฅํ•˜๊ณ  ๋ชจ์ž„ ์ฐธ์—ฌ ์—ฌ๋ถ€์ธ BoardStatus์™€ ๋ชจ์ž„ ์ธ์›์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ๋ชจ์ž„ ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ์ฐธ์—ฌ ์ธ์›์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ, ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๋‚ด๊ฐ€ ์ฐธ์—ฌํ•œ ๋ชจ๋“  ๋ชจ์ž„์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ์—์„œ GET ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด Headers๋กœ ์ „๋‹ฌ๋ฐ›์€ Authorization ๊ฐ’์—์„œ memberId๋ฅผ ์ถ”์ถœํ•˜์—ฌ ํ•ด๋‹น ID ๊ฐ’์ด ์ €์žฅ๋œ ๋ชจ์ž„ ์ •๋ณด๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
//ApplicantService
//๋ชจ์ž„ ์ฐธ์—ฌ
    public Applicant joinBoard(long boardId, boolean isJoin){
        //ํ•ด๋‹น ๋ชจ์ž„์— ์ฐธ์—ฌํ–ˆ๋Š”์ง€ ํ™•์ธ
        Long memberId = extractMemberId();
        isJoinAlready(boardId, memberId);
        //๋ชจ์ž„ ์ฐธ์—ฌ ๋กœ์ง
        Applicant applicant = new Applicant();
        Board board = boardRepository.getById(boardId); //๋ณด๋“œ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        applicant.setBoard(board);
        applicant.setBoardImageUrl(board.getImageUrl());
        Member member = memberRepository.getById(memberId); //๋ฉค๋ฒ„ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        applicant.setMember(member);
        applicant.setMemberImageUrl(member.getImageUrl());
        applicant.setMemberNickname(member.getNickname());
        //ํ˜„์žฌ ๋ชจ์ž„ ์ฐธ์—ฌ ์ธ์› ์ˆ˜ ์—…๋ฐ์ดํŠธ
        if(board.getCurrentNum() < board.getTotalNum()){
            board.setCurrentNum(board.getCurrentNum()+1);
            if (board.getCurrentNum() == board.getTotalNum()){
                board.setStatus(Board.BoardStatus.BOARD_COMPLETE);
                String rootImagePath = board.getImageUrl();
                String cutPath = rootImagePath.substring(0, rootImagePath.length()-4);
                System.out.println(cutPath);
                board.setImageUrl(cutPath+"-closed.png"); //๋งˆ๊ฐ ์ด๋ฏธ์ง€๋กœ ๋ณ€๊ฒฝ
                boardRepository.save(board);
                //๋ชจ์ง‘ ๋งˆ๊ฐ ์•Œ๋ฆผ ๋ฐœ์†ก
                notifyCompleted(board);
            }
        }else {//์ธ์›์ˆ˜ ๋‹ค ์ฐผ์œผ๋ฉด ์ถ”๊ฐ€ ์•ˆํ•จ
            throw new BusinessLogicException(ExceptionCode.NOT_ALLOW_PARTICIPATE);
        }
        //์ฐธ์—ฌ ์ •๋ณด ์ €์žฅ
        Applicant savedApplicant = applicantRepository.save(applicant);
        //๋ชจ์ž„ ์ฐธ์—ฌ ์ฒ˜๋ฆฌ
        applicant.setJoin(true);
        //์•Œ๋ฆผ ๋ฐœ์†ก
        notifyApplicant(board);
        notifyParticipants(board);
        
        return savedApplicant;
    }
    
    
    //๋ชจ์ž„ ์ฐธ์—ฌ ์ค‘์ธ ๋ชจ๋“  ์ธ์›์—๊ฒŒ ์•Œ๋ฆผ ์ „์†ก
    private void notifyParticipants(Board board){
        alarmService.sendParticipantsNotification(board);
    }
    //๋ชจ์ž„ ์ฐธ์—ฌ ์™„๋ฃŒ ์•Œ๋ฆผ ์ „์†ก (๋ชจ์ž„ ์ฐธ์—ฌ ์‹ ์ฒญ์ž์—๊ฒŒ ์ „์†ก)
    private void notifyApplicant (Member member, Board board){
        alarmService.sendApplicantNotification(member,board);
    }
    //๋ชจ์ž„ ๋ชจ์ง‘ ๋งˆ๊ฐ ์•Œ๋ฆผ ์ „์†ก
    private void notifyCompleted(Board board){
        alarmService.sendCompletedNotification(board);
    }
2. ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๋ชจ์ž„ ๋งˆ๊ฐ ์ฒ˜๋ฆฌ
Spring boot์—์„œ ์ œ๊ณตํ•˜๋Š” Scheduler ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ์ž„ ๋งˆ๊ฐ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ผ์ •ํ•œ ์ฃผ๊ธฐ๋กœ ๋งˆ๊ฐ ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋งˆ๊ฐ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Scheduler ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ์ž„ ๋งˆ๊ฐ ์ฒ˜๋ฆฌ ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. - ๋ชจ์ž„ 2์ผ ์ „ - ๋ชจ์ง‘ ์ธ์›์ด ์ถฉ์กฑ๋˜์—ˆ์„ ๋•Œ ๋งค์ผ 00์‹œ์— ํ™•์ธํ•˜์—ฌ ๊ธ€ ์ž‘์„ฑ ์‹œ ์„ค์ •ํ•œ Date์˜ 2์ผ ์ „์ด๋ผ๋ฉด BoardStatus๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ๋ชจ์ง‘ ์ธ์›์ด ์ถฉ์กฑ๋˜์—ˆ๋Š”์ง€๋Š” ๋ชจ์ž„ ์ฐธ์—ฌ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋ชจ์ž„ ์ฐธ์—ฌ๋ฅผ ํ•˜๋ฉฐ ํ˜„์žฌ ์ธ์›์—์„œ +1 ๋ฅผ ํ•ด์ฃผ๋ฉฐ, ํ˜„์žฌ ์ธ์›(current num)๊ณผ ๋ชจ์ง‘ ์ธ์›(total num)๊ฐ’์ด ๊ฐ™์•„์ง€๋ฉด BoardStatus๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. BoardStatus๋ฅผ *BOARD_RECRUITING โ†’ BOARD_COMPLETE* ๋กœ ์ƒํƒœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋ชจ์ž„ ๋งˆ๊ฐ ์ฒ˜๋ฆฌ ํ›„ BoardStatus๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ BoardStatus ๋ณ€๊ฒฝ ํ›„์— ๋ชจ์ž„ ์ด๋ฏธ์ง€๋ฅผ ๋งˆ๊ฐ ์ด๋ฏธ์ง€๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
//BoardService
   //๋‚ ์งœ์ง€๋‚œ ๋ชจ์ž„ ๋งˆ๊ฐ์ฒ˜๋ฆฌ
    @Scheduled(cron = "0 0 0 * * *") //๋งค์ผ 00์‹œ์— ์ˆ˜ํ–‰
    public void checkDate(){
        LocalDate today = LocalDate.now();
        List<Board> closedList = findEventsScheduledForDate(today.plus(2, ChronoUnit.DAYS));
        for (Board board : closedList){
            board.setStatus(Board.BoardStatus.BOARD_COMPLETE);
            String rootImagePath = board.getImageUrl();
            String cutPath = rootImagePath.substring(0, rootImagePath.length()-4);
            System.out.println(cutPath);
            board.setImageUrl(cutPath+"-closed.png");
            boardRepository.save(board);
            //์•Œ๋ฆผ ๋ฐœ์†ก
            notifyDeadline(board);
        }
    }
    
    //ํ•ด๋‹น ๋‚ ์งœ์— ์˜ˆ์ •๋œ ๋ชจ์ž„ ๊ฒ€์ƒ‰(์ด๋ฉ”์ผ ๋ฐœ์†ก ๊ด€๋ จ ๋ฉ”์„œ๋“œ)
    public List<Board> findEventsScheduledForDate(LocalDate eventDate ) {
        //BoardStatus๊ฐ€ ๋ชจ์ง‘์ค‘ ์ƒํƒœ์ธ ๋ชจ์ž„๋งŒ ๋ฐ˜ํ™˜
        return boardRepository.findByDateAndStatus(eventDate, Board.BoardStatus.BOARD_RECRUITING);
    }
3. SSE ๋ฐฉ์‹์œผ๋กœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ ๋น„๋™๊ธฐ ๊ตฌํ˜„

์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๋ฆผ์„ ์‚ญ์ œํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์•Œ๋ฆผ GET ์š”์ฒญ ์‹œ ์ง€๋‚œ ์•Œ๋ฆผ๋“ค์„ ๋ชจ๋‘ ๋ณด๋‚ด์ฃผ๊ณ , ํŠน์ • ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ๋ณด๋‚ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ค„ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์„ ๊ฐ€์ง„ SSE(Server-Sent-Events) ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์•Œ๋ฆผ ๋ฐœ์†ก ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. - ๋ชจ์ž„ ๊ธ€ ์ž‘์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ - ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ์ž„์— ์ฐธ์—ฌํ–ˆ์„ ๋•Œ - ๊ธฐ์กด์— ๋ชจ์ž„์— ์ฐธ์—ฌํ•œ ๋ชจ๋“  ์ธ์›์—๊ฒŒ ์•Œ๋ฆผ ๋ฐœ์†ก - ๋ชจ์ž„์ด ๋งˆ๊ฐ๋˜์—ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ์—์„œ ์•Œ๋ฆผ ๊ตฌ๋… ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ๊ตฌ๋… ์š”์ฒญ ์ „ ๋ฐœ์ƒํ•˜์—ฌ ๋ˆ„๋ฝ๋œ ์•Œ๋ฆผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋ˆ„๋ฝ๋œ ์•Œ๋ฆผ์„ ํฌํ•จํ•ด ๊ตฌ๋…์ด ๋˜์–ด์žˆ๋Š” ๋™์•ˆ ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜๋ฉด ์•Œ๋ฆผ์„ ๋ณด๋‚ด์ค๋‹ˆ๋‹ค.
//alaramService
    //ํด๋ผ๊ฐ€ ๊ตฌ๋…์„ ์œ„ํ•ด ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ
    public SseEmitter subscribe(Long memberId, String lastEventId) {
        String id = makeTimeIncludedId(memberId);
        SseEmitter emitter = emitterRepository.save(id, new SseEmitter(DEFAULT_TIMEOUT));
        //emitter ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ emitter๋ฅผ ์‚ญ์ œ (๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ „์†ก๋œ ์ƒํƒœ)
        emitter.onCompletion(() -> emitterRepository.deleteById(id));
        //emitter ๊ฐ€ ํƒ€์ž„์•„์›ƒ๋˜์—ˆ์œผ๋ฉด emitter ์‚ญ์ œ (์ง€์ •๋œ ์‹œ๊ฐ„๋™์•ˆ ์–ด๋–ค ์ด๋ฒคํŠธ๋„ ์ „์†ก x)
        emitter.onTimeout(() -> emitterRepository.deleteById(id));
       //503 ์—๋Ÿฌ ๋ฐฉ์ง€ํ•˜๊ณ ์ž ๋”๋ฏธ ์ด๋ฒคํŠธ ์ „๋‹ฌ
        sendToClient(emitter, id, "EventStream Created. [memberId=" + memberId + "]");
        //ํด๋ผ๊ฐ€ ๋ฏธ์ˆ˜์‹ ํ•œ event๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ์ „์†ก (์œ ์‹ค ๋ฐฉ์ง€)
        if (hasLostData(lastEventId)){
            sendLostdata(lastEventId, memberId, emitter);
        }
        return emitter;
    }
    /**
     * ์•Œ๋ฆผ ์ƒ์„ฑ, ์ „์†ก ๋ฉ”์„œ๋“œ
     * ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์•Œ๋žŒ์„ ์ฝ์Œ์ฒ˜๋ฆฌ
     */
    @Async
    @Transactional
    public void sendAlarm(Member member, Board board, Alarm.AlarmStatus alarmStatus, String content){
        Alarm alarm = Alarm.create(member, board, alarmStatus,content);
        alarmRepository.save(alarm);
        Map<String, SseEmitter> sseEmitters = emitterRepository.findAllEmittersStartWithByMemberId(member.getId());
        String eventId = makeTimeIncludedId(member.getId());
        System.out.println(eventId);
        sseEmitters.forEach((key, emitter) -> {
            //๋ฐ์ดํ„ฐ ์บ์‹œ ์ €์žฅ (์œ ์‹ค ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด)
            emitterRepository.saveEventCache(key,alarm);
            //๋ฐ์ดํ„ฐ ์ „์†ก
            sendToClient(emitter,eventId, content);
        });
    }

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-03-29 09.40.10.png

4. ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•

  1. AWS๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„ ํ™˜๊ฒฝ ๊ตฌ์ถ•

    AWS๋Š” ๋งŽ์€ ์ ์œ ์œจ์„ ์ฐจ์ง€ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ จ ์ž๋ฃŒ๋“ค์ด ๋งŽ๊ณ , ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„๋ฅผ ์ฒ˜์Œ ๊ตฌ์ถ•ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ์ž๋ฃŒ๊ฐ€ ๋งŽ์€ ๊ฒƒ์„ ํฐ ์žฅ์ ์œผ๋กœ ์ƒ๊ฐํ•˜์—ฌ AWS๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. AWS EC2 ์™€ RDS๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜์˜€์œผ๋ฉฐ, S3 ๋ฒ„ํ‚ท์„ 2๊ฐœ ๊ตฌ์„ฑํ•˜์—ฌ ํ”„๋ก ํŠธ ๋ฐฐํฌ์™€ ํŒŒ์ผ ์„œ๋ฒ„๋กœ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

    ๋˜ํ•œ, ๋™์  ์ฝ˜ํ…์ธ  ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ•๊ณผ ํŠธ๋ž˜ํ”ฝ ๊ธ‰์ฆ์„ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด AWS CloudFront๋ฅผ ์ ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„ 515ms โ†’ 84ms ๋‹จ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค.

    ๋‚ด ๋„๋ฉ”์ธ ํ•œ๊ตญ์—์„œ ๋„๋ฉ”์ธ ์ฃผ์†Œ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  AWS route53์„ ์ด์šฉํ•ด CloudFront์˜ ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์— DNS๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

  2. Github Action์„ ์ด์šฉํ•˜์—ฌ ์ˆ˜๋™ ๋ฐฐํฌ ์‹œ ๊ฐ€์กŒ๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ

    ์ด์ „ Stackoverflow ํด๋ก  ์ฝ”๋”ฉ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉฐ ์ˆ˜๋™์œผ๋กœ jar ํŒŒ์ผ์„ ๋นŒ๋“œํ•˜์—ฌ AWS EC2๋กœ ์˜ฎ๊ธด ํ›„ ์„œ๋ฒ„๋ฅผ ๋ฐฐํฌํ–ˆ๋Š”๋ฐ, ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ ๊ณผ์ •์—์„œ ๊ฐœ๋ฐœ์ž์˜ ์‹ค์ˆ˜๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋กœ ๋ฐฐํฌ๊นŒ์ง€ ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ๊นŒ์ง€ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด Github Action์„ ์ด์šฉํ•ด CI/CD๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

    ๋ฐฑ์—”๋“œ์˜ ๊ฒฝ์šฐ ๋„์ปค ์ด๋ฏธ์ง€๋กœ jar ํŒŒ์ผ์„ ๋นŒ๋“œํ•˜์—ฌ Ec2 ์—์„œ ๋„์ปค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ณผ์ •์œผ๋กœ ์„œ๋ฒ„๋ฅผ ๋ฐฐํฌํ–ˆ๊ณ , ํ”„๋ก ํŠธ์—”๋“œ์˜ ๊ฒฝ์šฐ main ๋ธŒ๋ Œ์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋นŒ๋“œํ•œ ๋’ค, S3 ๋ฒ„ํ‚ท์œผ๋กœ ๋ฐฐํฌํ–ˆ์Šต๋‹ˆ๋‹ค.

    ๊ทธ ๊ฒฐ๊ณผ๋กœ ์ตœ๊ทผ 10๊ฐœ ์„œ๋ฒ„ ๋ฐฐํฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ CI/CD ๊ณผ์ •์— ํ‰๊ท  1๋ถ„ 32์ดˆ ์†Œ์š”๋˜๋„๋ก ๋‹จ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค.

  3. ์šด์˜ ์„œ๋ฒ„ ์•ˆ์ •ํ™”

    ํ•„์ˆ˜ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์„ ๋ฐฑ์—”๋“œ๊ฐ€ ๋จผ์ € ์™„๋ฃŒํ•˜์—ฌ ์ถ”ํ›„ ๊ฐœ๋ฐœ ์˜ˆ์ •์œผ๋กœ ์ •ํ•œ ํ•ญ๋ชฉ์„ ๋จผ์ € ์ž‘์—…ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ์„œ๋ฒ„ ์‹ ๊ทœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€๋กœ ์šด์˜์— ์˜๋„ํ•˜์ง€ ์•Š์€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์šด์˜๊ณผ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌ ์ž‘์—…ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋˜ํ•œ AWS ๋ฅผ ์ด์šฉํ•ด ์šด์˜ ์„œ๋ฒ„์™€ ๋™์ผํ•œ ์ŠคํŽ™์œผ๋กœ ๊ตฌ์„ฑํ–ˆ์œผ๋ฉฐ, ์‹ ๊ทœ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœ ์„œ๋ฒ„์— ๋จผ์ € ๋ฐฐํฌํ•˜์—ฌ ๊ธฐ๋Šฅ์„ ํ™•์ธํ•œ ๋’ค ๋ฌธ์ œ๊ฐ€ ์—†์œผ๋ฉด main ๋ธŒ๋ Œ์น˜๋กœ mergeํ•˜์—ฌ ์šด์˜ ์„œ๋ฒ„์— ๋ฐฐํฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ์— ๋Œ€ํ•œ ๋ถ€๋‹ด์„ ๋œ๊ณ  ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์—…์—์„œ ์—ฌ๋Ÿฌ ๋Œ€์˜ ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•ด ์šด์˜ํ•˜๋Š” ๊ณผ์ •๊ณผ ์„œ๋ฒ„ ํ•œ๋Œ€๊ฐ€ ๊ฐ€์ง€๋Š” ๋ถ€๋‹ด์„ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ๊ธ์ •์ ์ธ ์ด์œ ๋ฅผ ์ง์ ‘ ๋Š๋‚„ ์ˆ˜ ์žˆ๋Š” ๊ฒฝํ—˜์ด์—ˆ์Šต๋‹ˆ๋‹ค.


๊ทœ์น™

โœ… git commit ๊ทœ์น™
Type ๋‚ด์šฉ
feat ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€
fix ๋ฒ„๊ทธ ์ˆ˜์ •
docs ๋ฌธ์„œ ์ˆ˜์ •
style ์ฝ”๋“œ ํฌ๋งทํŒ…, ์„ธ๋ฏธ์ฝœ๋ก  ๋ˆ„๋ฝ (์ฝ”๋“œ๋ณ€๊ฒฝ x)
refactor ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง

์‚ฌ์šฉ ์˜ˆ์‹œ [์ง๋ฌด] Type : ๋ณ€๊ฒฝ ๋‚ด์šฉ ex) header ์ปดํฌ๋„ŒํŠธ ์ž‘์—… ์™„๋ฃŒ โžก๏ธ [FE]feat : header component ์ž‘์—… ์™„๋ฃŒ

โœ… ๋ฐฐํฌ ๊ทœ์น™
  • ๋งค ์Šคํ”„๋ฆฐํŠธ ๋งˆ๊ฐ์ผ 16์‹œ์— ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ, ๋ฐฐํฌ ์™„๋ฃŒ ํ›„ 2์‹œ๊ฐ„๋™์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค. (default)
  • ์„œ๋ฒ„ ๋ฐฐํฌ - ๋ฐฑ์—”๋“œ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ์™„๋ฃŒ๋˜๋ฉด 3์ธ์ด ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ํ›„ ๋ฐฐํฌ ์ง„ํ–‰ํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ๋ฐฐํฌ - ๋งค ์Šคํ”„๋ฆฐํŠธ ๋งˆ๊ฐ ํ›„ ์ทจํ•ฉํ•˜์—ฌ ์ง„ํ–‰ํ•œ๋‹ค.
  • ๋ฐฐํฌ ๋‚ ์งœ ๋˜๋Š” ์‹œ๊ฐ„์— ๋Œ€ํ•ด ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•  ์‹œ ์†Œํ†ต ์ฝ”์–ด ํƒ€์ž„์— ๊ณต์œ ํ•œ๋‹ค.
  • ๋ฐฐํฌ ๋ฒ„์ „์€ ํ˜„์žฌ ์Šคํ”„๋ฆฐํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •ํ•œ๋‹ค. ex_์Šคํ”„๋ฆฐํŠธ1 ์ž‘์—…๋ฌผ ๋ฐฐํฌ ์‹œ V1.0.0 ~
โœ… ์šด์˜/๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋ถ„๋ฆฌ
  • ๋ฆฌํŒฉํ† ๋งํ•œ ๋‚ด์šฉ(feat_FE/BE) -> dev ๋ธŒ๋ Œ์น˜๋กœ ์—…๋กœ๋“œ -> ๊ฐœ๋ฐœ ์„œ๋ฒ„์— ๋ฐฐํฌํ•˜์—ฌ ํ™•์ธ
  • ์„œ๋น„์Šค์ค‘์ธ ๊ณณ์—์„œ ๋ฒ„๊ทธ ์ˆ˜์ • ํ•„์š” ์‹œ -> main ๋ธŒ๋ Œ์น˜์—์„œ ๋ฐ”๋กœ fix or hotfix๋กœ push.
  • ๊ฐœ๋ฐœ ์„œ๋ฒ„์—์„œ ํ…Œ์ŠคํŠธ ์™„๋ฃŒํ•˜์—ฌ ์„œ๋น„์Šค ํ•  ์‚ฌํ•ญ -> main ๋ธŒ๋žœ์น˜๋กœ merge -> ์šด์˜์„œ๋ฒ„์— ๋ฐฐํฌํ•˜์—ฌ ํ™•์ธ

๋ฒ„์ „ ์—…๋ฐ์ดํŠธ ๋กœ๊ทธ

V1.0.0 ๋ฉ”์ธํŽ˜์ด์ง€, ์œ ์ € ์ •๋ณด ์กฐํšŒ, ๋ชจ์ž„ ๊ธ€ ์ƒ์„ธ ์กฐํšŒ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ
V2.0.0 ๋ฉ”์ธ ํŽ˜์ด์ง€ ์ถ”๊ฐ€ ๊ตฌ์„ฑ, ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ„ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ, ๋ชจ์ž„ ์ƒ์„ฑ, ์ฐธ์—ฌ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ
V3.0.0 ์•Œ๋ฆผ, ํŒ”๋กœ์ž‰, ํŽ˜์ด์ง€ ํšจ๊ณผ, ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ

V4.0.1 ์•Œ๋ฆผ ๊ธฐ๋Šฅ ๋กœ์ง ๋ถ„๋ฆฌ ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
V4.0.2 docker-compose ์‚ฌ์šฉ์„ ์œ„ํ•œ workflow ์ˆ˜์ •, ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํŒŒ์ผ ์ถ”๊ฐ€

์•ž์œผ๋กœ์˜ ๊ฐœ๋ฐœ ๊ณ„ํš

  • ๋ชจ์ž„ ์ด๋ฏธ์ง€ ์ถœ๋ ฅ ์ด์ƒ ์ด์Šˆ ์ฒ˜๋ฆฌ
  • ์•Œ๋ฆผ ์„œ๋ฒ„ ๋ถ„๋ฆฌ
  • ํšŒ์›๊ฐ€์ž… ์‹œ ์ด๋ฉ”์ผ ์ธ์ฆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ, OOP ์›์น™ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ ์ˆ˜์ •
  • @setter ๋“ฑ ์ ์ ˆํ•œ ๊ธฐ๋Šฅ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธ ๋ฐ ์ˆ˜์ •
  • About

    Celebee๐Ÿ ํ”„๋กœ์ ํŠธ์—์„œ ์ž‘์—…ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

    https://celebee.kro.kr


    Languages

    Language:Java 99.4%Language:Dockerfile 0.6%