-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1003 from BCSDLab/develop
develop -> main
- Loading branch information
Showing
102 changed files
with
2,883 additions
and
421 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
src/main/java/in/koreatech/koin/admin/history/aop/AdminActivityHistoryAspect.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,136 @@ | ||
package in.koreatech.koin.admin.history.aop; | ||
|
||
import org.apache.commons.lang3.EnumUtils; | ||
import org.aspectj.lang.ProceedingJoinPoint; | ||
import org.aspectj.lang.annotation.Around; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.annotation.Pointcut; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.context.request.RequestContextHolder; | ||
import org.springframework.web.context.request.ServletRequestAttributes; | ||
import org.springframework.web.util.ContentCachingRequestWrapper; | ||
|
||
import in.koreatech.koin.admin.history.enums.DomainType; | ||
import in.koreatech.koin.admin.history.model.AdminActivityHistory; | ||
import in.koreatech.koin.admin.history.repository.AdminActivityHistoryRepository; | ||
import in.koreatech.koin.admin.user.model.Admin; | ||
import in.koreatech.koin.admin.user.repository.AdminRepository; | ||
import in.koreatech.koin.global.auth.AuthContext; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Aspect | ||
@Component | ||
@Profile("!test") | ||
@RequiredArgsConstructor | ||
public class AdminActivityHistoryAspect { | ||
private static final String REGEX_NUMERIC = "^[0-9]*$"; | ||
private static final String SEGMENT_SHOPS = "SHOPS"; | ||
private static final String SEGMENT_BENEFIT = "benefit"; | ||
private static final String SEGMENT_CATEGORIES = "CATEGORIES"; | ||
private static final String SEGMENT_CLOSE = "close"; | ||
private static final String SEGMENT_ABTEST = "abtest"; | ||
|
||
private final AuthContext authContext; | ||
private final AdminRepository adminRepository; | ||
private final AdminActivityHistoryRepository adminActivityHistoryRepository; | ||
|
||
@Pointcut("execution(* in.koreatech.koin.admin..controller.*.*(..))") | ||
private void allAdminControllers() { | ||
} | ||
|
||
@Pointcut("!@annotation(org.springframework.web.bind.annotation.GetMapping)") | ||
private void excludeGetMapping() { | ||
} | ||
|
||
@Pointcut("!execution(* in.koreatech.koin.admin.user.controller.AdminUserController.adminLogin(..)) && " | ||
+ "!execution(* in.koreatech.koin.admin.user.controller.AdminUserController.logout(..)) && " | ||
+ "!execution(* in.koreatech.koin.admin.user.controller.AdminUserController.refresh(..)) && " | ||
+ "!execution(* in.koreatech.koin.admin.user.controller.AdminUserController.createAdmin(..)) && " | ||
+ "!execution(* in.koreatech.koin.admin.user.controller.AdminUserController.adminPasswordChange(..)) && " | ||
+ "!execution(* in.koreatech.koin.admin.abtest.controller.AbtestController.assignOrGetAbtestVariable(..))") | ||
private void excludeSpecificMethods() { | ||
} | ||
|
||
@Around("allAdminControllers() && excludeGetMapping() && excludeSpecificMethods()") | ||
public Object logAdminActivity(ProceedingJoinPoint joinPoint) throws Throwable { | ||
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); | ||
String requestURI = request.getRequestURI(); | ||
String requestMethod = request.getMethod(); | ||
|
||
ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper)request; | ||
String requestMessage = new String(cachingRequest.getContentAsByteArray()); | ||
|
||
Object result = joinPoint.proceed(); | ||
|
||
Admin admin = adminRepository.getById(authContext.getUserId()); | ||
DomainInfo domainInfo = getDomainInfo(requestURI); | ||
|
||
adminActivityHistoryRepository.save(AdminActivityHistory.builder() | ||
.domainId(domainInfo.domainId()) | ||
.admin(admin) | ||
.requestMethod(requestMethod) | ||
.domainName(domainInfo.domainName()) | ||
.requestMessage(requestMessage) | ||
.build()); | ||
|
||
return result; | ||
} | ||
|
||
private DomainInfo getDomainInfo(String requestURI) { | ||
String[] segments = requestURI.split("/"); | ||
Integer domainId = null; | ||
String domainName = null; | ||
|
||
for (int i = segments.length - 1; i >= 0; i--) { | ||
String segment = segments[i]; | ||
|
||
if (isDomainType(segment)) { | ||
domainName = getDomainName(segment, segments, i); | ||
domainId = getDomainId(segments, i); | ||
break; | ||
} | ||
|
||
if (isCloseAbtest(segment, segments, i)) { | ||
domainName = segments[i - 1].toUpperCase(); | ||
domainId = Integer.valueOf(segments[i + 1]); | ||
break; | ||
} | ||
} | ||
|
||
return new DomainInfo(domainId, domainName); | ||
} | ||
|
||
private boolean isDomainType(String segment) { | ||
return EnumUtils.isValidEnumIgnoreCase(DomainType.class, segment); | ||
} | ||
|
||
private String getDomainName(String segment, String[] segments, int index) { | ||
String domainName = segment.toUpperCase(); | ||
|
||
if (SEGMENT_SHOPS.equals(domainName) && SEGMENT_BENEFIT.equals(segments[index - 2])) { | ||
return segments[index - 2].toUpperCase(); | ||
} | ||
|
||
if (SEGMENT_CATEGORIES.equals(domainName)) { | ||
return (segments[index - 1] + domainName).toUpperCase(); | ||
} | ||
|
||
return domainName; | ||
} | ||
|
||
private Integer getDomainId(String[] segments, int index) { | ||
if (index != segments.length - 1 && segments[index + 1].matches(REGEX_NUMERIC)) { | ||
return Integer.valueOf(segments[index + 1]); | ||
} | ||
return null; | ||
} | ||
|
||
private boolean isCloseAbtest(String segment, String[] segments, int index) { | ||
return SEGMENT_CLOSE.equals(segment) && SEGMENT_ABTEST.equals(segments[index - 1]); | ||
} | ||
|
||
private record DomainInfo(Integer domainId, String domainName) { | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/java/in/koreatech/koin/admin/history/controller/HistoryApi.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,57 @@ | ||
package in.koreatech.koin.admin.history.controller; | ||
|
||
import static in.koreatech.koin.domain.user.model.UserType.ADMIN; | ||
|
||
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.RequestParam; | ||
|
||
import in.koreatech.koin.admin.history.dto.AdminHistoryResponse; | ||
import in.koreatech.koin.admin.history.dto.AdminHistorysResponse; | ||
import in.koreatech.koin.global.auth.Auth; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
|
||
@Tag(name = "(Admin) History: 기록", description = "관리자 기록 관련 API") | ||
public interface HistoryApi { | ||
@ApiResponses( | ||
value = { | ||
@ApiResponse(responseCode = "200"), | ||
@ApiResponse(responseCode = "400", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), | ||
} | ||
) | ||
@Operation(summary = "히스토리 리스트 조회") | ||
@GetMapping("/admin/historys") | ||
ResponseEntity<AdminHistorysResponse> getHistorys( | ||
@RequestParam(required = false) Integer page, | ||
@RequestParam(required = false) Integer limit, | ||
@RequestParam(required = false) String requestMethod, | ||
@RequestParam(required = false) String domainName, | ||
@RequestParam(required = false) Integer domainId, | ||
@Auth(permit = {ADMIN}) Integer adminId | ||
); | ||
|
||
@ApiResponses( | ||
value = { | ||
@ApiResponse(responseCode = "200"), | ||
@ApiResponse(responseCode = "400", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), | ||
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), | ||
} | ||
) | ||
@Operation(summary = "히스토리 단건 조회") | ||
@GetMapping("/admin/history/{id}") | ||
ResponseEntity<AdminHistoryResponse> getHistory( | ||
@PathVariable(name = "id") Integer id, | ||
@Auth(permit = {ADMIN}) Integer adminId | ||
); | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/in/koreatech/koin/admin/history/controller/HistoryController.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,47 @@ | ||
package in.koreatech.koin.admin.history.controller; | ||
|
||
import static in.koreatech.koin.domain.user.model.UserType.ADMIN; | ||
|
||
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.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import in.koreatech.koin.admin.history.dto.AdminHistoryResponse; | ||
import in.koreatech.koin.admin.history.dto.AdminHistorysCondition; | ||
import in.koreatech.koin.admin.history.dto.AdminHistorysResponse; | ||
import in.koreatech.koin.admin.history.service.HistoryService; | ||
import in.koreatech.koin.global.auth.Auth; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class HistoryController implements HistoryApi { | ||
|
||
private final HistoryService historyService; | ||
|
||
@GetMapping("/admin/historys") | ||
public ResponseEntity<AdminHistorysResponse> getHistorys( | ||
@RequestParam(required = false) Integer page, | ||
@RequestParam(required = false) Integer limit, | ||
@RequestParam(required = false) String requestMethod, | ||
@RequestParam(required = false) String domainName, | ||
@RequestParam(required = false) Integer domainId, | ||
@Auth(permit = {ADMIN}) Integer adminId | ||
) { | ||
AdminHistorysCondition adminHistorysCondition = new AdminHistorysCondition(page, limit, requestMethod, | ||
domainName, domainId); | ||
AdminHistorysResponse historys = historyService.getHistorys(adminHistorysCondition); | ||
return ResponseEntity.ok(historys); | ||
} | ||
|
||
@GetMapping("/admin/history/{id}") | ||
public ResponseEntity<AdminHistoryResponse> getHistory( | ||
@PathVariable(name = "id") Integer id, | ||
@Auth(permit = {ADMIN}) Integer adminId | ||
) { | ||
AdminHistoryResponse history = historyService.getHistory(id); | ||
return ResponseEntity.ok(history); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
src/main/java/in/koreatech/koin/admin/history/dto/AdminHistoryResponse.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,58 @@ | ||
package in.koreatech.koin.admin.history.dto; | ||
|
||
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; | ||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
import com.fasterxml.jackson.annotation.JsonFormat; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
import in.koreatech.koin.admin.history.enums.DomainType; | ||
import in.koreatech.koin.admin.history.enums.HttpMethodType; | ||
import in.koreatech.koin.admin.history.model.AdminActivityHistory; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@JsonNaming(value = SnakeCaseStrategy.class) | ||
public record AdminHistoryResponse( | ||
@Schema(description = "고유 id", example = "1", requiredMode = REQUIRED) | ||
Integer id, | ||
|
||
@Schema(description = "도메인 엔티티 id", example = "null", requiredMode = REQUIRED) | ||
Integer domainId, | ||
|
||
@Schema(description = "이름", example = "신관규", requiredMode = REQUIRED) | ||
String name, | ||
|
||
@Schema(description = "도메인 이름", example = "코인 공지", requiredMode = REQUIRED) | ||
String domainName, | ||
|
||
@Schema(description = "HTTP 요청 메소드 종류", example = "생성", requiredMode = REQUIRED) | ||
String requestMethod, | ||
|
||
@Schema(description = "HTTP 요청 메시지 바디", example = """ | ||
{ | ||
"title": "제목 예시", | ||
"content": "본문 내용 예시" | ||
} | ||
""", | ||
requiredMode = REQUIRED | ||
) | ||
String requestMessage, | ||
|
||
@Schema(description = "요청 시간", example = "2019-08-16-23-01-52", requiredMode = REQUIRED) | ||
@JsonFormat(pattern = "yyyy-MM-dd-HH-mm-ss") | ||
LocalDateTime createdAt | ||
) { | ||
public static AdminHistoryResponse from(AdminActivityHistory adminActivityHistory) { | ||
return new AdminHistoryResponse( | ||
adminActivityHistory.getId(), | ||
adminActivityHistory.getDomainId(), | ||
adminActivityHistory.getAdmin().getUser().getName(), | ||
DomainType.valueOf(adminActivityHistory.getDomainName()).getDescription(), | ||
HttpMethodType.valueOf(adminActivityHistory.getRequestMethod()).getValue(), | ||
adminActivityHistory.getRequestMessage(), | ||
adminActivityHistory.getCreatedAt() | ||
); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/in/koreatech/koin/admin/history/dto/AdminHistorysCondition.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 in.koreatech.koin.admin.history.dto; | ||
|
||
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; | ||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; | ||
|
||
import java.util.Objects; | ||
|
||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
import in.koreatech.koin.global.model.Criteria; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@JsonNaming(value = SnakeCaseStrategy.class) | ||
public record AdminHistorysCondition( | ||
@Schema(description = "페이지", example = "1", defaultValue = "1", requiredMode = NOT_REQUIRED) | ||
Integer page, | ||
|
||
@Schema(description = "페이지당 조회할 최대 개수", example = "10", defaultValue = "10", requiredMode = NOT_REQUIRED) | ||
Integer limit, | ||
|
||
@Schema(description = "HTTP 메소드", example = "POST", requiredMode = NOT_REQUIRED) | ||
String requestMethod, | ||
|
||
@Schema(description = "도메인 이름", example = "NOTICE", requiredMode = NOT_REQUIRED) | ||
String domainName, | ||
|
||
@Schema(description = "특정 엔티티 id", requiredMode = NOT_REQUIRED) | ||
Integer domainId | ||
) { | ||
public AdminHistorysCondition { | ||
if (Objects.isNull(page)) { | ||
page = Criteria.DEFAULT_PAGE; | ||
} | ||
if (Objects.isNull(limit)) { | ||
limit = Criteria.DEFAULT_LIMIT; | ||
} | ||
} | ||
} |
Oops, something went wrong.