문서 작업 자동화 api

Jeongho Kim·2024년 3월 17일

ad-platform

목록 보기
1/2

보안상 이유로 회사 코드와는 다르다.

리플렉션을 이용해 문서 자동화

  • 스프링 부트 애플리케이션 내의 컨트롤러에서 사용되는 주요 어노테이션 정보(@RequestMapping, @RequestBody, @ResponseBody, @RequestParam)를 분석하고,
  • 이 정보를 프로젝트 디렉토리 내의 CSV 파일과 비교하여 일치하지 않는 경우를 찾아 냈다.

파일 구조

프로젝트 구조: 컨트롤러 이름으로 된 디렉토리가 있으며, 각 디렉토리 안에는 urls.csv, RequestBody.csv, ResponseBody.csv, RequestParam.csv 파일이 있다.

코드

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

@RestController
public class VerificationController {

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    @GetMapping("/api/verify")
    public ResponseEntity<?> verifyControllerAgainstCsv() {
        Map<String, Object> result = new HashMap<>();
        handlerMapping.getHandlerMethods().forEach((info, handlerMethod) -> {
            Method method = handlerMethod.getMethod();
            String controllerName = method.getDeclaringClass().getSimpleName();

            try {
                List<String> verificationMessages = analyzeAndCompareControllerDetails(method, controllerName);
                if (!verificationMessages.isEmpty()) {
                    result.put(controllerName, verificationMessages);
                }
            } catch (Exception e) {
                result.put(controllerName, List.of("Error during verification: " + e.getMessage()));
            }
        });

        return ResponseEntity.ok(result);
    }
    
    
    private List<String> analyzeAndCompareControllerDetails(Method method, String controllerName) throws Exception {
        ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        List<String> messages = new ArrayList<>();

        List<String> requestParams = Arrays.stream(method.getParameters())
                .filter(p -> p.isAnnotationPresent(RequestParam.class))
                .map(Parameter::getName)
                .collect(Collectors.toList());
        List<String> requestBodyTypes = Arrays.stream(method.getParameters())
                .filter(p -> p.isAnnotationPresent(RequestBody.class))
                .map(p -> p.getType().getSimpleName())
                .collect(Collectors.toList());
        String responseBodyType = method.getReturnType().getSimpleName();
        
       messages.addAll(compareWithCsv(controllerName, "RequestParam.csv", requestParams));
	messages.addAll(compareWithCsv(controllerName, "RequestBody.csv", requestBodyTypes));
	messages.addAll(compareWithCsv(controllerName, "ResponseBody.csv", Collections.singletonList(responseBodyType)));

        return messages;
    }

    private List<String> compareWithCsv(String controllerName, String csvFileName, List<String> expectedValues) throws Exception {
        List<String> messages = new ArrayList<>();
        Path csvFilePath = Paths.get("src/main/resources/" + controllerName + "/" + csvFileName);

        if (Files.exists(csvFilePath)) {
            List<String> csvLines = Files.readAllLines(csvFilePath);
            List<String> csvValues = csvLines.stream().flatMap(line -> Arrays.stream(line.split(","))).collect(Collectors.toList());

            if (!Objects.equals(csvValues, expectedValues)) {
                messages.add("Mismatch in " + csvFileName + ": Expected " + expectedValues + ", Found in CSV " + csvValues);
            }
        } else {
            messages.add(csvFileName + " not found for " + controllerName);
        }

        return messages;
    }
}

스프링 부트 애플리케이션 시작 시 모든 컨트롤러의 메서드를 분석하는 코드다.
먼저 @RequestParam, @RequestBody, @ResponseBody 어노테이션의 사용 여부와 상세 내용을 체크한다.
그 후, 이 정보를 해당 컨트롤러 디렉토리 내의 CSV 파일과 비교하여 일치 여부를 검증한다.
일치하지 않는 경우, 콘솔에 메시지를 출력한다.

실제 코드와 차이점

세부 구현 사항에 차이가 있다. directory 구조도 차이가 있다.
그리고 실제 코드에선 Class 이름뿐만 아니라, 필드값들도 다 비교했다.
구현하면서 id로 추론하는 방법과, type과 순서로 추론하는 방법이 이런 구현을 하다가 나왔겠구나 싶었다.

0개의 댓글