diff --git a/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/Device.java b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/Device.java new file mode 100644 index 00000000..c0562d82 --- /dev/null +++ b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/Device.java @@ -0,0 +1,36 @@ +package com.whoz_in.api_query_jpa.device; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import java.util.List; +import java.util.UUID; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Subselect; + +@Entity +@Getter +@Subselect("SELECT d.id , d.member_id " + + "FROM device_entity d") +@Immutable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Device { + + @Id + @Column(name = "id", nullable = false) + private UUID id; + + @Column(name ="member_id", nullable = false) + private UUID memberId; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "device_id", referencedColumnName = "id") + private List deviceInfos; + +} \ No newline at end of file diff --git a/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfo.java b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfo.java new file mode 100644 index 00000000..67583b8a --- /dev/null +++ b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfo.java @@ -0,0 +1,28 @@ +package com.whoz_in.api_query_jpa.device; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.util.UUID; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Subselect; + +@Entity +@Getter +@Subselect("SELECT di.id, di.device_id, di.mac, di.ssid " + + "FROM device_info_entity di") +@Immutable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DeviceInfo { + + @Id + private Long id; + + private UUID deviceId; + + private String mac; + + private String ssid; +} \ No newline at end of file diff --git a/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoJpaViewer.java b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoJpaViewer.java new file mode 100644 index 00000000..bc593da1 --- /dev/null +++ b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoJpaViewer.java @@ -0,0 +1,21 @@ +package com.whoz_in.api_query_jpa.device; + +import com.whoz_in.main_api.query.device.view.DeviceInfoViewer; +import com.whoz_in.main_api.query.device.view.RegisteredSsids; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +@Component +@RequiredArgsConstructor +public class DeviceInfoJpaViewer implements DeviceInfoViewer { + private final DeviceInfoRepository deviceInfoRepository; + @Override + public RegisteredSsids findRegisteredSsids(UUID ownerId, String room, String mac) { + return new RegisteredSsids( + deviceInfoRepository.findAllByMac(ownerId, room, mac).stream() + .map(DeviceInfo::getSsid) + .toList()); + } +} diff --git a/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoRepository.java b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoRepository.java new file mode 100644 index 00000000..2f2c4306 --- /dev/null +++ b/modules/infrastructure/api-query-jpa/src/main/java/com/whoz_in/api_query_jpa/device/DeviceInfoRepository.java @@ -0,0 +1,18 @@ +package com.whoz_in.api_query_jpa.device; + +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface DeviceInfoRepository extends JpaRepository { + @Query(value = "SELECT di.id, di.device_id AS deviceId, di.mac, di.ssid " + + "FROM device_entity d " + + "JOIN device_info_entity di ON d.id = di.device_id " + + "WHERE d.member_id = :ownerId " + + "AND di.room = :room " + + "AND di.mac = :mac", + nativeQuery = true) + List findAllByMac(@Param("ownerId") UUID ownerId, @Param("room") String room, @Param("mac") String mac); +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoAddHandler.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoAddHandler.java index bc34afe5..e96bcd60 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoAddHandler.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoAddHandler.java @@ -1,8 +1,6 @@ package com.whoz_in.main_api.command.device.application; import com.whoz_in.domain.device.DeviceRepository; -import com.whoz_in.domain.device.model.DeviceInfo; -import com.whoz_in.domain.device.model.MacAddress; import com.whoz_in.domain.device.service.DeviceOwnershipService; import com.whoz_in.domain.member.model.MemberId; import com.whoz_in.domain.member.service.MemberFinderService; @@ -11,6 +9,8 @@ import com.whoz_in.domain.network_log.MonitorLogRepository; import com.whoz_in.main_api.command.shared.application.CommandHandler; import com.whoz_in.main_api.shared.application.Handler; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfo; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfoStore; import com.whoz_in.main_api.shared.utils.RequesterInfo; import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; @@ -20,7 +20,7 @@ @RequiredArgsConstructor public class DeviceInfoAddHandler implements CommandHandler { private final RequesterInfo requesterInfo; - private final DeviceInfoStore deviceInfoStore; + private final TempDeviceInfoStore tempDeviceInfoStore; private final MemberFinderService memberFinderService; private final DeviceOwnershipService deviceOwnershipService; private final DeviceRepository deviceRepository; @@ -41,9 +41,9 @@ public Void handle(DeviceInfoAdd req) { String mac = managedLog.getMac(); //등록할 DeviceInfo 생성 - DeviceInfo deviceInfo = DeviceInfo.create(req.room(), managedLog.getSsid(), MacAddress.create(mac)); + TempDeviceInfo deviceInfo = new TempDeviceInfo(req.room(), managedLog.getSsid(), mac); //이미 등록된 DeviceInfo가 아닌지 미리 확인 - deviceInfoStore.mustNotExist(requesterId, deviceInfo); + tempDeviceInfoStore.mustNotExist(requesterId.id(), deviceInfo); //모니터 로그에서 현재 접속 중인 맥이 있는지 확인 (넉넉하게 15분) monitorLogRepository.mustExistAfter(mac, LocalDateTime.now().minusMinutes(15)); @@ -56,7 +56,7 @@ public Void handle(DeviceInfoAdd req) { }); //마침내! DeviceInfo를 추가한다. - deviceInfoStore.add(requesterId, deviceInfo); + tempDeviceInfoStore.add(requesterId.id(), deviceInfo); return null; } diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceRegisterHandler.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceRegisterHandler.java index b6a6533f..61faa5d8 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceRegisterHandler.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceRegisterHandler.java @@ -3,11 +3,13 @@ import com.whoz_in.domain.device.DeviceRepository; import com.whoz_in.domain.device.model.Device; import com.whoz_in.domain.device.model.DeviceInfo; +import com.whoz_in.domain.device.model.MacAddress; import com.whoz_in.domain.member.model.MemberId; import com.whoz_in.domain.member.service.MemberFinderService; import com.whoz_in.domain.shared.event.EventBus; import com.whoz_in.main_api.command.shared.application.CommandHandler; import com.whoz_in.main_api.shared.application.Handler; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfoStore; import com.whoz_in.main_api.shared.utils.RequesterInfo; import java.util.List; import lombok.RequiredArgsConstructor; @@ -17,7 +19,7 @@ @RequiredArgsConstructor public class DeviceRegisterHandler implements CommandHandler { private final RequesterInfo requesterInfo; - private final DeviceInfoStore deviceInfoStore; + private final TempDeviceInfoStore tempDeviceInfoStore; private final MemberFinderService memberFinderService; private final DeviceRepository deviceRepository; private final EventBus eventBus; @@ -28,9 +30,12 @@ public Void handle(DeviceRegister cmd) { MemberId requesterId = requesterInfo.getMemberId(); memberFinderService.mustExist(requesterId); - deviceInfoStore.verifyAllAdded(requesterId, cmd.room()); + tempDeviceInfoStore.verifyAllAdded(requesterId.id(), cmd.room()); - List deviceInfos = deviceInfoStore.takeout(requesterId); + List deviceInfos = tempDeviceInfoStore.takeout(requesterId.id()) + .stream() + .map(cdi-> DeviceInfo.create(cdi.getRoom(), cdi.getSsid(), MacAddress.create(cdi.getMac()))) + .toList(); Device device = Device.create(requesterId, deviceInfos, cmd.deviceName()); deviceRepository.save(device); diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/presentation/DeviceController.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/presentation/DeviceController.java index 004484a7..97cdd6ac 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/presentation/DeviceController.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/presentation/DeviceController.java @@ -4,7 +4,9 @@ import com.whoz_in.main_api.command.device.application.DeviceRegister; import com.whoz_in.main_api.command.shared.application.CommandBus; import com.whoz_in.main_api.command.shared.presentation.CommandController; +import com.whoz_in.main_api.shared.presentation.ResponseEntityGenerator; import com.whoz_in.main_api.shared.presentation.SuccessBody; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -22,12 +24,12 @@ public DeviceController(CommandBus commandBus) { @PostMapping("/device/info") public ResponseEntity> addDeviceInfo(@RequestBody DeviceInfoAdd request) { dispatch(request); - return null; + return ResponseEntityGenerator.success("기기 정보 등록 완료", HttpStatus.CREATED); } @PostMapping("/device") public ResponseEntity> registerDevice(@RequestBody DeviceRegister request) { dispatch(request); - return null; + return ResponseEntityGenerator.success("기기 등록 완료", HttpStatus.CREATED); } } diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpController.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpController.java new file mode 100644 index 00000000..d8153af7 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpController.java @@ -0,0 +1,22 @@ +package com.whoz_in.main_api.command.private_ip; + +import com.whoz_in.main_api.command.shared.application.CommandBus; +import com.whoz_in.main_api.command.shared.presentation.CommandController; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1") +public class PrivateIpController extends CommandController { + + public PrivateIpController(CommandBus commandBus) { + super(commandBus); + } + + @PutMapping("/private-ip") + public void updatePrivateIp(@RequestBody PrivateIpUpdate req){ + dispatch(req); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdate.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdate.java new file mode 100644 index 00000000..78e7ea64 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdate.java @@ -0,0 +1,15 @@ +package com.whoz_in.main_api.command.private_ip; + +import com.whoz_in.domain.device.model.IpAddress; +import com.whoz_in.main_api.command.shared.application.Command; +import java.util.Map; + +public record PrivateIpUpdate( + String room, + Map privateIpList //Map<와이파이이름, 아이피> +) implements Command { + + public PrivateIpUpdate { + privateIpList.values().forEach(IpAddress::create); //아이피 형식 검증 + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdateHandler.java b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdateHandler.java new file mode 100644 index 00000000..4f30dd3f --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/command/private_ip/PrivateIpUpdateHandler.java @@ -0,0 +1,18 @@ +package com.whoz_in.main_api.command.private_ip; + +import com.whoz_in.main_api.command.shared.application.CommandHandler; +import com.whoz_in.main_api.shared.application.Handler; +import com.whoz_in.main_api.shared.caching.private_ip.PrivateIpStore; +import lombok.RequiredArgsConstructor; + +@Handler +@RequiredArgsConstructor +public class PrivateIpUpdateHandler implements CommandHandler { + private final PrivateIpStore privateIpStore; + + @Override + public Void handle(PrivateIpUpdate command) { + privateIpStore.put(command.room(), command.privateIpList()); + return null; + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityConfig.java b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityConfig.java index a8f2dc1c..d2d9c38b 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityConfig.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityConfig.java @@ -25,6 +25,15 @@ public FilterRegistrationBean jwtAuthenticationFilterRe return registrationBean; } + //위와 동일한 이유로 추가 + @Bean + public FilterRegistrationBean serverAuthenticationFilterRegistration( + ServerAuthenticationFilter serverAuthenticationFilter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(serverAuthenticationFilter); + registrationBean.setEnabled(false); + return registrationBean; + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityFilterChainConfig.java b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityFilterChainConfig.java index 65a5a2b8..9c4aa98b 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityFilterChainConfig.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SecurityFilterChainConfig.java @@ -23,9 +23,25 @@ public class SecurityFilterChainConfig { private final ClientRegistrationRepository clientRegistrationRepository; private final LoginSuccessHandler loginSuccessHandler; private final LoginFailureHandler loginFailureHandler; + private final ServerAuthenticationFilter serverAuthenticationFilter; private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CorsConfigurationSource corsConfigurationSource; + @Bean + @Order(0) + public SecurityFilterChain serverToServerFilterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity.securityMatcher( + "/api/v1/private-ip" + ); + + commonConfigurations(httpSecurity); + httpSecurity.logout(AbstractHttpConfigurer::disable); + //TODO: ip 화이트 리스트 + httpSecurity.addFilterAt(serverAuthenticationFilter, LogoutFilter.class); + + return httpSecurity.build(); + } + @Bean @Order(1) public SecurityFilterChain oauth2FilterChain(HttpSecurity httpSecurity) throws Exception { @@ -50,7 +66,7 @@ public SecurityFilterChain oauth2FilterChain(HttpSecurity httpSecurity) throws E return httpSecurity.build(); } - //POST, PUT, PATCH, DELETE 중 인증인가 필요 없는 엔드포인트 + //인증인가 필요 없는 엔드포인트 @Bean @Order(2) public SecurityFilterChain noAuthenticationFilterChain(HttpSecurity httpSecurity) throws Exception { @@ -77,6 +93,10 @@ public SecurityFilterChain authenticationFilterChain(HttpSecurity httpSecurity) ); httpSecurity.authorizeHttpRequests(auth-> { //인증 필요 + auth.requestMatchers(HttpMethod.GET, + "/api/v1/device/info-status", + "/api/v1/private-ip/*" + ).authenticated(); auth.requestMatchers(HttpMethod.POST, "/api/v1/device", "/api/v1/device/info" diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/ServerAuthenticationFilter.java b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/ServerAuthenticationFilter.java new file mode 100644 index 00000000..131059f2 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/ServerAuthenticationFilter.java @@ -0,0 +1,37 @@ +package com.whoz_in.main_api.config.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class ServerAuthenticationFilter extends OncePerRequestFilter { + private final String apiKey; + + public ServerAuthenticationFilter(@Value("${api-key}") String apiKey) { + this.apiKey = apiKey; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + Optional extractedKey = extractApiKey(request); + + if (extractedKey.filter(apiKey::equals).isEmpty()) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key"); + return; + } + + filterChain.doFilter(request, response); + } + + private Optional extractApiKey(HttpServletRequest request){ + return Optional.ofNullable(request.getHeader("Authorization")); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/utils/SpringSecurityRequesterInfo.java b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SpringSecurityRequesterInfo.java similarity index 87% rename from modules/main-api/src/main/java/com/whoz_in/main_api/shared/utils/SpringSecurityRequesterInfo.java rename to modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SpringSecurityRequesterInfo.java index 55762f3d..df159c33 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/utils/SpringSecurityRequesterInfo.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/config/security/SpringSecurityRequesterInfo.java @@ -1,7 +1,7 @@ -package com.whoz_in.main_api.shared.utils; +package com.whoz_in.main_api.config.security; import com.whoz_in.domain.member.model.MemberId; -import com.whoz_in.main_api.config.security.JwtAuthentication; +import com.whoz_in.main_api.shared.utils.RequesterInfo; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatus.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatus.java new file mode 100644 index 00000000..e4533cc9 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatus.java @@ -0,0 +1,20 @@ +package com.whoz_in.main_api.query.device.application; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.whoz_in.main_api.query.shared.application.Response; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfoStore; +import java.util.Map; + + +/** + * 이 클래스는 사용자가 등록해야 할 DeviceInfo들의 상태를 가지고 있다.
+ * + * {@code isAddedPerSsid} 맵은 각 SSID의 임시 등록 여부를 나타낸다.
+ * - Key는 사용자가 아직 등록하지 않은 SSID를 의미한다. (db에 없다는 말이다.)
+ * - Value가 {@code true}인 경우, 해당 SSID는 + * {@link TempDeviceInfoStore}에 이미 등록된 것이다.
+ * 모두 true라면 기기 등록을 완료할 수 있다.
+ */ +public record TempDeviceInfosStatus( + @JsonProperty("status") Map isAddedPerSsid +) implements Response {} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusGet.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusGet.java new file mode 100644 index 00000000..bcbcf88e --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusGet.java @@ -0,0 +1,8 @@ +package com.whoz_in.main_api.query.device.application; + +import com.whoz_in.main_api.query.shared.application.Query; + +public record TempDeviceInfosStatusGet( + String room, + String ip +) implements Query {} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusHandler.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusHandler.java new file mode 100644 index 00000000..9047b59a --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/application/TempDeviceInfosStatusHandler.java @@ -0,0 +1,53 @@ +package com.whoz_in.main_api.query.device.application; + +import com.whoz_in.domain.network_log.ManagedLog; +import com.whoz_in.domain.network_log.ManagedLogRepository; +import com.whoz_in.main_api.config.RoomSsidConfig; +import com.whoz_in.main_api.query.device.view.DeviceInfoViewer; +import com.whoz_in.main_api.query.shared.application.QueryHandler; +import com.whoz_in.main_api.shared.application.Handler; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfo; +import com.whoz_in.main_api.shared.caching.device.TempDeviceInfoStore; +import com.whoz_in.main_api.shared.utils.RequesterInfo; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; + +@Handler +@RequiredArgsConstructor +public class TempDeviceInfosStatusHandler implements QueryHandler { + private final RoomSsidConfig ssidConfig; + private final RequesterInfo requesterInfo; + private final DeviceInfoViewer deviceInfoViewer; + private final TempDeviceInfoStore tempDeviceInfoStore; + private final ManagedLogRepository managedLogRepository; + + @Override + public TempDeviceInfosStatus handle(TempDeviceInfosStatusGet query) { + UUID requesterId = requesterInfo.getMemberId().id(); + + // 사용자의 기기를 맥으로 찾기 위해 로그를 가져옴 + ManagedLog log = managedLogRepository.getLatestByRoomAndIpAfter( + query.room(), query.ip(), LocalDateTime.now().minusDays(1)); //이거 managed log가 판단해야 함 + // 방에 존재하는 와이파이들 + List roomSsids = ssidConfig.getSsids(query.room()); + // 사용자가 이전에 등록한 기기 정보를 가져옴 + List registeredSsids = deviceInfoViewer + .findRegisteredSsids(requesterId, query.room(), log.getMac()) + .ssids(); + // 임시로 등록한 기기 정보를 가져옴 + List addedTempDeviceInfos = tempDeviceInfoStore.get(requesterId); + // 방에 존재하는 와이파이 중 이전에 등록한 와이파이를 제외하고 임시로 등록되었는지를 체크함 + Map isAddedPerSsid = roomSsids.stream() + .filter(ssid -> !registeredSsids.contains(ssid)) //등록되지 않은 ssid만 남김 + .collect(Collectors.toMap( + ssid -> ssid, + ssid -> addedTempDeviceInfos.stream() + .anyMatch(tdi -> tdi.getSsid().equals(ssid)) + )); + return new TempDeviceInfosStatus(isAddedPerSsid); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/presentation/DeviceQueryController.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/presentation/DeviceQueryController.java new file mode 100644 index 00000000..927fa0f3 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/presentation/DeviceQueryController.java @@ -0,0 +1,30 @@ +package com.whoz_in.main_api.query.device.presentation; + +import com.whoz_in.main_api.query.device.application.TempDeviceInfosStatus; +import com.whoz_in.main_api.query.device.application.TempDeviceInfosStatusGet; +import com.whoz_in.main_api.query.shared.application.QueryBus; +import com.whoz_in.main_api.query.shared.presentation.QueryController; +import com.whoz_in.main_api.shared.presentation.CrudResponseCode; +import com.whoz_in.main_api.shared.presentation.ResponseEntityGenerator; +import com.whoz_in.main_api.shared.presentation.SuccessBody; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1") +public class DeviceQueryController extends QueryController { + + public DeviceQueryController(QueryBus queryBus) { + super(queryBus); + } + + @GetMapping("/device/info-status") + public ResponseEntity> getTempDeviceInfosStatus(@RequestParam String room, @RequestParam String ip){ + return ResponseEntityGenerator.success( + ask(new TempDeviceInfosStatusGet(room, ip)), + CrudResponseCode.READ); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/DeviceInfoViewer.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/DeviceInfoViewer.java new file mode 100644 index 00000000..e3fc4eae --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/DeviceInfoViewer.java @@ -0,0 +1,8 @@ +package com.whoz_in.main_api.query.device.view; + +import com.whoz_in.main_api.query.shared.application.Viewer; +import java.util.UUID; + +public interface DeviceInfoViewer extends Viewer { + RegisteredSsids findRegisteredSsids(UUID ownerId, String room, String mac); +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/RegisteredSsids.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/RegisteredSsids.java new file mode 100644 index 00000000..7e58aa7d --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/device/view/RegisteredSsids.java @@ -0,0 +1,8 @@ +package com.whoz_in.main_api.query.device.view; + +import com.whoz_in.main_api.query.shared.application.View; +import java.util.List; + +public record RegisteredSsids( + List ssids +) implements View {} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpList.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpList.java new file mode 100644 index 00000000..3ecf46db --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpList.java @@ -0,0 +1,8 @@ +package com.whoz_in.main_api.query.private_ip; + +import com.whoz_in.main_api.query.shared.application.Response; +import java.util.List; + +public record PrivateIpList( + List ipList +) implements Response {} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGet.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGet.java new file mode 100644 index 00000000..44e441d3 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGet.java @@ -0,0 +1,7 @@ +package com.whoz_in.main_api.query.private_ip; + +import com.whoz_in.main_api.query.shared.application.Query; + +public record PrivateIpListGet( + String room +) implements Query {} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGetHandler.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGetHandler.java new file mode 100644 index 00000000..f80f1094 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpListGetHandler.java @@ -0,0 +1,17 @@ +package com.whoz_in.main_api.query.private_ip; + +import com.whoz_in.main_api.query.shared.application.QueryHandler; +import com.whoz_in.main_api.shared.application.Handler; +import com.whoz_in.main_api.shared.caching.private_ip.PrivateIpStore; +import lombok.RequiredArgsConstructor; + +@Handler +@RequiredArgsConstructor +public class PrivateIpListGetHandler implements QueryHandler { + private final PrivateIpStore privateIpStore; + @Override + public PrivateIpList handle(PrivateIpListGet query) { + return new PrivateIpList(privateIpStore.get(query.room()).values().stream() + .toList()); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpQueryController.java b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpQueryController.java new file mode 100644 index 00000000..164a5404 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/query/private_ip/PrivateIpQueryController.java @@ -0,0 +1,25 @@ +package com.whoz_in.main_api.query.private_ip; + +import com.whoz_in.main_api.query.shared.application.QueryBus; +import com.whoz_in.main_api.query.shared.presentation.QueryController; +import com.whoz_in.main_api.shared.presentation.CrudResponseCode; +import com.whoz_in.main_api.shared.presentation.ResponseEntityGenerator; +import com.whoz_in.main_api.shared.presentation.SuccessBody; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1") +public class PrivateIpQueryController extends QueryController { + public PrivateIpQueryController(QueryBus queryBus) { + super(queryBus); + } + + @GetMapping("/private-ip/{room}") + public ResponseEntity> getPrivateIps(@PathVariable String room){ + return ResponseEntityGenerator.success(ask(new PrivateIpListGet(room)), CrudResponseCode.READ); + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfo.java b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfo.java new file mode 100644 index 00000000..0f10cc05 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfo.java @@ -0,0 +1,15 @@ +package com.whoz_in.main_api.shared.caching.device; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@EqualsAndHashCode +@RequiredArgsConstructor +public final class TempDeviceInfo { + private final String room; + private final String ssid; + @EqualsAndHashCode.Exclude + private final String mac; +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoStore.java b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfoStore.java similarity index 53% rename from modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoStore.java rename to modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfoStore.java index 4651e0e6..8f054fe5 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/command/device/application/DeviceInfoStore.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/device/TempDeviceInfoStore.java @@ -1,11 +1,10 @@ -package com.whoz_in.main_api.command.device.application; +package com.whoz_in.main_api.shared.caching.device; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.whoz_in.domain.device.model.DeviceInfo; -import com.whoz_in.domain.member.model.MemberId; import com.whoz_in.main_api.config.RoomSsidConfig; import java.util.List; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -15,30 +14,31 @@ /** * 사전 지식: 사용자는 기기 등록을 위해선 해당 방에 존재하는 와이파이에 대한 맥을(DeviceInfo) 가지고 있어야 하며, 모두 가지면 기기 등록을 시작할 수 있다. - * 따라서 이 클래스는 기기 등록 전까지 맥을 임시 저장 및 관리하는 책임을 가진다. - * (연결 시마다 맥이 바뀌도록 설정한 기기도 있기 때문에 시간 제한을 뒀다.) + * 책임: 따라서 이 클래스는 기기 등록 전까지 맥을 임시 저장 및 관리하는 책임을 가진다. + * 주의: command, query side에서 공용으로 사용하는 것이므로 어느 한 쪽에 의존하는 클래스를 사용하지 않도록 한다. + * 참고: 연결 시마다 맥이 바뀌도록 설정한 기기도 있기 때문에 시간 제한을 뒀다. */ @Component @RequiredArgsConstructor -public final class DeviceInfoStore { - private static final Cache> store = CacheBuilder.newBuilder() +public final class TempDeviceInfoStore { + private static final Cache> store = CacheBuilder.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) // 5분 동안 접근이 없으면 만료 .build(); private final RoomSsidConfig ssidConfig; - //이전에 등록되지 않은 DeviceInfo인지 검증 - public void mustNotExist(MemberId ownerId, DeviceInfo deviceInfo){ - List deviceInfos = store.getIfPresent(ownerId); - if (deviceInfos != null && deviceInfos.stream().anyMatch(di->isInSameRoomAndWifi(di, deviceInfo))) + //이전에 등록되지 않은 TempDeviceInfo인지 검증 + public void mustNotExist(UUID ownerId, TempDeviceInfo deviceInfo){ + List deviceInfos = store.getIfPresent(ownerId); + if (deviceInfos != null && deviceInfos.stream().anyMatch(di->di.equals(deviceInfo))) throw new IllegalArgumentException("이미 등록됨"); } //TODO: deviceInfo들에 room이 있는데 room을 굳이 받아야 하나 - //room의 모든 와이파이에 대해 DeviceInfo가 추가되었는지 검증 - public void verifyAllAdded(MemberId ownerId, String room){ - List deviceInfos = get(ownerId); + //room의 모든 와이파이에 대해 CachedDevice가 추가되었는지 검증 + public void verifyAllAdded(UUID ownerId, String room){ + List deviceInfos = get(ownerId); List unregisteredSsids = ssidConfig.getSsids(room).stream() .filter(ssid -> deviceInfos.stream().noneMatch(di -> di.getSsid().equals(ssid))) .toList(); @@ -48,10 +48,10 @@ public void verifyAllAdded(MemberId ownerId, String room){ } //DeviceInfo 추가 - public void add(MemberId ownerId, DeviceInfo newDeviceInfo) { + public void add(UUID ownerId, TempDeviceInfo newDeviceInfo) { try { - List deviceInfos = store.get(ownerId, CopyOnWriteArrayList::new); - deviceInfos.removeIf(di-> isInSameRoomAndWifi(di, newDeviceInfo)); + List deviceInfos = store.get(ownerId, CopyOnWriteArrayList::new); + deviceInfos.removeIf(di-> di.equals(newDeviceInfo)); deviceInfos.add(newDeviceInfo); } catch (ExecutionException e) { throw new IllegalStateException("value 초기화 실패"); @@ -59,24 +59,19 @@ public void add(MemberId ownerId, DeviceInfo newDeviceInfo) { } //불변 반환 - public List get(MemberId ownerId){ - List deviceInfos = store.getIfPresent(ownerId); + public List get(UUID ownerId){ + List deviceInfos = store.getIfPresent(ownerId); return deviceInfos != null ? List.copyOf(deviceInfos) : List.of(); } - public void remove(MemberId ownerId){ + public void remove(UUID ownerId){ store.invalidate(ownerId); } //반환 전 제거 - public List takeout(MemberId ownerId){ - List deviceInfos = get(ownerId); + public List takeout(UUID ownerId){ + List deviceInfos = get(ownerId); remove(ownerId); return deviceInfos; } - - //같은 방, 같은 와이파이에 대해 저장된 정보인지 확인 - private boolean isInSameRoomAndWifi(DeviceInfo deviceInfo1, DeviceInfo deviceInfo2){ - return deviceInfo1.getRoom().equals(deviceInfo2.getRoom()) && deviceInfo1.getSsid().equals(deviceInfo2.getSsid()); - } } diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/private_ip/PrivateIpStore.java b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/private_ip/PrivateIpStore.java new file mode 100644 index 00000000..6c424e72 --- /dev/null +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/caching/private_ip/PrivateIpStore.java @@ -0,0 +1,31 @@ +package com.whoz_in.main_api.shared.caching.private_ip; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.whoz_in.main_api.config.RoomSsidConfig; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public final class PrivateIpStore { + //Cache<방, Map> + private static final Cache> store = CacheBuilder.newBuilder().build(); + private final RoomSsidConfig ssidConfig; + + public void put(String room, Map privateIps){ + store.put(room, privateIps); + } + + public Map get(String room){ + Map privateIps = store.getIfPresent(room); + if (privateIps == null){ + throw new IllegalStateException("방에 대한 내부 아이피 정보가 없음"); //main-api 서버 시작된지 얼마 안됐을 때 or 잘못된 room을 넘겼을 때 + } + if (privateIps.size() != ssidConfig.getSsids(room).size()){ + throw new IllegalStateException(room + "의 내부 아이피가 올바르지 않은 상태"); //해당 방의 와이파이가 잘 연결됐는지 확인해봐야 함 + } + return privateIps; + } +} diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringApplicationEventBus.java b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringApplicationEventBus.java similarity index 92% rename from modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringApplicationEventBus.java rename to modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringApplicationEventBus.java index 4b4daabb..da0191a0 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringApplicationEventBus.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringApplicationEventBus.java @@ -1,4 +1,4 @@ -package com.whoz_in.main_api.shared.domain; +package com.whoz_in.main_api.shared.infrastructure.domain; import com.whoz_in.domain.shared.event.DomainEvent; import com.whoz_in.domain.shared.event.EventBus; diff --git a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringSecurityPasswordEncoder.java b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringSecurityPasswordEncoder.java similarity index 91% rename from modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringSecurityPasswordEncoder.java rename to modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringSecurityPasswordEncoder.java index 4117efa1..47e7c450 100644 --- a/modules/main-api/src/main/java/com/whoz_in/main_api/shared/domain/SpringSecurityPasswordEncoder.java +++ b/modules/main-api/src/main/java/com/whoz_in/main_api/shared/infrastructure/domain/SpringSecurityPasswordEncoder.java @@ -1,4 +1,4 @@ -package com.whoz_in.main_api.shared.domain; +package com.whoz_in.main_api.shared.infrastructure.domain; import com.whoz_in.domain.member.service.PasswordEncoder; import lombok.RequiredArgsConstructor; diff --git a/modules/main-api/src/main/resources/application-main-api.yml b/modules/main-api/src/main/resources/application-main-api.yml index 6e8d4833..176c86d9 100644 --- a/modules/main-api/src/main/resources/application-main-api.yml +++ b/modules/main-api/src/main/resources/application-main-api.yml @@ -17,7 +17,9 @@ logging: frontend: base-url: ${FRONTEND_URL} -https-enabled: ${HTTPS-ENABLED} +api-key: ${API_KEY} + +https-enabled: ${HTTPS_ENABLED} oauth: redirectUri: ${OAUTH_REDIRECT_URL} diff --git a/modules/main-api/src/test/java/com/whoz_in/main_api/shared/RequesterInfoTest.java b/modules/main-api/src/test/java/com/whoz_in/main_api/shared/RequesterInfoTest.java index 86487b9c..567d6fec 100644 --- a/modules/main-api/src/test/java/com/whoz_in/main_api/shared/RequesterInfoTest.java +++ b/modules/main-api/src/test/java/com/whoz_in/main_api/shared/RequesterInfoTest.java @@ -2,7 +2,7 @@ import com.whoz_in.domain.member.model.MemberId; import com.whoz_in.main_api.config.security.JwtAuthentication; -import com.whoz_in.main_api.shared.utils.SpringSecurityRequesterInfo; +import com.whoz_in.main_api.config.security.SpringSecurityRequesterInfo; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/modules/master-api/src/main/java/com/whoz_in/master_api/private_ip/MainApiHandlerPrivateIpWriter.java b/modules/master-api/src/main/java/com/whoz_in/master_api/private_ip/MainApiHandlerPrivateIpWriter.java new file mode 100644 index 00000000..75d3ac1b --- /dev/null +++ b/modules/master-api/src/main/java/com/whoz_in/master_api/private_ip/MainApiHandlerPrivateIpWriter.java @@ -0,0 +1,22 @@ +package com.whoz_in.master_api.private_ip; + +import com.whoz_in.main_api.command.private_ip.PrivateIpUpdate; +import com.whoz_in.main_api.command.private_ip.PrivateIpUpdateHandler; +import com.whoz_in.network_api.private_ip.PrivateIpWriter; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +//main-api의 핸들러를 직접 호출하여 내부 아이피를 최신화함 +@Primary +@Component +@RequiredArgsConstructor +public class MainApiHandlerPrivateIpWriter implements PrivateIpWriter { + private final PrivateIpUpdateHandler privateIpUpdateHandler; + @Override + public boolean write(String room, Map privateIps) { + privateIpUpdateHandler.handle(new PrivateIpUpdate(room, privateIps)); + return true; + } +} diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/common/StubNetworkInterfaces.java b/modules/network-api/src/main/java/com/whoz_in/network_api/common/StubNetworkInterfaces.java index b1fe6cbf..3c376f6b 100644 --- a/modules/network-api/src/main/java/com/whoz_in/network_api/common/StubNetworkInterfaces.java +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/common/StubNetworkInterfaces.java @@ -19,6 +19,6 @@ public final class StubNetworkInterfaces implements SystemNetworkInterfaces{ @Override public List getLatest() { - return networkConfig.getNetworkInterfaces(); + return networkConfig.getAllNIs(); } } diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/config/NetworkConfig.java b/modules/network-api/src/main/java/com/whoz_in/network_api/config/NetworkConfig.java index 6fdfbc05..b97e6f57 100644 --- a/modules/network-api/src/main/java/com/whoz_in/network_api/config/NetworkConfig.java +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/config/NetworkConfig.java @@ -14,7 +14,8 @@ public class NetworkConfig { private final String room; private final String sudoPassword; - private final List networkInterfaces; + private final List allNIs; + private final List managedNIs; private final NetworkInterface monitorNI; private final List mdnsNIs; private final List arpNIs; @@ -45,7 +46,10 @@ public NetworkConfig( generateCommand(arpCommandTemplate, arp.interfaceName())); }) .toList(); - this.networkInterfaces = Stream.of(monitorNI, mdnsNIs, arpNIs) + this.managedNIs = Stream.of(mdnsNIs, arpNIs) + .flatMap(Collection::stream) + .toList(); + this.allNIs = Stream.of(monitorNI, this.managedNIs) .flatMap(list -> list instanceof Collection ? ((Collection) list).stream() : Stream.of((NetworkInterface) list)) .toList(); diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/config/RestTemplateConfig.java b/modules/network-api/src/main/java/com/whoz_in/network_api/config/RestTemplateConfig.java new file mode 100644 index 00000000..e999cd28 --- /dev/null +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package com.whoz_in.network_api.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/HttpPrivateIpWriter.java b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/HttpPrivateIpWriter.java new file mode 100644 index 00000000..9a82c0fa --- /dev/null +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/HttpPrivateIpWriter.java @@ -0,0 +1,72 @@ +package com.whoz_in.network_api.private_ip; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +//http로 알리도록 구현 +@Slf4j +@Component +public final class HttpPrivateIpWriter implements PrivateIpWriter { + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + private final String mainApiBaseUrl; + private final String mainApiKey; + + public HttpPrivateIpWriter(RestTemplate restTemplate, ObjectMapper objectMapper, + @Value("${main-api.base-url}") String mainApiBaseUrl, + @Value("${main-api.api-key}") String mainApiKey) { + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; + this.mainApiBaseUrl = mainApiBaseUrl; + this.mainApiKey = mainApiKey; + } + + //TODO: 다른 요청도 하게 되면 공통 로직 묶기 + @Override + public boolean write(String room, Map privateIps) { + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", mainApiKey); + headers.set("Content-Type", "application/json"); + + HttpEntity requestEntity = new HttpEntity<>(createRequestBody(room, privateIps), headers); + + try { + restTemplate.exchange( + mainApiBaseUrl + "/api/v1/private-ip", + HttpMethod.PUT, + requestEntity, + Void.class + ); + return true; + } catch (ResourceAccessException e){ + log.error("main api에 접근할 수 없음 : {}", e.getMessage()); //서버가 꺼져있는지 확인 + } catch (HttpClientErrorException.Forbidden e) { + log.error("Api key 인증 실패 : {}", e.getMessage()); + } catch (Exception e) { + log.error("알 수 없는 예외 : {}", e.getMessage()); + } + return false; + } + + private String createRequestBody(String room, Map privateIps) { + try { + return objectMapper.writeValueAsString(Map.of( + "room", room, + "private_ip_list", privateIps + )); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialize request body", e); + } + } +} diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpUpdater.java b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpUpdater.java new file mode 100644 index 00000000..fa376a93 --- /dev/null +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpUpdater.java @@ -0,0 +1,43 @@ +package com.whoz_in.network_api.private_ip; + +import com.whoz_in.network_api.common.NetworkInterface; +import com.whoz_in.network_api.config.NetworkConfig; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +//주기적으로 자신의 내부 아이피를 알아내고 PrivateIpWriter를 이용하여 main-api에 저장 +@Slf4j +@Component +@RequiredArgsConstructor +public class PrivateIpUpdater { + private Map sent = Map.of(); + private final NetworkConfig networkConfig; + private final PrivateIpWriter writer; + + @Scheduled(fixedRate = 30000) + private void update() { + Map privateIp = getPrivateIpPerSsid(); + if (sent.equals(privateIp)) + return; + if (writer.write(networkConfig.getRoom(), privateIp)) { + sent = privateIp; + log.info("[private ip] updated"); + } + } + + private Map getPrivateIpPerSsid(){ + List networkInterfaces = networkConfig.getManagedNIs(); + //아이피를 얻지 못했을 경우 담지 않는다. + Map privateIps = new HashMap<>(); + for (NetworkInterface ni : networkInterfaces) { + SystemPrivateIpResolver.getIPv4(ni.getInterfaceName()) + .ifPresent(ip->privateIps.put(ni.getAltSsid(), ip)); + } + return privateIps; + } +} diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpWriter.java b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpWriter.java new file mode 100644 index 00000000..85de5144 --- /dev/null +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/PrivateIpWriter.java @@ -0,0 +1,9 @@ +package com.whoz_in.network_api.private_ip; + +import java.util.Map; + +//main-api에게 자신(network-api)이 가진 내부 아이피를 알리는 기능 +public interface PrivateIpWriter { + //반환 값은 성공 여부를 나타낸다. + boolean write(String room, Map privateIps); //Map<방, Optional<아이피>> +} diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/SystemPrivateIpResolver.java b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/SystemPrivateIpResolver.java new file mode 100644 index 00000000..f9ab1072 --- /dev/null +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/private_ip/SystemPrivateIpResolver.java @@ -0,0 +1,25 @@ +package com.whoz_in.network_api.private_ip; + +import java.net.InetAddress; +import java.net.SocketException; +import java.util.Collections; +import java.util.Optional; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +//내부 아이피를 알아내는 클래스 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class SystemPrivateIpResolver { + public static Optional getIPv4(String interfaceName) { + try { + return Collections.list(java.net.NetworkInterface.getNetworkInterfaces()).stream() + .filter(networkInterface -> networkInterface.getName().equals(interfaceName)) + .flatMap(networkInterface -> Collections.list(networkInterface.getInetAddresses()).stream()) + .filter(address -> !address.isLoopbackAddress() && address instanceof java.net.Inet4Address) + .map(InetAddress::getHostAddress) + .findAny(); + } catch (SocketException e) { + throw new IllegalStateException("시스템에 네트워크 인터페이스가 아예 존재하지 않음"); + } + } +} diff --git a/modules/network-api/src/main/java/com/whoz_in/network_api/system_validator/SystemValidator.java b/modules/network-api/src/main/java/com/whoz_in/network_api/system_validator/SystemValidator.java index edf07bb4..9fb6d9e5 100644 --- a/modules/network-api/src/main/java/com/whoz_in/network_api/system_validator/SystemValidator.java +++ b/modules/network-api/src/main/java/com/whoz_in/network_api/system_validator/SystemValidator.java @@ -40,7 +40,7 @@ public SystemValidator( //네트워크 인터페이스 정보 List system = systemNIs.getLatest(); - List setting = config.getNetworkInterfaces(); + List setting = config.getAllNIs(); //네트워크 인터페이스 출력 log.info("\n시스템 네트워크 인터페이스 - \n{}\n설정된 네트워크 인터페이스 - \n{}", @@ -64,7 +64,7 @@ private void checkRegularly() { log.info("시스템 검증 시작.."); //네트워크 인터페이스 상태 검증 ValidationResult result = networkInterfaceValidator.getValidationResult( - systemNIs.getLatest(), config.getNetworkInterfaces()); + systemNIs.getLatest(), config.getAllNIs()); //더 많은 검증하면 result에 추가하기.. if (result.hasErrors()) { diff --git a/modules/network-api/src/main/resources/application-network-api.yml b/modules/network-api/src/main/resources/application-network-api.yml index 0d3496f7..d561be38 100644 --- a/modules/network-api/src/main/resources/application-network-api.yml +++ b/modules/network-api/src/main/resources/application-network-api.yml @@ -18,5 +18,9 @@ command: sudo_password: ${SUDO_PASSWORD} +main-api: + base-url: ${MAIN_API_BASE_URL} + api-key: ${MAIN_API_API_KEY} + logging: config: "classpath:logback-common.xml" \ No newline at end of file