From 8c861f6bf16261d247d394ba0c41ac62b6dd3bfe Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:13:38 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat=20:=20ApiKeyProperty->ApiKeyProvider?= =?UTF-8?q?=EB=A1=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/talkka/server/admin/config/ApiKeyProvider.java | 5 +++++ .../com/talkka/server/api/core/config/ApiKeyProperty.java | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java delete mode 100644 server/src/main/java/com/talkka/server/api/core/config/ApiKeyProperty.java diff --git a/server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java b/server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java new file mode 100644 index 00000000..bcf773b6 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java @@ -0,0 +1,5 @@ +package com.talkka.server.admin.config; + +public interface ApiKeyProvider { + String getApiKey(); +} diff --git a/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProperty.java b/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProperty.java deleted file mode 100644 index 114ce92d..00000000 --- a/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProperty.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.talkka.server.api.core.config; - -public interface ApiKeyProperty { - String getApiKey(); -} From 86d6106fe55f950cdfecf068c3ffdfa70d229c94 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:14:12 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20BusApiKeyProperty->PropertyApi?= =?UTF-8?q?KeyProvider=EB=A1=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/PropertyApiKeyProvider.java} | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename server/src/main/java/com/talkka/server/{api/datagg/config/BusApiKeyProperty.java => admin/config/PropertyApiKeyProvider.java} (75%) diff --git a/server/src/main/java/com/talkka/server/api/datagg/config/BusApiKeyProperty.java b/server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java similarity index 75% rename from server/src/main/java/com/talkka/server/api/datagg/config/BusApiKeyProperty.java rename to server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java index d5ac166e..1e8b5359 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/config/BusApiKeyProperty.java +++ b/server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java @@ -1,19 +1,17 @@ -package com.talkka.server.api.datagg.config; +package com.talkka.server.admin.config; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import com.talkka.server.api.core.config.ApiKeyProperty; - import lombok.Getter; import lombok.Setter; @Getter @Component @ConfigurationProperties(prefix = "openapi.public.bus.service-key") -public class BusApiKeyProperty implements ApiKeyProperty { +public class PropertyApiKeyProvider implements ApiKeyProvider { @Setter private List keys; From 2588db232dd46a72033a82a5854e7f7addf7880b Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:14:39 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat=20:=20PersistenceApiKey=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/PersistenceApiKeyProvider.java | 60 +++++++++++++++++++ .../admin/controller/AdminController.java | 14 ++--- .../controller/PublicApiKeyController.java | 41 +++++++++++++ .../server/admin/dao/PublicApiKeyEntity.java | 49 +++++++++++++++ .../admin/dao/PublicApiKeyRepository.java | 11 ++++ .../server/admin/dto/PublicApiKeyRespDto.java | 21 +++++++ .../PublicApiKeyAlreadyExistsException.java | 9 +++ .../PublicApiKeyNotFoundException.java | 9 +++ .../admin/service/PublicApiKeyService.java | 48 +++++++++++++++ 9 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java create mode 100644 server/src/main/java/com/talkka/server/admin/controller/PublicApiKeyController.java create mode 100644 server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java create mode 100644 server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyRepository.java create mode 100644 server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java create mode 100644 server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyAlreadyExistsException.java create mode 100644 server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyNotFoundException.java create mode 100644 server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java diff --git a/server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java b/server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java new file mode 100644 index 00000000..0ff56d79 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java @@ -0,0 +1,60 @@ +package com.talkka.server.admin.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.Primary; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.talkka.server.admin.dao.PublicApiKeyEntity; +import com.talkka.server.admin.dao.PublicApiKeyRepository; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +@Primary +public class PersistenceApiKeyProvider implements ApiKeyProvider { + + private final PublicApiKeyRepository publicApiKeyRepository; + private final List keys = new ArrayList<>(); + private int rollingKeyIndex = 0; + private final int MAX_USAGE = 950; // 일일 최대 사용량 + + @Override + public String getApiKey() { + rollingKeyIndex = (rollingKeyIndex + 1) % keys.size(); + updateUsage(keys.get(rollingKeyIndex)); + return keys.get(rollingKeyIndex).getSecret(); + } + + // 매일 자정에 키 사용량 리셋 + @PostConstruct + @Scheduled(cron = "0 0 0 * * *") + public void init() { + rollingKeyIndex = 0; + keys.clear(); + keys.addAll(publicApiKeyRepository.findAll()); + // 사용량 초기화 + keys.forEach(PublicApiKeyEntity::reset); + publicApiKeyRepository.saveAll(keys); + } + + // 20분마다 사용량 DB에 반영 + @Scheduled(cron = "0 */20 * * * *") + private void persist() { + System.out.println("persist key!!"); + publicApiKeyRepository.saveAll(keys); + } + + private void updateUsage(PublicApiKeyEntity key) { + key.use(); + // 일일 사용량이 초과되면 리스트에서 삭제 후 DB에 반영 + if (key.getKeyUsage() >= MAX_USAGE) { + keys.remove(key); + publicApiKeyRepository.save(key); + } + } +} diff --git a/server/src/main/java/com/talkka/server/admin/controller/AdminController.java b/server/src/main/java/com/talkka/server/admin/controller/AdminController.java index 4a0a34fa..2cb58b18 100644 --- a/server/src/main/java/com/talkka/server/admin/controller/AdminController.java +++ b/server/src/main/java/com/talkka/server/admin/controller/AdminController.java @@ -7,6 +7,7 @@ import com.talkka.server.admin.service.AdminService; import com.talkka.server.admin.service.CollectBusRouteService; +import com.talkka.server.admin.service.PublicApiKeyService; import com.talkka.server.user.service.UserService; import lombok.RequiredArgsConstructor; @@ -18,8 +19,7 @@ public class AdminController { private final AdminService adminService; private final UserService userService; private final CollectBusRouteService collectBusRouteService; - // private final PublicApiKeyService publicApiKeyService; - // private final PublicApiKeyService publicApiKeyService; + private final PublicApiKeyService publicApiKeyService; // private final DynamicSchedulingConfig dynamicSchedulingConfig; @GetMapping("") @@ -51,11 +51,11 @@ public String collectRoute(Model model) { return "admin/collect"; } - // @GetMapping("/key") - // public String publicApiKey(Model model) { - // model.addAttribute("apiKeys", publicApiKeyService.getKeyList()); - // return "admin/key"; - // } + @GetMapping("/key") + public String publicApiKey(Model model) { + model.addAttribute("apiKeys", publicApiKeyService.getKeyList()); + return "admin/key"; + } // // @GetMapping("/scheduler") // public String scheduler(Model model) { diff --git a/server/src/main/java/com/talkka/server/admin/controller/PublicApiKeyController.java b/server/src/main/java/com/talkka/server/admin/controller/PublicApiKeyController.java new file mode 100644 index 00000000..db888b4c --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/controller/PublicApiKeyController.java @@ -0,0 +1,41 @@ +package com.talkka.server.admin.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.talkka.server.admin.exception.PublicApiKeyAlreadyExistsException; +import com.talkka.server.admin.exception.PublicApiKeyNotFoundException; +import com.talkka.server.admin.service.PublicApiKeyService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/api/key") +public class PublicApiKeyController { + private final PublicApiKeyService publicApiKeyService; + + @PostMapping("") + public ResponseEntity createKey(@RequestBody String secret) { + try { + publicApiKeyService.createKey(secret); + } catch (PublicApiKeyAlreadyExistsException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + return ResponseEntity.ok().build(); + } + + @DeleteMapping("") + public ResponseEntity deleteKey(@RequestBody String secret) { + try { + publicApiKeyService.deleteKey(secret); + } catch (PublicApiKeyNotFoundException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + return ResponseEntity.ok().build(); + } +} diff --git a/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java new file mode 100644 index 00000000..b54d8361 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java @@ -0,0 +1,49 @@ +package com.talkka.server.admin.dao; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity(name = "public_apikey") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class PublicApiKeyEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "secret", nullable = false) + private String secret; + + @Column(name = "key_usage", nullable = false) + private Integer keyUsage; + + @Column(name = "created_at", nullable = false) + @CreatedDate + private LocalDateTime createdAt; + + public void use() { + this.keyUsage++; + } + + public void reset() { + this.keyUsage = 0; + } + +} diff --git a/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyRepository.java b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyRepository.java new file mode 100644 index 00000000..3ca66741 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyRepository.java @@ -0,0 +1,11 @@ +package com.talkka.server.admin.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PublicApiKeyRepository extends JpaRepository { + boolean existsBySecret(String secret); + + void deleteBySecret(String secret); +} diff --git a/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java b/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java new file mode 100644 index 00000000..d0936a04 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java @@ -0,0 +1,21 @@ +package com.talkka.server.admin.dto; + +import java.time.LocalDateTime; + +import com.talkka.server.admin.dao.PublicApiKeyEntity; + +public record PublicApiKeyRespDto( + Long id, + String secret, + Integer keyUsage, + LocalDateTime createdAt +) { + public static PublicApiKeyRespDto of(PublicApiKeyEntity key) { + return new PublicApiKeyRespDto( + key.getId(), + key.getSecret(), + key.getKeyUsage(), + key.getCreatedAt() + ); + } +} diff --git a/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyAlreadyExistsException.java b/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyAlreadyExistsException.java new file mode 100644 index 00000000..1a4db304 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyAlreadyExistsException.java @@ -0,0 +1,9 @@ +package com.talkka.server.admin.exception; + +public class PublicApiKeyAlreadyExistsException extends RuntimeException { + private static final String MESSAGE = "이미 등록된 api key 입니다."; + + public PublicApiKeyAlreadyExistsException() { + super(MESSAGE); + } +} diff --git a/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyNotFoundException.java b/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyNotFoundException.java new file mode 100644 index 00000000..fdf54f55 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyNotFoundException.java @@ -0,0 +1,9 @@ +package com.talkka.server.admin.exception; + +public class PublicApiKeyNotFoundException extends RuntimeException { + private static final String MESSAGE = "존재하지 않는 api key 입니다."; + + public PublicApiKeyNotFoundException() { + super(MESSAGE); + } +} diff --git a/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java b/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java new file mode 100644 index 00000000..7a618aa8 --- /dev/null +++ b/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java @@ -0,0 +1,48 @@ +package com.talkka.server.admin.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.talkka.server.admin.dao.PublicApiKeyEntity; +import com.talkka.server.admin.dao.PublicApiKeyRepository; +import com.talkka.server.admin.dto.PublicApiKeyRespDto; +import com.talkka.server.admin.exception.PublicApiKeyAlreadyExistsException; +import com.talkka.server.admin.exception.PublicApiKeyNotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PublicApiKeyService { + private final PublicApiKeyRepository publicApiKeyRepository; + + @Transactional + public PublicApiKeyRespDto createKey(String secret) throws PublicApiKeyAlreadyExistsException { + if (publicApiKeyRepository.existsBySecret(secret)) { + throw new PublicApiKeyAlreadyExistsException(); + } + var key = publicApiKeyRepository.save( + PublicApiKeyEntity.builder() + .secret(secret) + .keyUsage(0) + .build() + ); + return PublicApiKeyRespDto.of(key); + } + + @Transactional + public void deleteKey(String secret) throws PublicApiKeyNotFoundException { + if (!publicApiKeyRepository.existsBySecret(secret)) { + throw new PublicApiKeyNotFoundException(); + } + publicApiKeyRepository.deleteBySecret(secret); + } + + public List getKeyList() { + return publicApiKeyRepository.findAll().stream() + .map(PublicApiKeyRespDto::of) + .toList(); + } +} From ea90bd48656334b18008e40793cce7b0c0a99f39 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:15:44 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat=20:=20(Admin)=20api=20key=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=99=94=EB=A9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/admin/collect-route-form.html | 31 ----- .../main/resources/templates/admin/key.html | 112 ++++++++++++++++++ 2 files changed, 112 insertions(+), 31 deletions(-) delete mode 100644 server/src/main/resources/templates/admin/collect-route-form.html create mode 100644 server/src/main/resources/templates/admin/key.html diff --git a/server/src/main/resources/templates/admin/collect-route-form.html b/server/src/main/resources/templates/admin/collect-route-form.html deleted file mode 100644 index 3fc9e7f0..00000000 --- a/server/src/main/resources/templates/admin/collect-route-form.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Create Collected Bus Route - - - - -
-

Add New Collected Bus Route

- - - -
-
- - -
- - Cancel -
-
- - - - - - diff --git a/server/src/main/resources/templates/admin/key.html b/server/src/main/resources/templates/admin/key.html new file mode 100644 index 00000000..f0acab91 --- /dev/null +++ b/server/src/main/resources/templates/admin/key.html @@ -0,0 +1,112 @@ + + + + + API 키 관리 + + + + + + + +
+

API 키 관리

+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + +
#API 키사용횟수생성일삭제
+ +
+
+ + + + + + + + + From 4403c494b436ac90afeebfe3cf4ca99fe20db06e Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:18:33 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat=20:=20ApiKeyProvider=EC=99=80=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/{admin => api/core}/config/ApiKeyProvider.java | 2 +- .../datagg}/config/PersistenceApiKeyProvider.java | 3 ++- .../{admin => api/datagg}/config/PropertyApiKeyProvider.java | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) rename server/src/main/java/com/talkka/server/{admin => api/core}/config/ApiKeyProvider.java (57%) rename server/src/main/java/com/talkka/server/{admin => api/datagg}/config/PersistenceApiKeyProvider.java (94%) rename server/src/main/java/com/talkka/server/{admin => api/datagg}/config/PropertyApiKeyProvider.java (84%) diff --git a/server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java similarity index 57% rename from server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java rename to server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java index bcf773b6..907b3808 100644 --- a/server/src/main/java/com/talkka/server/admin/config/ApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java @@ -1,4 +1,4 @@ -package com.talkka.server.admin.config; +package com.talkka.server.api.core.config; public interface ApiKeyProvider { String getApiKey(); diff --git a/server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java similarity index 94% rename from server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java rename to server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java index 0ff56d79..0f87be2f 100644 --- a/server/src/main/java/com/talkka/server/admin/config/PersistenceApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java @@ -1,4 +1,4 @@ -package com.talkka.server.admin.config; +package com.talkka.server.api.datagg.config; import java.util.ArrayList; import java.util.List; @@ -9,6 +9,7 @@ import com.talkka.server.admin.dao.PublicApiKeyEntity; import com.talkka.server.admin.dao.PublicApiKeyRepository; +import com.talkka.server.api.core.config.ApiKeyProvider; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; diff --git a/server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java similarity index 84% rename from server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java rename to server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java index 1e8b5359..b4ca6b58 100644 --- a/server/src/main/java/com/talkka/server/admin/config/PropertyApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java @@ -1,10 +1,12 @@ -package com.talkka.server.admin.config; +package com.talkka.server.api.datagg.config; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import com.talkka.server.api.core.config.ApiKeyProvider; + import lombok.Getter; import lombok.Setter; From 1fd88f1eed8b4c8930be92e62dceee66c490587f Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:18:46 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat=20:=20ApiKeyProvider=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/api/datagg/service/SimpleBusApiService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java index 43aacea5..d0b92b37 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java +++ b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java @@ -17,8 +17,8 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; +import com.talkka.server.api.core.config.ApiKeyProvider; import com.talkka.server.api.core.exception.ApiClientException; -import com.talkka.server.api.datagg.config.BusApiKeyProperty; import com.talkka.server.api.datagg.dto.BusArrivalBodyDto; import com.talkka.server.api.datagg.dto.BusArrivalRespDto; import com.talkka.server.api.datagg.dto.BusLocationBodyDto; @@ -36,7 +36,7 @@ @RequiredArgsConstructor public class SimpleBusApiService implements BusApiService { private static final Logger log = LoggerFactory.getLogger(SimpleBusApiService.class); - private final BusApiKeyProperty busApiKeyProperty; + private final ApiKeyProvider apiKeyProvider; private final RestTemplate restTemplate = new RestTemplate(); private static final String host = "apis.data.go.kr"; @@ -119,7 +119,7 @@ private URI getOpenApiUri(String path, MultiValueMap params) { .scheme("https") .host(host) .path(path) - .queryParam("serviceKey", this.busApiKeyProperty.getApiKey()) + .queryParam("serviceKey", this.apiKeyProvider.getApiKey()) .queryParams(params) .build(); } From b0157eba197a357b5651ec3385fe7f75811f7cb6 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:42:28 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat=20:=20(Admin)=20api=20key=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/main/resources/templates/admin/key.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/resources/templates/admin/key.html b/server/src/main/resources/templates/admin/key.html index f0acab91..89bc4dc7 100644 --- a/server/src/main/resources/templates/admin/key.html +++ b/server/src/main/resources/templates/admin/key.html @@ -64,9 +64,9 @@

API 키 관리

fetch('/admin/api/key', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'text/plain' }, - body: JSON.stringify(apiKey) + body: apiKey }) .then(response => { if (response.ok) { From 5af87e3c10a9bb409800bacb1c3053a9c4150d68 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 14:48:25 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/api/datagg/config/PersistenceApiKeyProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java index 0f87be2f..5d2ea18f 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java @@ -46,7 +46,6 @@ public void init() { // 20분마다 사용량 DB에 반영 @Scheduled(cron = "0 */20 * * * *") private void persist() { - System.out.println("persist key!!"); publicApiKeyRepository.saveAll(keys); } From 0f91f6d4ed2be60c05d15b5d2c3b910f82efb102 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 17:40:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix=20:=20api=20path=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EB=9F=89=20=EC=A7=91?= =?UTF-8?q?=EA=B3=84=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/admin/dao/PublicApiKeyEntity.java | 11 ---- .../server/admin/dto/PublicApiKeyRespDto.java | 7 +- .../admin/service/PublicApiKeyService.java | 26 ++++++-- .../api/core/config/ApiKeyProvider.java | 2 +- .../config/PersistenceApiKeyProvider.java | 64 +++++++++++++------ .../datagg/config/PropertyApiKeyProvider.java | 2 +- .../datagg/service/SimpleBusApiService.java | 2 +- 7 files changed, 72 insertions(+), 42 deletions(-) diff --git a/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java index b54d8361..a470c709 100644 --- a/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java +++ b/server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java @@ -31,19 +31,8 @@ public class PublicApiKeyEntity { @Column(name = "secret", nullable = false) private String secret; - @Column(name = "key_usage", nullable = false) - private Integer keyUsage; - @Column(name = "created_at", nullable = false) @CreatedDate private LocalDateTime createdAt; - public void use() { - this.keyUsage++; - } - - public void reset() { - this.keyUsage = 0; - } - } diff --git a/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java b/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java index d0936a04..36409d43 100644 --- a/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java +++ b/server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java @@ -1,20 +1,21 @@ package com.talkka.server.admin.dto; import java.time.LocalDateTime; +import java.util.Map; import com.talkka.server.admin.dao.PublicApiKeyEntity; public record PublicApiKeyRespDto( Long id, String secret, - Integer keyUsage, + Map keyUsage, LocalDateTime createdAt ) { - public static PublicApiKeyRespDto of(PublicApiKeyEntity key) { + public static PublicApiKeyRespDto of(PublicApiKeyEntity key, Map usageMap) { return new PublicApiKeyRespDto( key.getId(), key.getSecret(), - key.getKeyUsage(), + usageMap, key.getCreatedAt() ); } diff --git a/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java b/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java index 7a618aa8..f693e274 100644 --- a/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java +++ b/server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java @@ -1,6 +1,8 @@ package com.talkka.server.admin.service; +import java.util.ArrayList; import java.util.List; +import java.util.TreeMap; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -10,6 +12,7 @@ import com.talkka.server.admin.dto.PublicApiKeyRespDto; import com.talkka.server.admin.exception.PublicApiKeyAlreadyExistsException; import com.talkka.server.admin.exception.PublicApiKeyNotFoundException; +import com.talkka.server.api.datagg.config.PersistenceApiKeyProvider; import lombok.RequiredArgsConstructor; @@ -17,6 +20,7 @@ @RequiredArgsConstructor public class PublicApiKeyService { private final PublicApiKeyRepository publicApiKeyRepository; + private final PersistenceApiKeyProvider persistenceApiKeyProvider; @Transactional public PublicApiKeyRespDto createKey(String secret) throws PublicApiKeyAlreadyExistsException { @@ -26,10 +30,11 @@ public PublicApiKeyRespDto createKey(String secret) throws PublicApiKeyAlreadyEx var key = publicApiKeyRepository.save( PublicApiKeyEntity.builder() .secret(secret) - .keyUsage(0) .build() ); - return PublicApiKeyRespDto.of(key); + persistenceApiKeyProvider.addKey(key); + // 처음 생성시에는 빈 사용량맵 반환 + return PublicApiKeyRespDto.of(key, new TreeMap<>()); } @Transactional @@ -38,11 +43,22 @@ public void deleteKey(String secret) throws PublicApiKeyNotFoundException { throw new PublicApiKeyNotFoundException(); } publicApiKeyRepository.deleteBySecret(secret); + persistenceApiKeyProvider.deleteKey(secret); } public List getKeyList() { - return publicApiKeyRepository.findAll().stream() - .map(PublicApiKeyRespDto::of) - .toList(); + List result = new ArrayList<>(); + var keyEntityList = publicApiKeyRepository.findAll(); + var secretList = persistenceApiKeyProvider.getKeyList(); + var usageMapList = persistenceApiKeyProvider.getUsageMap(); + for (int i = 0; i < secretList.size(); i++) { + for (var keyEntity : keyEntityList) { + if (keyEntity.getSecret().equals(secretList.get(i))) { + result.add(PublicApiKeyRespDto.of(keyEntity, usageMapList.get(i))); + break; + } + } + } + return result; } } diff --git a/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java index 907b3808..a7bfd8bd 100644 --- a/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java @@ -1,5 +1,5 @@ package com.talkka.server.api.core.config; public interface ApiKeyProvider { - String getApiKey(); + String getApiKey(String path); } diff --git a/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java index 5d2ea18f..8a28e4e1 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java @@ -2,6 +2,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import org.springframework.context.annotation.Primary; import org.springframework.scheduling.annotation.Scheduled; @@ -10,8 +12,10 @@ import com.talkka.server.admin.dao.PublicApiKeyEntity; import com.talkka.server.admin.dao.PublicApiKeyRepository; import com.talkka.server.api.core.config.ApiKeyProvider; +import com.talkka.server.api.core.exception.ApiClientException; import jakarta.annotation.PostConstruct; +import lombok.Getter; import lombok.RequiredArgsConstructor; @Component @@ -20,15 +24,25 @@ public class PersistenceApiKeyProvider implements ApiKeyProvider { private final PublicApiKeyRepository publicApiKeyRepository; - private final List keys = new ArrayList<>(); + @Getter + private final List keyList = new ArrayList<>(); + @Getter + private final List> usageMap = new ArrayList<>(); private int rollingKeyIndex = 0; private final int MAX_USAGE = 950; // 일일 최대 사용량 @Override - public String getApiKey() { - rollingKeyIndex = (rollingKeyIndex + 1) % keys.size(); - updateUsage(keys.get(rollingKeyIndex)); - return keys.get(rollingKeyIndex).getSecret(); + public String getApiKey(String path) throws ApiClientException { + for (int i = 0; i < keyList.size(); i++) { + rollingKeyIndex = (rollingKeyIndex + 1) % keyList.size(); + // 호출한 적이 없거나 호출횟수가 MAX_USAGE 보다 작으면 api key 제공 + if (!usageMap.get(rollingKeyIndex).containsKey(path) + || usageMap.get(rollingKeyIndex).get(path) < MAX_USAGE) { + updateUsage(rollingKeyIndex, path); + return keyList.get(rollingKeyIndex); + } + } + throw new ApiClientException("사용 가능한 키가 없습니다."); } // 매일 자정에 키 사용량 리셋 @@ -36,25 +50,35 @@ public String getApiKey() { @Scheduled(cron = "0 0 0 * * *") public void init() { rollingKeyIndex = 0; - keys.clear(); - keys.addAll(publicApiKeyRepository.findAll()); - // 사용량 초기화 - keys.forEach(PublicApiKeyEntity::reset); - publicApiKeyRepository.saveAll(keys); + keyList.clear(); + usageMap.clear(); + var keys = publicApiKeyRepository.findAll(); + for (var key : keys) { + keyList.add(key.getSecret()); + usageMap.add(new TreeMap<>()); + } + } + + public void addKey(PublicApiKeyEntity key) { + keyList.add(key.getSecret()); + usageMap.add(new TreeMap<>()); } - // 20분마다 사용량 DB에 반영 - @Scheduled(cron = "0 */20 * * * *") - private void persist() { - publicApiKeyRepository.saveAll(keys); + public void deleteKey(String secret) { + int idx = keyList.indexOf(secret); + keyList.remove(idx); + usageMap.remove(idx); } - private void updateUsage(PublicApiKeyEntity key) { - key.use(); - // 일일 사용량이 초과되면 리스트에서 삭제 후 DB에 반영 - if (key.getKeyUsage() >= MAX_USAGE) { - keys.remove(key); - publicApiKeyRepository.save(key); + private void updateUsage(int idx, String path) { + var map = usageMap.get(idx); + if (!map.containsKey(path)) { + map.put(path, 1); + } else { + map.put(path, map.get(path) + 1); + if (map.get(path) > MAX_USAGE) { + map.remove(path); + } } } } diff --git a/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java b/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java index b4ca6b58..efa70c98 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java +++ b/server/src/main/java/com/talkka/server/api/datagg/config/PropertyApiKeyProvider.java @@ -20,7 +20,7 @@ public class PropertyApiKeyProvider implements ApiKeyProvider { private int rollingKeyIndex = 0; @Override - public String getApiKey() { + public String getApiKey(String path) { rollingKeyIndex = (rollingKeyIndex + 1) % keys.size(); return keys.get(rollingKeyIndex); } diff --git a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java index d0b92b37..ae416f81 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java +++ b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java @@ -119,7 +119,7 @@ private URI getOpenApiUri(String path, MultiValueMap params) { .scheme("https") .host(host) .path(path) - .queryParam("serviceKey", this.apiKeyProvider.getApiKey()) + .queryParam("serviceKey", this.apiKeyProvider.getApiKey(path)) .queryParams(params) .build(); } From 246efe216809f3c4b21d0a137ae2fda27a2a9649 Mon Sep 17 00:00:00 2001 From: PSH Date: Thu, 29 Aug 2024 17:44:15 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix=20:=20api=20path=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EB=9F=89=20=EC=A7=91?= =?UTF-8?q?=EA=B3=84=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/main/resources/templates/admin/key.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/main/resources/templates/admin/key.html b/server/src/main/resources/templates/admin/key.html index 89bc4dc7..a8a93a1b 100644 --- a/server/src/main/resources/templates/admin/key.html +++ b/server/src/main/resources/templates/admin/key.html @@ -41,7 +41,11 @@

API 키 관리

- + +
+
+
+