커널360 BE 2기 FINAL PROJECT 배드민턴 칠까? 프로젝트입니다 ❕
✅ 배드민턴을 치고 싶은데 같이 칠 사람이 없어서 동호회에 가입하고 싶다 !
✅ 매 경기마다 제비뽑기로 대진표를 짜는 것이 불편하다 !
✅ 내가 원하는 시간대와 장소에서 열리는 경기에 참여하고 싶다 !
✅ 경기 승패를 쉽게 기록하고 싶다 !
✅ 내가 참여한 경기와 경기 결과를 보고 싶다 !
✅ 배드민턴 동호회 내에서 나의 위치를 확인하고 싶다 !
❇️ 마음에 드는 동호회를 지역별로 조회하고, 가입할 수 있습니다 !
❇️ 동호회장 및 운영 매니저는 새로운 경기 일정을 생성할 수 있습니다 !
❇️ 동호회 가입자는 본인이 가능한 시간대와 장소에서 주최되는 경기에 참여할 수 있습니다 !
❇️ 모집이 마감되면 자동으로 대진표가 만들어집니다 !
❇️ 경기 중에 점수를 기록할 수 있습니다 !
❇️ 경기 종료 시 경기 결과와 승패가 기록됩니다 !
❇️ 내가 참여한 경기와 경기 결과, 승패 여부 등을 마이페이지에서 확인할 수 있습니다 !
❇️ 경기 승률과 경기 참여 횟수에 따른 내 티어를 확인할 수 있습니다 !
박소은 | 이강민 | 이선우 |
---|
- JAVA 17
- Spring Boot 3.3.2
- Spring Data JPA
- Next.js
- MySQL 8
- Docker
-
Git-flow 전략을 기반으로 main, develop, release, test 브랜치와 feature 보조 브랜치를 운용했습니다.
-
main, develop, release, test, feature 브랜치로 나누어 개발을 했습니다.
- develop 브랜치는 개발 단계에서 git-flow의 master 역할을 하는 브랜치입니다.
- release 브랜치는 main에 push하기 전에 배포테스트를 하기 위한 브랜치입니다.
- test 브랜치는 프론트엔드 작업자 분들과 함께 API 테스트를 하기 위한 브랜치입니다.
- feature 브랜치는 기능 단위로 독립적인 개발 환경을 위하여 사용하고 merge 후 각 브랜치를 삭제해주었습니다.
F2_PLAY_BADMINTON_BE/
│
├── .github/
├── .gradle/
├── .idea/
├── badminton-api/
│ ├── bin/
│ ├── build/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── org.badminton.api/
│ │ │ └── resources/
│ │ │ ├── .env
│ │ │ ├── application.yaml
│ │ │ ├── data.sql
│ │ │ └── logback-spring.xml
│ │ └── test/
│ ├── build.gradle
│ └── Dockerfile
├── badminton-batch/
├── badminton-domain/
├── bin/
├── gradle/
├── logs/
├── .env
├── .gitignore
├── build.gradle
├── docker-compose.yaml
├── gradlew
└── settings.gradle
- 전체 개발 기간 : 2024-09-09 ~ 2024-10-17
- GitHub Projects와 Issues를 사용하여 진행 상황을 공유했습니다.
- Swagger로 개발한 API를 문서화해서 프론트엔드분들과 함께 공유했습니다.
- 개발 중 팀원들과 논의할 사항들을 GitHub Discussions에 등록했습니다.
- Github Wiki를 사용하여 개발 중 공부한 내용을 문서로 작성했습니다.
- 10분에서 15분의 스크럼과 회고를 통해 개발 방향성에 대한 고민을 나누고 Notion에 회의 내용을 기록했습니다.
- 이미지, 동호회 이름, 소개글
- 동호회 티어
가입하지 않고 사용할 수 있는 기능
- 가입하지 않은 사용자에 대해 가입 버튼 활성화
가입해야 사용할 수 있는 기능
동호회장
운영진
- 경기 제목
- 소개글(게시글과 유사)
- 시간, 장소
- 티어 제한
- 누구나 참여할 수 있는 경기 → 랜덤으로 !!
- 상/중/하 →
최소 티어
- 모집 기한
- 모집 인원
- **
단식
**인지 **복식
**인지 - 경기 상태
- 모집 중
- 모집 완료
- 완료
가입해야 사용할 수 있는 기능
모든 가입자
- 달력 경기 프리뷰 → 한 달 동안의 경기(경기 제목, 경기 모집 상태, 경기 날짜)
- 달력 날짜를 누르면 경기 일정 리스트 조회 →
- 시간, 장소, 티어 제한, 소개글, 모집 기한, 모집 인원
- 경기 상태
- 모집 인원 미달 시 경기 취소
- 모집 인원 도달 시 경기 마감
가입해야 사용할 수 있는 기능
모든 가입자
- 티어 미달 시 참여 신청 불가
- 경기 일정 용어 → 명확하게 수정(event)
가입해야 사용할 수 있는 기능
모든 가입자
- 만들어진 대진표를 조회
- 모집 기한이 지나자마자 최소 인원이 모이면 대진표가 자동으로 만들어진다.
- 최소 인원이 모이지 않았다면 경기가 취소 상태로 변경됨
<추가 기능>
- 당일 인원(외부인) 추가 및 대진표 수정
- 인원 설정 시 제한을 둬야 할 것 같다. (
fix
하는 것이 좋을 것 같다)- **
단식
**일 경우 → 짝수만 - **
복식
**일 경우 → **4
**의 배수만
- **
<확장성 - 대진표 생성 rule>
- **
admin
**에서 **rule
**을 추가- event를 등록하는 총무 → matching 방법 (라디오 버튼)
- 지금은
random
만 enable - 나이순, 성별, 티어(구력이 비슷한 사람끼리)
- 해당 rule에 대해 matching이 될 수 있도록 !!
- rule strategy (
Interface
)- 지금은 random으로만❗️
- 정책 패턴
가입해야 사용할 수 있는 기능
모든 가입자
- 경기 스코어 기록 → 저장 → 어디서 볼 수 있을까?
<추가 기능>
- 경기 결과 스크린샷을 공유할 수 있도록
- 경기 결과를 SNS에 올리도록 할 수 있도록!!
가입해야 사용할 수 있는 기능
동호회장
운영진
- 회원 Role 관리 → 동호회장, 운영진
- 권한 설정
- 동호회 탈퇴
- 동호회 기능 사용 중지(며칠?)
가입하지 않고 사용할 수 있는 기능
- 티어별로 조회
- 마우스 오버했을 때 회원 간단 정보를 조회 가능
<추가사항>
- 랭킹 조회
이름
티어
프로필 이미지
전적(?승?패?무)
- 티어를 기록하는 기준: 승패율 + 경기 횟수 + 경력
- → Rating
- 경기 결과는 개인 점수에만 반영, 개인 점수는 티어로 표현 가능
- 그룹 점수는 그룹원들의 티어 분포도 !!!
- 실력 점수, 활동 점수
- 승률
- 그룹 내에서만 쓸 수 있는, 외부에 보여줄 수 있는 것을 구분
- 전체 동호회 조회
- 동호회 소개 페이지 조회
- 프로필 사진 수정
- 로그아웃
- 이름, 이메일, 프로필 사진 조회
- 회원 탈퇴
- 동호회 가입 신청
- 가입한 동호회 이름, 역할, 티어, 전적 조회
- 경기 참여 신청 및 취소
- 특정 경기 월별, 일별로 조회
- 대진표 조회
- 게임의 세트 상세 점수 조회
- 동호회 수정
- 경기 생성, 삭제 및 수정
- 대진표 생성
- 세트별 점수 저장
- 동호회원 강제 탈퇴
- 동호회원 정지
- 동호회원 역할 변경
- oAuth 로그인은
naver
,kakao
,google
API 사용
@Bean
@Order(2)
public SecurityFilterChain clubFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/v1/clubs/**")
.csrf(AbstractHttpConfigurer::disable)
.cors(this::corsConfigurer)
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil, clubMemberService),
UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new ClubMembershipFilter(clubMemberService), JwtAuthenticationFilter.class)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/v1/clubs", "/v1/clubs/{clubId}", "/v1/clubs/search")
.permitAll()
.requestMatchers(HttpMethod.POST, "/v1/clubs")
.permitAll()
.requestMatchers(HttpMethod.DELETE, "/v1/clubs/{clubId}")
.access(hasClubRole("OWNER"))
.requestMatchers(HttpMethod.PATCH, "/v1/clubs/{clubId}")
.access(hasClubRole("OWNER", "MANAGER"))
.requestMatchers(HttpMethod.GET, "/v1/clubs/{clubId}/leagues/{leagueId}")
.access(hasClubRole("OWNER", "MANAGER", "USER"))
.requestMatchers(HttpMethod.GET, "/v1/clubs/{clubId}/clubMembers")
필터를 사용해서 권한 별 api 사용 구현
@Getter
public class BadmintonException extends RuntimeException {
private final ErrorCode errorCode;
private final String errorMessage;
public BadmintonException(ErrorCode errorCode, String errorDetails) {
this(errorCode, errorDetails, null);
}
public BadmintonException(ErrorCode errorCode, String errorDetails, Exception e) {
super(errorCode.getDescription() + errorDetails, e);
this.errorCode = errorCode;
this.errorMessage = errorCode.getDescription() + errorDetails;
}
public BadmintonException(ErrorCode errorCode) {
this(errorCode, (Exception)null);
}
public BadmintonException(ErrorCode errorCode, Exception e) {
this.errorCode = errorCode;
this.errorMessage = errorCode.getDescription();
}
}
RuntimeException
을 상속받은 BadmintonException
을 만들어 커스텀 예외 처리 구현
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Bean
public Job deleteMemberJob(Step deleteMemberStep, JobRepository jobRepository) {
return new JobBuilder("deleteMemberJob", jobRepository)
.start(deleteMemberStep)
.build();
}
@Bean
public Step deleteMemberStep(ItemReader<MemberEntity> reader, ItemProcessor<MemberEntity, MemberEntity> processor,
ItemWriter<MemberEntity> writer, JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("deleteMemberStep", jobRepository)
.<MemberEntity, MemberEntity>chunk(10, transactionManager)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
배치와 스케줄러를 사용해 주기적으로 회원 삭제 및 정지 해제 로직 실행
AWS
의 EC2와 RDS를 사용해서 배포
테스트용 EC2와 Production용 EC2 두 개 생성
networks:
backend:
driver: bridge
services:
badminton-api:
build: ./badminton-api # badminton-api 모듈에 있는 Dockerfile을 빌드
ports:
- "8080:8080" # API 서비스의 포트
networks:
- backend
image: speech2/badminton:api
badminton-batch:
build: ./badminton-batch # badminton-batch 모듈에 있는 Dockerfile을 빌드
ports:
- "9090:9090"
networks:
- backend
image: speech2/badminton:batch
Docker-compose.yaml 파일 사용해서 jar 파일 대체
AWS
의 CloudWatch를 사용해서 로그 관리
AWS
의 S3을 사용해서 회원 프로필 이미지나 동호회 배너 이미지 업로드
jmeter
를 사용해서 부하 테스트 및 성능 테스트
spring:
profiles:
active: ${ACTIVE_PROFILE}
application:
name: badminton
jpa:
hibernate:
ddl-auto: ${DDL_METHOD_API}
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
defer-datasource-initialization: true
messages:
encoding: UTF-8
basename: messages
sql:
init:
mode: ${SQL_MODE}
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USER_NAME}
password: ${DATABASE_USER_PASSWORD}
driver-class-name: ${DATABASE_DRIVER}
jwt:
secret: ${JWT_SECRET}
.Env 파일을 이용해서 키 공개 X
@Constraint(validatedBy = ClubDescriptionValidatorImpl.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ClubDescriptionValidator {
String message() default "동호회 소개란은 2자 이상 입력해야 하며 최대 1000자까지 입력할 수 있습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
커스텀 Validation을 사용해서 검증
GitHub GitHub - Kernel360/F2_WHERE_ARE_YOU_BE: kernel360 Final 프로젝트…
GitHub GitHub - Kernel360/F2_WHERE_ARE_YOU_BE: kernel360 Final 프로젝트…
https://www.figma.com/design/mx70EdVAm7gOnxGUIwztdJ/PLAY_BADMINTON?node-id=0-1&node-type=canvas