Skip to content

Commit

Permalink
Merge pull request #1003 from BCSDLab/develop
Browse files Browse the repository at this point in the history
develop -> main
  • Loading branch information
BaeJinho4028 authored Nov 7, 2024
2 parents 26c0d73 + 9e81d6d commit 06ffdf1
Show file tree
Hide file tree
Showing 102 changed files with 2,883 additions and 421 deletions.
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) {
}
}
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
);
}
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);
}
}
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()
);
}
}
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;
}
}
}
Loading

0 comments on commit 06ffdf1

Please sign in to comment.