Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

compileOnly 'org.projectlombok:lombok'
// runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/example/moim/club/entity/Club.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.example.moim.global.exception.ResponseCode;
import com.example.moim.global.util.file.model.FileInfo;
import com.example.moim.match.entity.Match;
import com.example.moim.statistic.entity.Statistic;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -85,6 +86,10 @@ public static Club from(ClubInput clubInput, FileInfo fileInfo) {
club.mainUniformColor = clubInput.getMainUniformColor();
club.subUniformColor = clubInput.getSubUniformColor();
club.memberCount = 1;

Statistic.createStatistic(club, SportsType.OVERALL);
Statistic.createStatistic(club, SportsType.FUTSAL);
Statistic.createStatistic(club, SportsType.SOCCER);
return club;
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/example/moim/club/entity/UserClub.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class UserClub extends BaseEntity {
private Integer scheduleCount;
private Integer matchCount;

private int score;

public static UserClub createLeaderUserClub(User user, Club club) {
UserClub userClub = new UserClub();
userClub.user = user;
Expand Down Expand Up @@ -59,4 +61,8 @@ public static UserClub createUserClub(User user, Club club) {
public void changeUserClub(ClubRole clubRole) {
this.clubRole = clubRole;
}

public void updateScore(int score) {
this.score += score;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.util.Optional;

public enum SportsType {
SOCCER("축구"), FUTSAL("풋살");
SOCCER("축구"), FUTSAL("풋살"), OVERALL("전체");
private final String koreanName;

SportsType(String koreanName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ public enum ResponseCode {
MATCH_TIME_OUT(HttpStatus.BAD_REQUEST, "MATCH4007", "매치가 종료된 후 48시간이 지났습니다."),
MATCH_NOT_CONFIRMED(HttpStatus.BAD_REQUEST, "MATCH4008", "확정된 매치가 아닙니다."),
MATCH_SCHEDULE_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCH4009", "매치 일정이 확정되지 않았거나, 존재하지 않습니다."),
MATCH_DUPLICATED(HttpStatus.BAD_REQUEST, "MATCH4010", "해당 시간에 다른 매치가 존재합니다."),

// MatchUser Error
MATCH_USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCH4005", "가입된 모임이 없습니다."),
MATCH_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, "MATCH4006", "매치를 취소할 수 없습니다."),
MATCH_USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCHUSER4001", "가입된 모임이 없습니다."),
MATCH_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, "MATCHUSER4002", "매치를 취소할 수 없습니다."),
MATCH_USER_NOT_ATTENDANCE(HttpStatus.BAD_REQUEST, "MATCHUSER4003", "매치에 참여한 클럽 소속이 아닙니다"),

// Statistic Error
STATISTIC_NOT_FOUND(HttpStatus.NOT_FOUND, "STATISTIC4001", "전적을 찾을 수 없습니다."),

// Token Error
ACCESS_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "TOKEN4001", "헤더에 토큰 값이 없습니다"),
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/example/moim/match/entity/Match.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public void setMatchScore(int homeScore, int awayScore) {
public void timeDuplicationCheck(LocalDateTime startTime, LocalDateTime endTime) {
if ((startTime.isBefore(this.startTime) && endTime.isBefore(this.startTime)) ||
(startTime.isAfter(this.endTime) && endTime.isAfter(this.endTime))) {
throw new MatchRecordExpireException("해당 시간대에 다른 매치 일정이 있습니다");
// throw new MatchRecordExpireException("해당 시간대에 다른 매치 일정이 있습니다");
throw new MatchControllerAdvice(ResponseCode.MATCH_DUPLICATED);
}
}
}
14 changes: 12 additions & 2 deletions src/main/java/com/example/moim/match/entity/MatchUser.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.example.moim.match.entity;

import com.example.moim.club.entity.Club;
import com.example.moim.global.exception.ResponseCode;
import com.example.moim.match.exception.advice.MatchControllerAdvice;
import com.example.moim.schedule.entity.ScheduleVote;
import com.example.moim.schedule.vote.entity.ScheduleVote;
import com.example.moim.club.entity.UserClub;
import com.example.moim.match.dto.MatchRecordInput;
import com.example.moim.statistic.entity.Statistic;
import com.example.moim.user.entity.User;
import jakarta.persistence.*;
import lombok.Getter;
Expand All @@ -25,13 +29,16 @@ public class MatchUser {
private Club club;

private int score;
private String season;

public static MatchUser createMatchUser(Match match, ScheduleVote scheduleVote) {
MatchUser matchUser = new MatchUser();
matchUser.match = match;
matchUser.user = scheduleVote.getUser();
matchUser.club = findUserClubInMatch(match, scheduleVote.getUser());
matchUser.club = scheduleVote.getSchedule().getClub();
// matchUser.club = findUserClubInMatch(match, scheduleVote.getUser());
matchUser.score = 0;
matchUser.season = Statistic.getCurrentSeason();

return matchUser;
}
Expand All @@ -40,14 +47,17 @@ public void recordScore(MatchRecordInput matchRecordInput) {
this.score = matchRecordInput.getScore();
}

// 이거 뭐지
private static Club findUserClubInMatch(Match match, User user) {
for (UserClub userClub : user.getUserClub()) {
Club myClub = userClub.getClub();

if (myClub.equals(match.getHomeClub()) || myClub.equals(match.getAwayClub())) {
return myClub;
}
}

throw new RuntimeException("해당 유저는 매치에 참여한 클럽 소속이 아닙니다.");
// throw new RuntimeException("해당 유저는 매치에 참여한 클럽 소속이 아닙니다.");
throw new MatchControllerAdvice(ResponseCode.MATCH_USER_NOT_ATTENDANCE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.moim.match.entity.MatchUser;
import com.example.moim.match.repository.MatchRepository;
import com.example.moim.match.repository.MatchUserRepository;
import com.example.moim.statistic.service.StatisticService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -19,6 +20,7 @@
public class MatchAggregateService {
private final MatchRepository matchRepository;
private final MatchUserRepository matchUserRepository;
private final StatisticService statisticService;

//한시간마다 지금시간-48< 매치 끝나는시간 <지금시간이면 집계
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.HOURS)
Expand All @@ -43,6 +45,8 @@ public void aggregateMatchScore() {
}
log.info("Match ID: {}, homeScore: {}, awayScore: {}", match.getId(), homeScore, awayScore);
match.setMatchScore(homeScore, awayScore);

statisticService.updateStatistic(match);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.moim.statistic.controller;

import com.example.moim.global.exception.BaseResponse;
import com.example.moim.global.exception.ResponseCode;
import com.example.moim.statistic.dto.StatisticDTO;
import com.example.moim.statistic.service.StatisticService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
public class StatisticController {
private StatisticService statisticService;

// 전적 조회(전적 메인)
@GetMapping("/statistic/{clubId}")
public BaseResponse<StatisticDTO.StatisticOutPut> getStatistic(@PathVariable Long clubId,
@RequestBody StatisticDTO.StatisticInput request) {
return BaseResponse.onSuccess(statisticService.getStatistic(clubId, request.getTargetSeason(), request.getTargetType()), ResponseCode.OK);
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/example/moim/statistic/dto/StatisticDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.moim.statistic.dto;

import com.example.moim.statistic.entity.Statistic;
import lombok.AllArgsConstructor;
import lombok.Data;

public class StatisticDTO {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatisticDTO에 Request, Response 모아서 구현하신 이유가 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO들이 많아지면 관리하기가 힘들어진다고 생각이 들어서 이너 클래스로 관련된 DTO들을 모아서 구현했습니다!

@Data
@AllArgsConstructor
public static class StatisticInput {
private String targetSeason;
private String targetType;
}

@Data
@AllArgsConstructor
public static class StatisticOutPut {
private String season;
private String rank;
private int point;
private float winRate;
private int winCount;
private int defeatCount;
private int drawCount;
private String mvpName;
private int mvpGoalCount;

public StatisticOutPut(Statistic statistic) {
this.season = statistic.getSeason();
this.point = statistic.getPoint();
this.winRate = statistic.getWinRate();
this.winCount = statistic.getWinCount();
this.defeatCount = statistic.getDefeatCount();
this.drawCount = statistic.getDrawCount();
this.mvpName = statistic.getMvpName();
this.mvpGoalCount = statistic.getMvpScore();
this.rank = statistic.getTier().toString();
}
}

@Data
@AllArgsConstructor
public static class mvpDTO {
private String name;
private Long goalCount;
}
}
156 changes: 156 additions & 0 deletions src/main/java/com/example/moim/statistic/entity/Statistic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.example.moim.statistic.entity;

import com.example.moim.club.entity.Club;
import com.example.moim.club.entity.UserClub;
import com.example.moim.global.entity.BaseEntity;
import com.example.moim.global.enums.SportsType;
import jakarta.persistence.*;
import lombok.Getter;

import java.time.LocalDate;

import static com.example.moim.statistic.entity.Tier.ROOKIE;

@Entity
@Getter
public class Statistic extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "club_id")
private Club club;

private SportsType sportsType;
private String season; // -년 전반기 / 후반기
private Tier tier;
private int point;

private float winRate; // 소수점 한 자리 수까지 표기
private int winCount;
private int drawCount;
private int defeatCount;

private int winStreak;
private int defeatStreak;

private int mvpScore;
private String mvpName;

public static String getCurrentSeason() {
LocalDate now = LocalDate.now();
int year = now.getYear();
int month = now.getMonthValue();
String half = (month <= 6) ? "상반기" : "하반기";

return year + " " + half + "전적";
}

// 모임 만들때 기본적으로 하나 생성, 반기별로 하나씩 생성
public static Statistic createStatistic(Club club, SportsType sportsType) {
Statistic statistic = new Statistic();

statistic.club = club;
statistic.sportsType = sportsType;
statistic.season = Statistic.getCurrentSeason();
statistic.tier = ROOKIE;
statistic.point = 305;
statistic.winRate = 0f;
statistic.winCount = 0;
statistic.drawCount = 0;
statistic.defeatCount = 0;
statistic.winStreak = 0;
statistic.defeatStreak = 0;
statistic.mvpScore = 0;
statistic.mvpName = null;

return statistic;
}

public void updateStatistic(int homeScore, int awayScore, int opponentRank, String mvpName, int mvpScore) {
int currentMatches = this.winCount + this.drawCount + this.defeatCount; // 배치고사 단계: 3경기 미만, 이후 경기부터는 정식 랭크 적용
int pointsChange = 0;

int ourRankLevel = this.tier.getLevel();
int diff = opponentRank - ourRankLevel;

if (homeScore > awayScore) {
this.winCount++;
this.winStreak += this.winStreak + 1;
this.defeatStreak = 0;

if (this.tier == ROOKIE) {
pointsChange += 125;
} else {
pointsChange += 128;
// 3연승 이상이면 연승 보너스: 현재 연승 수 × 10점을 추가
if (this.winStreak >= 3) {
pointsChange += this.winStreak * 10;
}
// 랭크 차이 보너스: 우리 팀의 랭크와 상대팀의 랭크 차이에 따라 추가 점수를 부여
if (diff == 2) {
pointsChange += 100;
} else if (diff >= 3) {
pointsChange += 150;
}
}
} else if (homeScore < awayScore) {
this.defeatCount++;
this.defeatStreak += this.defeatStreak + 1;
this.winStreak = 0;

if (this.tier == ROOKIE) {
pointsChange -= 75;
} else {
// 기본 패배 페널티는 -72점
// 단, 연패 완화 로직: 3연패 시에는 -30점, 4연패 이상이면 페널티가 0점
if (this.defeatStreak == 3) {
pointsChange += -30;
} else if (this.defeatStreak >= 4) {
pointsChange += 0;
} else {
pointsChange += -72;
}
}
} else {
this.drawCount++;
this.winStreak = 0;
this.defeatStreak = 0;

if (this.tier == ROOKIE) {
pointsChange += 50;
} else {
pointsChange += 47;
}
}

this.point += pointsChange;

boolean isRookie = (currentMatches < 3);
if (!isRookie) {
if (this.point >= 1600) {
this.tier = Tier.M1;
} else if (this.point >= 1300) {
this.tier = Tier.M2;
} else if (this.point >= 875) {
this.tier = Tier.M3;
} else if (this.point >= 500) {
this.tier = Tier.M4;
} else {
this.tier = Tier.M5;
}
}

this.mvpName = mvpName;
this.mvpScore = mvpScore;

// 총 경기수 계산 후 승률 업데이트 (승률 = (승리수/총경기수)*100)
int totalMatches = this.winCount + this.drawCount + this.defeatCount;
this.winRate = totalMatches > 0 ? ((float) this.winCount / totalMatches) * 100 : 0;
}

public void updateMvp(UserClub userClub) {
this.mvpName = userClub.getUser().getName();
this.mvpScore = userClub.getScore();
}
}
Loading