Skip to content

Commit

Permalink
release: 0.2.1 (#141)
Browse files Browse the repository at this point in the history
* feat: 모임 삭제 기능 구현 (#102)

* refactor: 기획 변경에 따른 회원 카드 등록 API 리팩토링 (#105)

* refactor: sql 변경 (#104)

* refactor: User 클래스 필드명 변경 (#104)

* refactor: API 변경에 따른 코드 리팩토링 (#104)

* refactor: 불필요한 getId() 제거 ( 피드백 반영 ) (#104)

* feat: 유효성 검증 관련 에러 일괄 처리을 위한 메소드 추가 (#104)

* test: API 변경에 따른 통합 테스트 수정 및 단위 테스트 구현 (#104)

* refactor: UserMeGetResponse 필드 변경 (#104)

* fix: CI 에러 수정 (#104)

* refactor: 피드백 반영 (#104)

* fix: CI 에러 수정 (#104)

* refactor: JWT 에러 응답 리팩토링 및 인가 인증 예최 처리 코드 수정 (#103)

* refactor: 기타 코드 리팩토링( 피드백 반영 ) (#101)

* chore: JWT 관련 의존성 변경 (#101)

* refactor: JWT 및 인증 관련 로직 리팩토링 (#101)

* test: 테스트 코드 및 설정 관련 변경(#101)

* refactor: ObjectMapper Autowired 로 주입 (#101)

* refactor: AuthService 반환 타입 Optional<User> -> User 변경 (#101)

* fix: V6_create_users_interests.sql 추가 (#109) (#110)

* refactor: 회원 카드 등록시 JWT 정보도 함께 반환하도록 수정 (#114)

* chore : application.properties 에 jwt 관련 설정 값 추가 (#113)

* refactor : userService 회원 카드 등록 로직 수정 (#113)

* feat : UserRegisterResponse 필드 추가(#113)

* test: API 변경에 따른 테스트 관련 코드 수정 (#113)

* fix: 소셜 로그인 관련 500 에러 수정 및 OAuth 로직 일부 개선 (#112)

* fix: OAuthLoginController @RestController 어노테이션 추가(나는 바보..) 및 favicon 관련 임시 컨트롤러 생성 (#111)

* refactor : 설정 yml 리팩토링 (#111)

* refactor : SecurityConfig 리팩토링 (#111)

* refactor : 기타 OAuth 관련 로직 리팩토링(#111)

* refactor : Cors 허용 주소 임시 전부 허용 (#111)

* fix : SonarCloud 오류 수정 (#111)

* fix : SonarCloud 오류 수정 (#111)

* feat: 위치 기반 API 구현 및 테스트 (#108)

* refactor: 기타 코드 리팩토링( 피드백 반영 ) (#101)

* chore: JWT 관련 의존성 변경 (#101)

* refactor: JWT 및 인증 관련 로직 리팩토링 (#101)

* test: 테스트 코드 및 설정 관련 변경(#101)

* refactor: ObjectMapper Autowired 로 주입 (#101)

* refactor: AuthService 반환 타입 Optional<User> -> User 변경 (#101)

* refactor: application.properties redis.port 변경 (#91)

* refactor: 기존 Redis 설정 리팩토링 및 추가 구현 (#91)

* feat: 유저 위치 기반 관련 DTO 및 VO 구현 (#91)

* feat: 유저 위치 기반 API 구현 (#91)

* test: 유저 위치 기반 통합 테스트 관련 클래스 구현 및 테스트 (#91)

* test: redis port 변경에 따른 테스트 수정 (#91)

* fix: CI 에러 수정 (#91)

* fix: sonarCloud 에러 수정 (#91)

* refactor: 리뷰 반영 (#108)

* build: Gatling 세팅 (#115)

* feat: 부하테스트 툴 Gatling을 세팅한다

* feat: 예시 코드를 작성한다

* refactor: Sample 코드의 이름을 변경한다

* feat: 내 정보 조회에 Oauth 정보 추가 (#119)

* feat: 내 정보 조회에 Oauth 정보 추가

* test: Oauth 정보 추가에 따른 테스트 변경

* fix: 모임 목록 조회 조건에 활동 지역 조건이 안걸리는 버그 수정 (#121)

* feat: 회원 탈퇴 사유 저장 로직 추가 및 cors 관련 재설정 (#123)

* refactor: SecurityConfig Cors 관련 전체 허용으로 변경 및 회원 카드 등록 API permitAll 로 변경 (#117)

* refactor: 기존 엔티티 관련 리팩토링 (#117)

* feat: WithdrawReason 생성 sql 추가 (#117)

* feat: WithdrawReason 관련 도메인 구현 (#117)

* feat: 회원 탈퇴 시 탈퇴 사유 기능 추가 (#117)

* test: 회원 탈퇴 단위 테스트 (#117)

* refactor: 회원 탈퇴 API POST 로 수정 (#117)

* refactor: 회원탈퇴 관련 통합 테스트 수정 및 요청 DTO 수정 (#117)

* refactor: meeting 엔티티명 다시 복구 - 모든 테이블에 `s` 붙이는 방향 논의 필요 (#117)

* feat: withdraw_reason 테이블 이름 변경 (#117)

* fix: CI 에러 해결 (#117)

* fix: 모임 규격 변경 (#127)

* refactor: 코드 포매팅 적용 (#130)

* refactor: 유저조회, 자신조회, 유저들 조회 api응답에 친구 수 필드 추가 (#135)

* refactor: 유저조회, 자신조회, 유저들 조회 api에 친구 수 필드를 추가한다

* refactor: 자기자신 조회 api응답의 중복 oAuthType 필드를 삭제한다

* fix: JWT 재발급 오류 해결 (#138)

* fix: 토큰 재발급 에러 수정 (#132)

* fix: 토큰 재발급 에러 수정 (#132)

* test: 토큰 재발급 에러 수정에 따른 테스트 수정 (#132)

* test: SecurityConfig permitAll url 중복 제거 (#132)

* refactor: 틈틈 위치 기반 로직 수정 및 테스트  (#139)

* refactor: redis 관련 클래스 리팩토링 (#124)

* feat: 요청에 따른 100 m 이내 6명 조회 로직 구현 (#124)

* refactor: 위치 기반 DTO, VO 관련 리팩토링 (#124)

* refactor: 위치 기반 DTO, VO 관련 리팩토링 (#124)

* test: RedisRepository 리팩토링 및 추가 구현 (#124)

* test: 위치 기반 관련 테스트 수정 (#124)

* fix: sonarCloud 에러 수정 (#124)

---------

Co-authored-by: ddingmin <[email protected]>
Co-authored-by: xb205 <[email protected]>
Co-authored-by: devxb <[email protected]>
  • Loading branch information
4 people authored Jan 22, 2024
1 parent 738a370 commit b65d9a7
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 167 deletions.
3 changes: 1 addition & 2 deletions src/main/java/net/teumteum/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ private void checkRefreshTokenMatch(User user, String refreshToken) {


private TokenResponse issueNewToken(User user) {
return new TokenResponse(jwtService.createAccessToken(user.getOauth().getOauthId()),
jwtService.createRefreshToken());
return jwtService.createServiceToken(user);
}
}
7 changes: 3 additions & 4 deletions src/main/java/net/teumteum/core/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ public RedisConnectionFactory redisConnectionFactory() {
}

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
public RedisTemplate<String, String> redisTemplate() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import net.teumteum.core.property.JwtProperty;
import net.teumteum.user.domain.User;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

Expand Down Expand Up @@ -60,12 +59,14 @@ public String extractRefreshToken(HttpServletRequest request) {

public Long getUserIdFromToken(String token) {
try {
return Long.valueOf(getClaims(token).get("id", String.class));
} catch (Exception exception) {
throw new JwtException("Access Token is not valid");
Claims claims = getClaims(token);
return claims.get("id", Long.class);
} catch (ExpiredJwtException exception) {
return Long.valueOf(exception.getClaims().get("id").toString());
}
}


public TokenResponse createServiceToken(User users) {
String accessToken = createAccessToken(users.getId().toString());
String refreshToken = createRefreshToken();
Expand Down
52 changes: 47 additions & 5 deletions src/main/java/net/teumteum/core/security/service/RedisService.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
package net.teumteum.core.security.service;

import static java.util.Objects.requireNonNull;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.time.Duration;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import net.teumteum.teum_teum.domain.UserLocation;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RedisService {

private final RedisTemplate<String, Object> redisTemplate;

private static final String HASH_KEY = "userLocation";
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private ValueOperations<String, String> valueOperations;

@PostConstruct
void init() {
valueOperations = redisTemplate.opsForValue();
}

public String getData(String key) {
return (String) redisTemplate.opsForValue().get(key);
return valueOperations.get(key);
}

public void setData(String key, String value) {
redisTemplate.opsForValue().set(key, value);
valueOperations.set(key, value);
}

public void setDataWithExpiration(String key, String value, Long duration) {
Duration expireDuration = Duration.ofSeconds(duration);
redisTemplate.opsForValue().set(key, value, expireDuration);
valueOperations.set(key, value, expireDuration);
}

public void deleteData(String key) {
redisTemplate.delete(key);
valueOperations.getOperations().delete(key);
}

public void setUserLocation(UserLocation userLocation, Long duration) {
String key = HASH_KEY + userLocation.id();
String value;
try {
value = objectMapper.writeValueAsString(userLocation);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
valueOperations.set(key, value, duration);
}

public Set<UserLocation> getAllUserLocations() {
Set<String> keys = redisTemplate.keys(HASH_KEY + ":*");
return requireNonNull(keys).stream().map(key -> {
String value = valueOperations.get(key);
try {
return objectMapper.readValue(value, UserLocation.class);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}).collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,19 @@ public class TeumTeumController {
@ResponseStatus(HttpStatus.OK)
public UserAroundLocationsResponse getUserAroundLocations(
@Valid @RequestBody UserLocationRequest request) {
return teumTeumService.processingUserAroundLocations(request);
return teumTeumService.saveAndGetUserAroundLocations(request);
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException methodArgumentNotValidException) {
Sentry.captureException(methodArgumentNotValidException);

BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
List<ObjectError> errors = bindingResult.getAllErrors();

return ErrorResponse.of(errors.get(0).getDefaultMessage());
@ExceptionHandler({IllegalArgumentException.class, MethodArgumentNotValidException.class})
public ErrorResponse handleException(Exception exception) {
Sentry.captureException(exception);
if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
List<ObjectError> errors = bindingResult.getAllErrors();
return ErrorResponse.of(errors.get(0).getDefaultMessage());
} else {
return ErrorResponse.of(exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package net.teumteum.teum_teum.domain;


public record UserData(
public record UserLocation(
Long id,
Double latitude,
Double longitude,
String name,
String jobDetailClass,
Long characterId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import net.teumteum.teum_teum.domain.UserData;
import net.teumteum.teum_teum.domain.UserLocation;

public record UserLocationRequest(
@NotNull(message = "경도는 필수 입력값입니다.")
Double longitude,
@NotNull(message = "위도는 필수 입력값입니다.")
Double latitude,
@NotNull(message = "id 는 필수 입력값입니다.")
Long id,
@NotNull(message = "위도는 필수 입력값입니다.")
Double latitude,
@NotNull(message = "경도는 필수 입력값입니다.")
Double longitude,
@NotBlank(message = "이름은 필수 입력값입니다.")
String name,
@NotBlank(message = "직무는 필수 입력값입니다.")
Expand All @@ -19,7 +19,7 @@ public record UserLocationRequest(
Long characterId
) {

public UserData toUserData() {
return new UserData(id, name, jobDetailClass, characterId);
public UserLocation toUserLocation() {
return new UserLocation(id, latitude, longitude, name, jobDetailClass, characterId);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package net.teumteum.teum_teum.domain.response;

import java.util.List;
import net.teumteum.teum_teum.domain.UserData;
import net.teumteum.teum_teum.domain.UserLocation;

public record UserAroundLocationsResponse(
List<UserAroundLocationResponse> userLocations
List<UserAroundLocationResponse> aroundUserLocations
) {

public static UserAroundLocationsResponse of(List<UserData> userData) {
public static UserAroundLocationsResponse of(List<UserLocation> userData) {
return new UserAroundLocationsResponse(
userData.stream()
.map(UserAroundLocationResponse::of)
Expand All @@ -23,13 +23,13 @@ public record UserAroundLocationResponse(
) {

public static UserAroundLocationResponse of(
UserData userData
UserLocation userLocation
) {
return new UserAroundLocationResponse(
userData.id(),
userData.name(),
userData.jobDetailClass(),
userData.characterId()
userLocation.id(),
userLocation.name(),
userLocation.jobDetailClass(),
userLocation.characterId()
);
}
}
Expand Down
110 changes: 33 additions & 77 deletions src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java
Original file line number Diff line number Diff line change
@@ -1,103 +1,59 @@
package net.teumteum.teum_teum.service;

import static java.lang.System.currentTimeMillis;
import static java.time.Duration.ofMinutes;
import static java.lang.Math.atan2;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static java.lang.Math.toRadians;
import static java.util.Comparator.comparingDouble;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.teumteum.teum_teum.domain.UserData;
import net.teumteum.core.security.service.RedisService;
import net.teumteum.teum_teum.domain.UserLocation;
import net.teumteum.teum_teum.domain.request.UserLocationRequest;
import net.teumteum.teum_teum.domain.response.UserAroundLocationsResponse;
import net.teumteum.teum_teum.domain.response.UserAroundLocationsResponse.UserAroundLocationResponse;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.domain.geo.Metrics;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class TeumTeumService {

private static final String KEY = "userLocation";
private static final int SEARCH_LIMIT = 6;
private static final Duration LOCATION_EXPIRATION = ofMinutes(1);

private final ObjectMapper objectMapper;
private final RedisService redisService;

private final RedisTemplate<String, Object> redisTemplate;

public UserAroundLocationsResponse processingUserAroundLocations(UserLocationRequest request) {
GeoOperations<String, Object> geoValueOperations = redisTemplate.opsForGeo();

String userDataJson = null;
try {
userDataJson = objectMapper.writeValueAsString(
request.toUserData()) + ":" + currentTimeMillis();
} catch (JsonProcessingException e) {
log.error("JsonProcessingException Occurred!");
}

geoValueOperations.add(KEY, new Point(request.longitude(), request.latitude()), userDataJson);

return getUserAroundLocations(geoValueOperations, request.longitude(), request.latitude());
public UserAroundLocationsResponse saveAndGetUserAroundLocations(UserLocationRequest request) {
redisService.setUserLocation(request.toUserLocation(), 60L);
return getUserAroundLocations(request);
}

private UserAroundLocationsResponse getUserAroundLocations(GeoOperations<String, Object> geoValueOperations,
Double longitude, Double latitude) {
private UserAroundLocationsResponse getUserAroundLocations(UserLocationRequest request) {
Set<UserLocation> allUserLocations = redisService.getAllUserLocations();

GeoResults<GeoLocation<Object>> geoResults
= geoValueOperations.radius(KEY,
new Circle(new Point(longitude, latitude), new Distance(100, Metrics.METERS)));
List<UserLocation> aroundUserLocations = allUserLocations.stream()
.filter(userLocation -> !userLocation.id().equals(request.id()))
.filter(userLocation -> calculateDistance(request.latitude(), request.longitude(),
userLocation.latitude(), userLocation.longitude()) <= 100)
.sorted(comparingDouble(userLocation
-> calculateDistance(request.latitude(), request.longitude(),
userLocation.latitude(), userLocation.longitude()))
).limit(SEARCH_LIMIT)
.toList();

return getUserAroundLocationsResponse(geoResults);
return UserAroundLocationsResponse.of(aroundUserLocations);
}

private UserAroundLocationsResponse getUserAroundLocationsResponse(GeoResults<GeoLocation<Object>> geoResults) {

List<UserAroundLocationResponse> userAroundLocationResponses = new ArrayList<>();

long currentTime = currentTimeMillis();
int count = 0;

for (GeoResult<GeoLocation<Object>> geoResult : Objects.requireNonNull(geoResults)) {
String userSavedTime = String.valueOf(geoResult.getContent().getName()).split(":")[5];
long timestamp = Long.parseLong(userSavedTime);

if (currentTime - timestamp < LOCATION_EXPIRATION.toMillis()) {
String savedUserLocation = String.valueOf(geoResult.getContent().getName());
String userDataJson = savedUserLocation.substring(savedUserLocation.lastIndexOf(":") + 1);

UserData userData = null;
try {
userData = objectMapper.readValue(userDataJson, UserData.class);
} catch (JsonProcessingException e) {
log.error("JsonProcessingException Occurred!");
}

UserAroundLocationResponse userAroundLocationResponse
= UserAroundLocationResponse.of(Objects.requireNonNull(userData));

userAroundLocationResponses.add(userAroundLocationResponse);
count++;

if (count >= SEARCH_LIMIT) {
break;
}
}
}
return new UserAroundLocationsResponse(userAroundLocationResponses);
private double calculateDistance(double latitude1, double longitude1, double latitude2, double longitude2) {
final int earthRadius = 6371;
double latDistance = toRadians(latitude2 - latitude1);
double lonDistance = toRadians(longitude2 - longitude1);
double a = sin(latDistance / 2) * sin(latDistance / 2)
+ Math.cos(toRadians(latitude1)) * Math.cos(toRadians(latitude2))
* sin(lonDistance / 2) * sin(lonDistance / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c * 1000;
}
}
Loading

0 comments on commit b65d9a7

Please sign in to comment.