Skip to content

Commit

Permalink
Feature/check attendace distance #387
Browse files Browse the repository at this point in the history
  • Loading branch information
hocaron authored Dec 18, 2023
2 parents 68bcee6 + b98bc99 commit 75dd05a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public enum ResultCode {
ATTENDANCE_ALREADY_CHECKED("이미 출석 체크를 했습니다."),
ATTENDANCE_TIME_BEFORE("아직 출석 체크 시간이 아닙니다."),
ATTENDANCE_TIME_OVER("출석 체크 시간이 지났습니다."),
ATTENDANCE_DISTANCE_OUT_OF_RANGE("유효한 출석체크 거리를 벗어났습니다."),

// Score HISTORY
SCORETYPE_INVALID_NAME("잘못된 점수 타입 이름입니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package kr.mashup.branding.domain.schedule;

import javax.persistence.Embeddable;

import lombok.Getter;

@Embeddable
@Getter
public class Location {
private Double latitude;
private Double longitude;

private static final double EARTH_RADIUS = 6371000; // 지구 반지름 (미터)

/**
* Haversine 공식을 사용하여 두 지점 간의 거리 계산
* <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine Formula - Wikipedia</a>
*
* @param targetLatitude 현재 위도
* @param targetLongitude 현재 경도
* @return 두 지점 간의 거리
*/
private double calculateDistance(double targetLatitude, double targetLongitude) {

double deltaLatitude = Math.toRadians(targetLatitude - latitude);
double deltaLongitude = Math.toRadians(targetLongitude - longitude);

double haversineA = Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) +
Math.cos(Math.toRadians(latitude)) * Math.cos(Math.toRadians(targetLatitude)) *
Math.sin(deltaLongitude / 2) * Math.sin(deltaLongitude / 2);

double centralAngle = 2 * Math.atan2(Math.sqrt(haversineA), Math.sqrt(1 - haversineA));

return EARTH_RADIUS * centralAngle;
}

/**
* 두 지점 간의 거리가 maxDistance 이하인지 확인하는 메서드
*
* @param targetLatitude 현재 위도
* @param targetLongitude 현재 경도
* @param maxDistance 두 지점 간의 최대 허용 거리
* @return 거리가 maxDistance 이하인 경우 true, 그렇지 않으면 false
*/
public boolean isWithinDistance(double targetLatitude, double targetLongitude, double maxDistance) {

double distance = calculateDistance(targetLatitude, targetLongitude);
return distance <= maxDistance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
Expand All @@ -15,14 +16,13 @@
import javax.persistence.OrderBy;
import javax.validation.constraints.NotNull;

import kr.mashup.branding.domain.ResultCode;
import kr.mashup.branding.domain.schedule.exception.ScheduleAlreadyPublishedException;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.util.Assert;

import kr.mashup.branding.domain.BaseEntity;
import kr.mashup.branding.domain.generation.Generation;
import kr.mashup.branding.domain.schedule.exception.ScheduleAlreadyPublishedException;
import kr.mashup.branding.util.DateRange;
import kr.mashup.branding.util.DateUtil;
import lombok.AccessLevel;
Expand Down Expand Up @@ -61,6 +61,9 @@ public class Schedule extends BaseEntity {
@Enumerated(EnumType.STRING)
private ScheduleStatus status;

@Embedded
private Location location;

@CreatedBy
private String createdBy;

Expand Down Expand Up @@ -146,4 +149,8 @@ public void changeIsCounted(Boolean isCounted) {
}

public Boolean isShowable() { return this.status == ScheduleStatus.PUBLIC; }

public Boolean isOnline() {
return this.location == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import kr.mashup.branding.service.attendance.AttendanceService;
import kr.mashup.branding.service.member.MemberService;
import kr.mashup.branding.service.schedule.ScheduleService;
import kr.mashup.branding.ui.attendance.request.AttendanceCheckRequest;
import kr.mashup.branding.ui.attendance.response.AttendanceCheckResponse;
import kr.mashup.branding.ui.attendance.response.AttendanceInfo;
import kr.mashup.branding.ui.attendance.response.PersonalAttendanceResponse;
Expand All @@ -51,6 +52,7 @@ public class AttendanceFacadeService {
private final static long PUSH_SCHEDULE_INTERVAL_MINUTES = 1;
private final static long ATTENDANCE_START_AFTER_MINUTES = 1;
private final static long ATTENDANCE_END_AFTER_MINUTES = 3;
private final static double ATTENDANCE_DISTANCE = 50;
private final AttendanceService attendanceService;
private final MemberService memberService;
private final ScheduleService scheduleService;
Expand All @@ -63,7 +65,7 @@ public class AttendanceFacadeService {
@Transactional
public AttendanceCheckResponse checkAttendance(
final Long memberId,
final String checkingCode
final AttendanceCheckRequest request
) {

final LocalDateTime checkTime = LocalDateTime.now();
Expand All @@ -72,13 +74,12 @@ public AttendanceCheckResponse checkAttendance(
final Event event;
final AttendanceCode attendanceCode;
try {
attendanceCode = attendanceCodeService.getByCodeOrThrow(checkingCode);
attendanceCode = attendanceCodeService.getByCodeOrThrow(request.getCheckingCode());
event = attendanceCode.getEvent();
} catch (NotFoundException e) {
throw new BadRequestException(ResultCode.ATTENDANCE_CODE_INVALID);
}


//멤버 최신 기수 확인
final int latestGenerationNumber = member.getMemberGenerations()
.stream()
Expand All @@ -93,8 +94,8 @@ public AttendanceCheckResponse checkAttendance(
throw new NotFoundException(ResultCode.EVENT_NOT_FOUND);
}


validAlreadyCheckAttendance(member, event);
validateAlreadyCheckAttendance(member, event);
validateWithinAttendanceDistance(request.getLatitude(), request.getLongitude(), event.getSchedule());

// 출석 체크
final Attendance attendance = attendanceService.checkAttendance(
Expand Down Expand Up @@ -178,7 +179,7 @@ private List<AttendanceCode> findAllEndsWithin(Long afterMinutes) {
/**
* 이미 출석 체크를 했는지 판별
*/
private void validAlreadyCheckAttendance(Member member, Event event) {
private void validateAlreadyCheckAttendance(Member member, Event event) {
final boolean isAlreadyCheck = attendanceService.isExist(member, event);
if (isAlreadyCheck) {
throw new BadRequestException(ResultCode.ATTENDANCE_ALREADY_CHECKED);
Expand Down Expand Up @@ -398,4 +399,19 @@ private List<AttendanceInfo> getAttendanceInfoByMember(
);
}).collect(Collectors.toList());
}

/**
* 현재 위치가 출석체크 가능한지 판별
*/
private void validateWithinAttendanceDistance(Double latitude, Double longitude, Schedule schedule) {

if (schedule.isOnline()) {
return;
}

final boolean isWithinAttendanceDistance = schedule.getLocation().isWithinDistance(latitude, longitude, ATTENDANCE_DISTANCE);
if (!isWithinAttendanceDistance) {
throw new BadRequestException(ResultCode.ATTENDANCE_DISTANCE_OUT_OF_RANGE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package kr.mashup.branding.ui.attendance;

import kr.mashup.branding.ui.attendance.request.AttendanceCheckRequest;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.ApiOperation;
import kr.mashup.branding.domain.member.Platform;
import kr.mashup.branding.facade.attendance.AttendanceFacadeService;
import kr.mashup.branding.security.MemberAuth;
import kr.mashup.branding.ui.ApiResponse;
import kr.mashup.branding.ui.attendance.request.AttendanceCheckRequest;
import kr.mashup.branding.ui.attendance.response.AttendanceCheckResponse;
import kr.mashup.branding.ui.attendance.response.PersonalAttendanceResponse;
import kr.mashup.branding.ui.attendance.response.PlatformAttendanceResponse;
Expand All @@ -32,18 +39,20 @@ public class AttendanceController {
"ATTENDANCE_TIME_OVER</br>" +
"ATTENDANCE_CODE_NOT_FOUND</br>" +
"ATTENDANCE_CODE_INVALID(등록된 코드와 다름)</br>" +
"ATTENDANCE_ALREADY_CHECKED" +
"ATTENDANCE_ALREADY_CHECKED</br>" +
"ATTENDANCE_DISTANCE_OUT_OF_RANGE" +
"</p>"

)
@PostMapping("/check")
public ApiResponse<AttendanceCheckResponse> check(
@ApiIgnore MemberAuth auth,
@RequestBody AttendanceCheckRequest req
@RequestBody AttendanceCheckRequest req,
@RequestHeader(value = "cipher", required = false) String cipher // for swagger, used in aop
) {

final AttendanceCheckResponse response
= attendanceFacadeService.checkAttendance(auth.getMemberId(), req.getCheckingCode());
= attendanceFacadeService.checkAttendance(auth.getMemberId(), req);

return ApiResponse.success(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
@Getter
public class AttendanceCheckRequest {
private String checkingCode;
private Double latitude;
private Double longitude;
}

0 comments on commit 75dd05a

Please sign in to comment.