-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Api Key 관련 클래스 이름 리팩토링 - ApiKeyProperty -> ApiKeyProvider - BusApiKeyProperty -> PropertyApiKeyProvider ## Persistence Api Key 구현 - 관리자 페이지에서 api키 관리 (등록/수정) - 등록된 api키는 db에 저장되고 매일자정 사용량이 초기화됨 - 사용량을 초과한 api key는 PersistenceApiKeyProvider에서 제거되고 자정이 다시 추가됨 - 사용량은 메모리에 저장하고 20분마다 db에 업데이트 close #146
- Loading branch information
Showing
15 changed files
with
412 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
server/src/main/java/com/talkka/server/admin/controller/PublicApiKeyController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
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 = "created_at", nullable = false) | ||
@CreatedDate | ||
private LocalDateTime createdAt; | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
server/src/main/java/com/talkka/server/admin/dao/PublicApiKeyRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PublicApiKeyEntity, Long> { | ||
boolean existsBySecret(String secret); | ||
|
||
void deleteBySecret(String secret); | ||
} |
22 changes: 22 additions & 0 deletions
22
server/src/main/java/com/talkka/server/admin/dto/PublicApiKeyRespDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
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, | ||
Map<String, Integer> keyUsage, | ||
LocalDateTime createdAt | ||
) { | ||
public static PublicApiKeyRespDto of(PublicApiKeyEntity key, Map<String, Integer> usageMap) { | ||
return new PublicApiKeyRespDto( | ||
key.getId(), | ||
key.getSecret(), | ||
usageMap, | ||
key.getCreatedAt() | ||
); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...r/src/main/java/com/talkka/server/admin/exception/PublicApiKeyAlreadyExistsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
server/src/main/java/com/talkka/server/admin/exception/PublicApiKeyNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
server/src/main/java/com/talkka/server/admin/service/PublicApiKeyService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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; | ||
|
||
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 com.talkka.server.api.datagg.config.PersistenceApiKeyProvider; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PublicApiKeyService { | ||
private final PublicApiKeyRepository publicApiKeyRepository; | ||
private final PersistenceApiKeyProvider persistenceApiKeyProvider; | ||
|
||
@Transactional | ||
public PublicApiKeyRespDto createKey(String secret) throws PublicApiKeyAlreadyExistsException { | ||
if (publicApiKeyRepository.existsBySecret(secret)) { | ||
throw new PublicApiKeyAlreadyExistsException(); | ||
} | ||
var key = publicApiKeyRepository.save( | ||
PublicApiKeyEntity.builder() | ||
.secret(secret) | ||
.build() | ||
); | ||
persistenceApiKeyProvider.addKey(key); | ||
// 처음 생성시에는 빈 사용량맵 반환 | ||
return PublicApiKeyRespDto.of(key, new TreeMap<>()); | ||
} | ||
|
||
@Transactional | ||
public void deleteKey(String secret) throws PublicApiKeyNotFoundException { | ||
if (!publicApiKeyRepository.existsBySecret(secret)) { | ||
throw new PublicApiKeyNotFoundException(); | ||
} | ||
publicApiKeyRepository.deleteBySecret(secret); | ||
persistenceApiKeyProvider.deleteKey(secret); | ||
} | ||
|
||
public List<PublicApiKeyRespDto> getKeyList() { | ||
List<PublicApiKeyRespDto> 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; | ||
} | ||
} |
5 changes: 0 additions & 5 deletions
5
server/src/main/java/com/talkka/server/api/core/config/ApiKeyProperty.java
This file was deleted.
Oops, something went wrong.
5 changes: 5 additions & 0 deletions
5
server/src/main/java/com/talkka/server/api/core/config/ApiKeyProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.talkka.server.api.core.config; | ||
|
||
public interface ApiKeyProvider { | ||
String getApiKey(String path); | ||
} |
84 changes: 84 additions & 0 deletions
84
server/src/main/java/com/talkka/server/api/datagg/config/PersistenceApiKeyProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.talkka.server.api.datagg.config; | ||
|
||
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; | ||
import org.springframework.stereotype.Component; | ||
|
||
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 | ||
@RequiredArgsConstructor | ||
@Primary | ||
public class PersistenceApiKeyProvider implements ApiKeyProvider { | ||
|
||
private final PublicApiKeyRepository publicApiKeyRepository; | ||
@Getter | ||
private final List<String> keyList = new ArrayList<>(); | ||
@Getter | ||
private final List<Map<String, Integer>> usageMap = new ArrayList<>(); | ||
private int rollingKeyIndex = 0; | ||
private final int MAX_USAGE = 950; // 일일 최대 사용량 | ||
|
||
@Override | ||
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("사용 가능한 키가 없습니다."); | ||
} | ||
|
||
// 매일 자정에 키 사용량 리셋 | ||
@PostConstruct | ||
@Scheduled(cron = "0 0 0 * * *") | ||
public void init() { | ||
rollingKeyIndex = 0; | ||
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<>()); | ||
} | ||
|
||
public void deleteKey(String secret) { | ||
int idx = keyList.indexOf(secret); | ||
keyList.remove(idx); | ||
usageMap.remove(idx); | ||
} | ||
|
||
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.