Springboot - 两个用户在调用与外部 API 交互的 EndPoint 时发生冲突

问题描述 投票:0回答:0

所以我有一个编译器微服务。该服务的主要职责是编译用户发送的代码片段。这是控制器类。

package codenemy.api.Compiler.Controller;

import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Service.CompilerService;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.service.ProblemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * @author chaudhary samir zafar
 * @version 1.0
 * @since 01/01/2023
 */
@RestController
@RequestMapping(path = "/api/compiler")
@CrossOrigin
public class CompilerController {
    private final ProblemService problemService;
    private final CompilerService compilerService;

    @Autowired
    public CompilerController(ProblemService problemService, CompilerService compilerService){
        this.problemService = problemService;
        this.compilerService = compilerService;
    }
    
    @PostMapping(path = "/runAllTestCases")
    public ResponseEntity<MultipleTestCaseResults> runScriptForAllTestCases(@RequestBody Request request) {

        return ResponseEntity.ok().body(compilerService.runScriptForAllTestCases(request, problemService.getProblem(request.problemId())));
    }

    @PostMapping(path = "/runAllTestCasesChallenge")
    public ResponseEntity<MultipleTestCaseResults> runScriptForAllTestCasesWithChallenge(@RequestBody Request request) {

        return ResponseEntity.ok().body(compilerService.runScriptForAllTestCasesWithChallenge(request, problemService.getProblem(request.problemId())));
    }

    @PostMapping(path = "/runSingleTestCase")
    public ResponseEntity<SingleTestCaseResult> runScriptForOneTestCaseVersionTwo(@RequestBody Request request) {

        Problem problem = problemService.getProblem(request.problemId());

        SingleTestCaseResult singleTestCaseResult = compilerService.runScriptForOneTestCase(request, problem);

        return ResponseEntity.ok().body(singleTestCaseResult);
    }
}

所以在用户点击这些端点之一之后,该控制器与 CompilerService 交互。此 CompilerService 为每个端点检索正确的 LanguageCompilerService,例如,假设用户想要编译 Java,因此此服务将检索一个名为 JavaCompilerService 的对象并调用该对象上的相应方法。如果用户想要保存提交,那么将包含/运行额外的逻辑。


import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Service.LanguageCompilerService.LanguageCompilerServiceIF;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.model.ProblemLanguage;
import codenemy.api.Submission.Model.Submission;
import codenemy.api.Submission.Service.SubmissionService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * @author chaudhary samir zafar
 * @version 1.0
 * @since 01/01/2023
 */
@Service
@AllArgsConstructor
public class CompilerService {
    private CompilerServiceFactory compilerServiceFactory;
    private SubmissionService submissionService;
    private ObjectMapper objectMapper;

    public SingleTestCaseResult runScriptForOneTestCase(Request request, Problem problem) {
        // Retrieve the correct and appropriate compiler service
        LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());

        // First find the problem Lang
        ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());

        return languageCompilerServiceIF.executeSingleTestCase(request, problem, problemLanguage);
    }

    public MultipleTestCaseResults runScriptForAllTestCases(Request request, Problem problem) {
        // Retrieve the correct and appropriate compiler service
        LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());

        // First find the problem Lang
        ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());

        MultipleTestCaseResults multipleTestCaseResults =
                languageCompilerServiceIF.executeAllTestCases(request, problem, problemLanguage);
        String json;

        if (multipleTestCaseResults.getError() == null) {
            try {
                json = objectMapper.writeValueAsString(multipleTestCaseResults);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        else {
            json = "Code did not compile";
        }

        submissionService.addSubmission(
                new Submission(0,
                        null,
                        problem,
                        LocalDateTime.now(),
                        json,
                        multipleTestCaseResults.getPercentage(),
                        multipleTestCaseResults.getPoints()),
                request.userId(),
                multipleTestCaseResults);

        return multipleTestCaseResults;
    }

    public MultipleTestCaseResults runScriptForAllTestCasesWithChallenge(Request request, Problem problem) {
        // Retrieve the correct and appropriate compiler service
        LanguageCompilerServiceIF languageCompilerServiceIF = compilerServiceFactory.getSpecificLanguageCompilerService(request.language());

        // First find the problem Lang
        ProblemLanguage problemLanguage = findProblemLanguageSelected(problem, request.language());

        return languageCompilerServiceIF.executeAllTestCases(request, problem, problemLanguage);
    }

    private ProblemLanguage findProblemLanguageSelected(Problem problem, String selectedLang){
        return problem.getProblemLanguages()
                        .stream()
                        .filter(element -> element.getLanguage().getProgrammingLanguage().equalsIgnoreCase(selectedLang))
                        .findAny()
                        .get();
    }
}

这里是 JavaCompilerService 的实现。

package codenemy.api.Compiler.Service.LanguageCompilerService;

import codenemy.api.Compiler.Model.MultipleTestCaseResults;
import codenemy.api.Compiler.Model.Request;
import codenemy.api.Compiler.Model.SingleTestCaseResult;
import codenemy.api.Compiler.Model.TestCaseResult;
import codenemy.api.Problem.model.Problem;
import codenemy.api.Problem.model.ProblemLanguage;
import codenemy.api.Util.CompilerUtility;
import lombok.AllArgsConstructor;

import java.util.Scanner;

/**
 * @author chaudhary samir zafar
 * @version 1.0
 * @since 01/01/2023
 */
@AllArgsConstructor
public class JavaCompilerService implements LanguageCompilerServiceIF {
    CompilerUtility compilerUtil;
    private static final String JAVA_VERSION = "15.0.2";

    @Override
    public SingleTestCaseResult executeSingleTestCase(Request request, Problem problem, ProblemLanguage problemLanguage) {
        TestCaseResult testCaseResult =
                compilerUtil.getTestCaseResult
                        ("TestRun.java", buildFinalScript(request, problemLanguage.getTestRunOne()), request.language(), JAVA_VERSION);

        if (testCaseResult.getError().size() > 0) {
            return new SingleTestCaseResult(null, null, null, null, false, testCaseResult.getError());
        }

        return compilerUtil.calculateSingleTestResultWithResponse(problem, testCaseResult);
    }

    @Override
    public MultipleTestCaseResults executeAllTestCases(Request request, Problem problem, ProblemLanguage problemLanguage) {
        TestCaseResult testCaseResult =
                compilerUtil.getTestCaseResult
                        ("TestRun.java", buildFinalScript(request, problemLanguage.getTestRunAll()), request.language(), JAVA_VERSION);

        if (testCaseResult.getError().size() > 0) {
           MultipleTestCaseResults multipleTestCaseResults = new MultipleTestCaseResults();
           multipleTestCaseResults.setError(testCaseResult.getError());
           return multipleTestCaseResults;
        }

        return compilerUtil.calculateAllTestResultsWithResponse(problem, testCaseResult);
    }

    private String buildFinalScript(Request request, String testRunOne) {
        Scanner scanner = new Scanner(request.script());
        StringBuilder importStatements = new StringBuilder();
        StringBuilder code = new StringBuilder();

        while (scanner.hasNext()){
            String newLine = scanner.nextLine();
            if (newLine.startsWith("import")) {
                importStatements.append(newLine).append("\r\n");
            } else {
                code.append(newLine).append("\r\n");
            }
        }

        scanner.close();

        return importStatements + testRunOne + code;
    }


}

JavaCompilerService 与 CompilerUtility 类交互。这就是我认为问题发生的地方。

  public TestCaseResult getTestCaseResult(String fileName, String fileContent, String programmingLang, String version) {
        TestCaseResult testCaseResult = new TestCaseResult(0, null);

        List<PistonFile> fileArrayList = List.of(new PistonFile(fileName, fileContent));
        PistonRequest pistonRequest = new PistonRequest(programmingLang, fileArrayList);
        pistonRequest.setVersion(version);

        PistonResponse pistonResponse = makeRequest(pistonRequest);

        if (pistonResponse == null) {
            log.info("The request to piston is breaking.");
            return null;
        }

        if (pistonResponse.run().signal() != null && pistonResponse.run().signal().equalsIgnoreCase("SIGKILL")) {
            testCaseResult.setError(List.of("Your code exceeded the runtime time limit\n"));
            return testCaseResult;
        }

        if (!pistonResponse.run().stderr().isEmpty() || !pistonResponse.run().stderr().isBlank()) {
            // Get the errors.
            List<String> errors = Arrays.asList(pistonResponse.run().stderr().split("\n"));
            testCaseResult.setError(errors);
            return testCaseResult;
        }

        String[] arrOfStdOut = pistonResponse.run().stdout().split("\n");
        ArrayList<String> result = new ArrayList<>();
        ArrayList<String> output = new ArrayList<>();

        for(String s : arrOfStdOut) {
            if (s.startsWith("Result - ")) {
                result.add(s.replace("Result - ", ""));
            } else {
                output.add(s + "\n");
            }
        }

        testCaseResult.setOutput(output);
        testCaseResult.setResult(result);

        return testCaseResult;
    }

    public PistonResponse makeRequest(PistonRequest requestData) {
        String url = "https://emkc.org/api/v2/piston/execute";

        URL obj = null;
        HttpURLConnection con = null;

        try {
            obj = new URL(url);
            con = (HttpURLConnection) obj.openConnection();

            // Set the request method and headers
            con.setRequestMethod("POST");
            con.setRequestProperty("Content-Type", "application/json");

            ObjectMapper objectMapper = new ObjectMapper();

            // Add throttling
            Thread.sleep(3000); // wait for 1 second before sending the request

            // Set the request body
            String requestBody = objectMapper.writeValueAsString(requestData);
            con.setDoOutput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.writeBytes(requestBody);
            wr.flush();
            wr.close();

            // Get the response
            int responseCode = con.getResponseCode();

            // Check if the response is okay
            if (responseCode == HttpStatus.OK.value()) {
                BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;
                StringBuilder response = new StringBuilder();
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }

                in.close();

                // Deserialize the response
                return objectMapper.readValue(response.toString(), PistonResponse.class);
            }
        }
        catch (Exception exception) {
            log.info(exception.getMessage());
        }

        return null;
    }

调用 getTestCaseResult 方法时,也会调用其中的 makeRequest。如果两个用户同时调用同一个端点,那么其中一个用户的 pistonResponse 返回为 null。我在 makeRequest 中进行了外部 API 调用,我相信当两个请求想要访问该代码时,它会中断其中一个连接。

这只是我对问题的理解,可能是其他原因出错。我不知道真正的根本原因和解决方案。

我认为通过实现队列来使用 Java 并发可以解决问题,但我不太熟悉它,所以进展不顺利。

java spring spring-boot java.util.concurrent
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.