ai + blog
This commit is contained in:
@@ -7,7 +7,12 @@
|
|||||||
"Bash(mvn dependency:tree:*)",
|
"Bash(mvn dependency:tree:*)",
|
||||||
"Bash(mvn spring-javaformat:apply:*)",
|
"Bash(mvn spring-javaformat:apply:*)",
|
||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(git commit:*)"
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" status)",
|
||||||
|
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" checkout 3da91e5 -- aioj-backend-ai-service/)",
|
||||||
|
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" push)",
|
||||||
|
"Bash(mvn compile:*)",
|
||||||
|
"Bash(mvn clean install:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.idea/encodings.xml
generated
1
.idea/encodings.xml
generated
@@ -6,6 +6,7 @@
|
|||||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-auth/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-auth/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/aioj-backend-blog-service/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-bom/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-bom/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 服务启动类
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(scanBasePackages = "cn.meowrain.aioj")
|
||||||
|
public class AIServiceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AIServiceApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.client;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.grpc.*;
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Service gRPC 客户端封装类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class AIServiceGrpcClient {
|
||||||
|
|
||||||
|
private final AIServiceGrpc.AIServiceBlockingStub blockingStub;
|
||||||
|
private final AIServiceGrpc.AIServiceStub asyncStub;
|
||||||
|
|
||||||
|
public AIServiceGrpcClient(
|
||||||
|
@Qualifier("aiServiceBlockingStub") AIServiceGrpc.AIServiceBlockingStub blockingStub,
|
||||||
|
@Qualifier("aiServiceStub") AIServiceGrpc.AIServiceStub asyncStub) {
|
||||||
|
this.blockingStub = blockingStub;
|
||||||
|
this.asyncStub = asyncStub;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析代码
|
||||||
|
*/
|
||||||
|
public AnalyzeCodeResponse analyzeCode(String code, String language, String questionId, String userId) {
|
||||||
|
try {
|
||||||
|
log.info("Calling gRPC analyzeCode for language: {}, questionId: {}", language, questionId);
|
||||||
|
|
||||||
|
AnalyzeCodeRequest request = AnalyzeCodeRequest.newBuilder()
|
||||||
|
.setCode(code)
|
||||||
|
.setLanguage(language)
|
||||||
|
.setQuestionId(questionId)
|
||||||
|
.setUserId(userId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalyzeCodeResponse response = blockingStub.analyzeCode(request);
|
||||||
|
|
||||||
|
log.info("gRPC analyzeCode response: success={}", response.getSuccess());
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
log.error("gRPC analyzeCode failed: {}", e.getStatus(), e);
|
||||||
|
return AnalyzeCodeResponse.newBuilder()
|
||||||
|
.setSuccess(false)
|
||||||
|
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化代码
|
||||||
|
*/
|
||||||
|
public OptimizeCodeResponse optimizeCode(String code, String language, String optimizationType) {
|
||||||
|
try {
|
||||||
|
log.info("Calling gRPC optimizeCode for language: {}, type: {}", language, optimizationType);
|
||||||
|
|
||||||
|
OptimizeCodeRequest request = OptimizeCodeRequest.newBuilder()
|
||||||
|
.setCode(code)
|
||||||
|
.setLanguage(language)
|
||||||
|
.setOptimizationType(optimizationType)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
OptimizeCodeResponse response = blockingStub.optimizeCode(request);
|
||||||
|
|
||||||
|
log.info("gRPC optimizeCode response: success={}", response.getSuccess());
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
log.error("gRPC optimizeCode failed: {}", e.getStatus(), e);
|
||||||
|
return OptimizeCodeResponse.newBuilder()
|
||||||
|
.setSuccess(false)
|
||||||
|
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例
|
||||||
|
*/
|
||||||
|
public GenerateTestCasesResponse generateTestCases(String code, String language, String problemDescription) {
|
||||||
|
try {
|
||||||
|
log.info("Calling gRPC generateTestCases for language: {}", language);
|
||||||
|
|
||||||
|
GenerateTestCasesRequest request = GenerateTestCasesRequest.newBuilder()
|
||||||
|
.setCode(code)
|
||||||
|
.setLanguage(language)
|
||||||
|
.setProblemDescription(problemDescription)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GenerateTestCasesResponse response = blockingStub.generateTestCases(request);
|
||||||
|
|
||||||
|
log.info("gRPC generateTestCases response: success={}", response.getSuccess());
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
log.error("gRPC generateTestCases failed: {}", e.getStatus(), e);
|
||||||
|
return GenerateTestCasesResponse.newBuilder()
|
||||||
|
.setSuccess(false)
|
||||||
|
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解释代码
|
||||||
|
*/
|
||||||
|
public ExplainCodeResponse explainCode(String code, String language, String detailLevel) {
|
||||||
|
try {
|
||||||
|
log.info("Calling gRPC explainCode for language: {}, level: {}", language, detailLevel);
|
||||||
|
|
||||||
|
ExplainCodeRequest request = ExplainCodeRequest.newBuilder()
|
||||||
|
.setCode(code)
|
||||||
|
.setLanguage(language)
|
||||||
|
.setDetailLevel(detailLevel)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ExplainCodeResponse response = blockingStub.explainCode(request);
|
||||||
|
|
||||||
|
log.info("gRPC explainCode response: success={}", response.getSuccess());
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
log.error("gRPC explainCode failed: {}", e.getStatus(), e);
|
||||||
|
return ExplainCodeResponse.newBuilder()
|
||||||
|
.setSuccess(false)
|
||||||
|
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.config;
|
||||||
|
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.grpc.AIServiceGrpc;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gRPC 客户端配置
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class GrpcClientConfiguration {
|
||||||
|
|
||||||
|
private final GrpcClientProperties properties;
|
||||||
|
private ManagedChannel channel;
|
||||||
|
|
||||||
|
public GrpcClientConfiguration(GrpcClientProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 gRPC ManagedChannel
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@Qualifier("aiServiceChannel")
|
||||||
|
public ManagedChannel aiServiceChannel() {
|
||||||
|
log.info("Initializing gRPC channel to {}:{}", properties.getHost(), properties.getPort());
|
||||||
|
|
||||||
|
NettyChannelBuilder builder = NettyChannelBuilder
|
||||||
|
.forAddress(properties.getHost(), properties.getPort())
|
||||||
|
.maxInboundMessageSize(properties.getMaxMessageSize());
|
||||||
|
|
||||||
|
if (properties.isTlsEnabled()) {
|
||||||
|
builder.useTransportSecurity();
|
||||||
|
} else {
|
||||||
|
builder.usePlaintext();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channel = builder.build();
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 AI Service gRPC 客户端存根
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AIServiceGrpc.AIServiceBlockingStub aiServiceBlockingStub(
|
||||||
|
@Qualifier("aiServiceChannel") ManagedChannel channel) {
|
||||||
|
log.info("Creating AI Service gRPC blocking stub");
|
||||||
|
return AIServiceGrpc.newBlockingStub(channel)
|
||||||
|
.withDeadlineAfter(properties.getTimeout(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 AI Service gRPC 异步客户端存根
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AIServiceGrpc.AIServiceStub aiServiceStub(
|
||||||
|
@Qualifier("aiServiceChannel") ManagedChannel channel) {
|
||||||
|
log.info("Creating AI Service gRPC async stub");
|
||||||
|
return AIServiceGrpc.newStub(channel)
|
||||||
|
.withDeadlineAfter(properties.getTimeout(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用关闭时清理资源
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
if (channel != null && !channel.isShutdown()) {
|
||||||
|
log.info("Shutting down gRPC channel");
|
||||||
|
try {
|
||||||
|
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("Error shutting down gRPC channel", e);
|
||||||
|
channel.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gRPC 客户端配置属性
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "grpc.client")
|
||||||
|
public class GrpcClientProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gRPC 服务器地址
|
||||||
|
*/
|
||||||
|
private String host = "localhost";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gRPC 服务器端口
|
||||||
|
*/
|
||||||
|
private int port = 50051;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接超时时间(秒)
|
||||||
|
*/
|
||||||
|
private int timeout = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用 TLS
|
||||||
|
*/
|
||||||
|
private boolean tlsEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大消息大小(字节)
|
||||||
|
*/
|
||||||
|
private int maxMessageSize = 10 * 1024 * 1024; // 10MB
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(int timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTlsEnabled() {
|
||||||
|
return tlsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTlsEnabled(boolean tlsEnabled) {
|
||||||
|
this.tlsEnabled = tlsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxMessageSize() {
|
||||||
|
return maxMessageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxMessageSize(int maxMessageSize) {
|
||||||
|
this.maxMessageSize = maxMessageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger 配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI customOpenAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("AIOJ AI 服务 API")
|
||||||
|
.version("1.0.0")
|
||||||
|
.description("AI 代码分析、优化、测试用例生成等服务接口")
|
||||||
|
.contact(new Contact()
|
||||||
|
.name("AIOJ Team")
|
||||||
|
.email("contact@aioj.com"))
|
||||||
|
.license(new License()
|
||||||
|
.name("Apache 2.0")
|
||||||
|
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.controller;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.dto.resp.*;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.service.AIService;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||||
|
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 服务控制器
|
||||||
|
*/
|
||||||
|
@Tag(name = "AI 服务", description = "AI 代码分析、优化、测试用例生成等接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/ai")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AIController {
|
||||||
|
|
||||||
|
private final AIService aiService;
|
||||||
|
|
||||||
|
@PostMapping("/analyze")
|
||||||
|
@Operation(summary = "分析代码", description = "对提交的代码进行分析,包括复杂度、性能、可读性等评分")
|
||||||
|
public Result<AnalyzeCodeRespDTO> analyzeCode(@Valid @RequestBody AnalyzeCodeReqDTO request) {
|
||||||
|
AnalyzeCodeRespDTO response = (AnalyzeCodeRespDTO) aiService.analyzeCode(request);
|
||||||
|
return Results.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/optimize")
|
||||||
|
@Operation(summary = "优化代码", description = "对代码进行优化,提升性能、可读性或内存使用")
|
||||||
|
public Result<OptimizeCodeRespDTO> optimizeCode(@Valid @RequestBody OptimizeCodeReqDTO request) {
|
||||||
|
OptimizeCodeRespDTO response = (OptimizeCodeRespDTO) aiService.optimizeCode(request);
|
||||||
|
return Results.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/test-cases")
|
||||||
|
@Operation(summary = "生成测试用例", description = "根据代码和问题描述自动生成测试用例")
|
||||||
|
public Result<GenerateTestCasesRespDTO> generateTestCases(@Valid @RequestBody GenerateTestCasesReqDTO request) {
|
||||||
|
GenerateTestCasesRespDTO response = (GenerateTestCasesRespDTO) aiService.generateTestCases(request);
|
||||||
|
return Results.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/explain")
|
||||||
|
@Operation(summary = "解释代码", description = "对代码进行详细解释,帮助理解代码逻辑")
|
||||||
|
public Result<ExplainCodeRespDTO> explainCode(@Valid @RequestBody ExplainCodeReqDTO request) {
|
||||||
|
ExplainCodeRespDTO response = (ExplainCodeRespDTO) aiService.explainCode(request);
|
||||||
|
return Results.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/health")
|
||||||
|
@Operation(summary = "健康检查", description = "检查 AI 服务是否正常运行")
|
||||||
|
public Result<String> health() {
|
||||||
|
return Results.success("AI Service is running");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码分析请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "代码分析请求")
|
||||||
|
public class AnalyzeCodeReqDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "代码不能为空")
|
||||||
|
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "def hello():\n print('Hello World')")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@NotBlank(message = "编程语言不能为空")
|
||||||
|
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
@Schema(description = "题目ID", example = "1001")
|
||||||
|
private String questionId;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID", example = "user123")
|
||||||
|
private String userId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码解释请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "代码解释请求")
|
||||||
|
public class ExplainCodeReqDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "代码不能为空")
|
||||||
|
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@NotBlank(message = "编程语言不能为空")
|
||||||
|
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
@Schema(description = "详细程度", example = "normal", allowableValues = {"brief", "normal", "detailed"})
|
||||||
|
private String detailLevel = "normal";
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "生成测试用例请求")
|
||||||
|
public class GenerateTestCasesReqDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "代码不能为空")
|
||||||
|
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@NotBlank(message = "编程语言不能为空")
|
||||||
|
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
@NotBlank(message = "问题描述不能为空")
|
||||||
|
@Schema(description = "问题描述", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String problemDescription;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码优化请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "代码优化请求")
|
||||||
|
public class OptimizeCodeReqDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "代码不能为空")
|
||||||
|
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@NotBlank(message = "编程语言不能为空")
|
||||||
|
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
@Schema(description = "优化类型", example = "performance", allowableValues = {"performance", "readability", "memory"})
|
||||||
|
private String optimizationType = "performance";
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码分析响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "代码分析响应")
|
||||||
|
public class AnalyzeCodeRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "响应消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "分析结果")
|
||||||
|
private CodeAnalysisRespDTO analysis;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码分析结果 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "代码分析结果")
|
||||||
|
public class CodeAnalysisRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "发现的问题")
|
||||||
|
private List<String> issues;
|
||||||
|
|
||||||
|
@Schema(description = "改进建议")
|
||||||
|
private List<String> suggestions;
|
||||||
|
|
||||||
|
@Schema(description = "复杂度评分 (0-100)")
|
||||||
|
private Integer complexityScore;
|
||||||
|
|
||||||
|
@Schema(description = "性能评分 (0-100)")
|
||||||
|
private Integer performanceScore;
|
||||||
|
|
||||||
|
@Schema(description = "可读性评分 (0-100)")
|
||||||
|
private Integer readabilityScore;
|
||||||
|
|
||||||
|
@Schema(description = "时间复杂度")
|
||||||
|
private String timeComplexity;
|
||||||
|
|
||||||
|
@Schema(description = "空间复杂度")
|
||||||
|
private String spaceComplexity;
|
||||||
|
|
||||||
|
@Schema(description = "最佳实践建议")
|
||||||
|
private List<String> bestPractices;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码解释响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "代码解释响应")
|
||||||
|
public class ExplainCodeRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "响应消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "代码解释")
|
||||||
|
private String explanation;
|
||||||
|
|
||||||
|
@Schema(description = "关键点")
|
||||||
|
private List<String> keyPoints;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "生成测试用例响应")
|
||||||
|
public class GenerateTestCasesRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "响应消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "测试用例列表")
|
||||||
|
private List<TestCaseDTO> testCases;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码优化响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "代码优化响应")
|
||||||
|
public class OptimizeCodeRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "响应消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "优化后的代码")
|
||||||
|
private String optimizedCode;
|
||||||
|
|
||||||
|
@Schema(description = "改进说明")
|
||||||
|
private List<String> improvements;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "测试用例")
|
||||||
|
public class TestCaseDTO {
|
||||||
|
|
||||||
|
@Schema(description = "输入")
|
||||||
|
private String input;
|
||||||
|
|
||||||
|
@Schema(description = "预期输出")
|
||||||
|
private String expectedOutput;
|
||||||
|
|
||||||
|
@Schema(description = "描述")
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.grpc;
|
||||||
|
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.Channel;
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.stub.AbstractStub;
|
||||||
|
import io.grpc.stub.ClientCalls;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Service gRPC 接口
|
||||||
|
*/
|
||||||
|
public class AIServiceGrpc {
|
||||||
|
|
||||||
|
private AIServiceGrpc() {}
|
||||||
|
|
||||||
|
public static final String SERVICE_NAME = "ai.service.AIService";
|
||||||
|
|
||||||
|
// 创建阻塞存根
|
||||||
|
public static AIServiceBlockingStub newBlockingStub(Channel channel) {
|
||||||
|
return new AIServiceBlockingStub(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建异步存根
|
||||||
|
public static AIServiceStub newStub(Channel channel) {
|
||||||
|
return new AIServiceStub(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻塞存根
|
||||||
|
*/
|
||||||
|
public static final class AIServiceBlockingStub extends AbstractStub<AIServiceBlockingStub> {
|
||||||
|
|
||||||
|
private AIServiceBlockingStub(Channel channel) {
|
||||||
|
super(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AIServiceBlockingStub(Channel channel, CallOptions callOptions) {
|
||||||
|
super(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AIServiceBlockingStub build(Channel channel, CallOptions callOptions) {
|
||||||
|
return new AIServiceBlockingStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析代码
|
||||||
|
*/
|
||||||
|
public AIServiceProto.AnalyzeCodeResponse analyzeCode(AIServiceProto.AnalyzeCodeRequest request) {
|
||||||
|
// 注意:这里需要真实的 gRPC 服务器才能调用
|
||||||
|
// 如果没有连接服务器,会抛出 StatusRuntimeException
|
||||||
|
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化代码
|
||||||
|
*/
|
||||||
|
public AIServiceProto.OptimizeCodeResponse optimizeCode(AIServiceProto.OptimizeCodeRequest request) {
|
||||||
|
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例
|
||||||
|
*/
|
||||||
|
public AIServiceProto.GenerateTestCasesResponse generateTestCases(AIServiceProto.GenerateTestCasesRequest request) {
|
||||||
|
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解释代码
|
||||||
|
*/
|
||||||
|
public AIServiceProto.ExplainCodeResponse explainCode(AIServiceProto.ExplainCodeRequest request) {
|
||||||
|
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步存根
|
||||||
|
*/
|
||||||
|
public static final class AIServiceStub extends AbstractStub<AIServiceStub> {
|
||||||
|
|
||||||
|
private AIServiceStub(Channel channel) {
|
||||||
|
super(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AIServiceStub(Channel channel, CallOptions callOptions) {
|
||||||
|
super(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AIServiceStub build(Channel channel, CallOptions callOptions) {
|
||||||
|
return new AIServiceStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析代码(异步)
|
||||||
|
*/
|
||||||
|
public void analyzeCode(AIServiceProto.AnalyzeCodeRequest request,
|
||||||
|
StreamObserver<AIServiceProto.AnalyzeCodeResponse> responseObserver) {
|
||||||
|
// 异步调用实现
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化代码(异步)
|
||||||
|
*/
|
||||||
|
public void optimizeCode(AIServiceProto.OptimizeCodeRequest request,
|
||||||
|
StreamObserver<AIServiceProto.OptimizeCodeResponse> responseObserver) {
|
||||||
|
// 异步调用实现
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例(异步)
|
||||||
|
*/
|
||||||
|
public void generateTestCases(AIServiceProto.GenerateTestCasesRequest request,
|
||||||
|
StreamObserver<AIServiceProto.GenerateTestCasesResponse> responseObserver) {
|
||||||
|
// 异步调用实现
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解释代码(异步)
|
||||||
|
*/
|
||||||
|
public void explainCode(AIServiceProto.ExplainCodeRequest request,
|
||||||
|
StreamObserver<AIServiceProto.ExplainCodeResponse> responseObserver) {
|
||||||
|
// 异步调用实现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamObserver 接口(简化版)
|
||||||
|
*/
|
||||||
|
public interface StreamObserver<V> {
|
||||||
|
void onNext(V value);
|
||||||
|
void onError(Throwable t);
|
||||||
|
void onCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.grpc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Service Proto 外部类
|
||||||
|
*/
|
||||||
|
public final class AIServiceProto {
|
||||||
|
private AIServiceProto() {}
|
||||||
|
|
||||||
|
// 代码分析请求
|
||||||
|
public static final class AnalyzeCodeRequest extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 implements
|
||||||
|
com.google.protobuf.Message {
|
||||||
|
|
||||||
|
private AnalyzeCodeRequest() {
|
||||||
|
this.code = "";
|
||||||
|
this.language = "";
|
||||||
|
this.questionId = "";
|
||||||
|
this.userId = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String language;
|
||||||
|
private String questionId;
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
public String getCode() { return code; }
|
||||||
|
public String getLanguage() { return language; }
|
||||||
|
public String getQuestionId() { return questionId; }
|
||||||
|
public String getUserId() { return userId; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public com.google.protobuf.Parser<AnalyzeCodeRequest> getParserForType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnalyzeCodeRequest getDefaultInstance() {
|
||||||
|
return new AnalyzeCodeRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnalyzeCodeRequest newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final AnalyzeCodeRequest result = new AnalyzeCodeRequest();
|
||||||
|
|
||||||
|
public Builder setCode(String value) {
|
||||||
|
result.code = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setLanguage(String value) {
|
||||||
|
result.language = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setQuestionId(String value) {
|
||||||
|
result.questionId = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setUserId(String value) {
|
||||||
|
result.userId = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalyzeCodeRequest build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码分析响应
|
||||||
|
public static final class AnalyzeCodeResponse extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private AnalyzeCodeResponse() {
|
||||||
|
this.success = false;
|
||||||
|
this.message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private CodeAnalysis analysis;
|
||||||
|
|
||||||
|
public boolean getSuccess() { return success; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public boolean hasAnalysis() { return analysis != null; }
|
||||||
|
public CodeAnalysis getAnalysis() { return analysis; }
|
||||||
|
|
||||||
|
public static AnalyzeCodeResponse newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final AnalyzeCodeResponse result = new AnalyzeCodeResponse();
|
||||||
|
|
||||||
|
public Builder setSuccess(boolean value) {
|
||||||
|
result.success = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMessage(String value) {
|
||||||
|
result.message = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setAnalysis(CodeAnalysis value) {
|
||||||
|
result.analysis = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalyzeCodeResponse build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码分析结果
|
||||||
|
public static final class CodeAnalysis extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private CodeAnalysis() {}
|
||||||
|
|
||||||
|
public java.util.List<String> getIssuesList() { return java.util.Collections.emptyList(); }
|
||||||
|
public java.util.List<String> getSuggestionsList() { return java.util.Collections.emptyList(); }
|
||||||
|
public int getComplexityScore() { return 0; }
|
||||||
|
public int getPerformanceScore() { return 0; }
|
||||||
|
public int getReadabilityScore() { return 0; }
|
||||||
|
public String getTimeComplexity() { return ""; }
|
||||||
|
public String getSpaceComplexity() { return ""; }
|
||||||
|
public java.util.List<String> getBestPracticesList() { return java.util.Collections.emptyList(); }
|
||||||
|
|
||||||
|
public static CodeAnalysis newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final CodeAnalysis result = new CodeAnalysis();
|
||||||
|
public CodeAnalysis build() { return result; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码优化请求
|
||||||
|
public static final class OptimizeCodeRequest extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private OptimizeCodeRequest() {
|
||||||
|
this.code = "";
|
||||||
|
this.language = "";
|
||||||
|
this.optimizationType = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String language;
|
||||||
|
private String optimizationType;
|
||||||
|
|
||||||
|
public String getCode() { return code; }
|
||||||
|
public String getLanguage() { return language; }
|
||||||
|
public String getOptimizationType() { return optimizationType; }
|
||||||
|
|
||||||
|
public static OptimizeCodeRequest newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final OptimizeCodeRequest result = new OptimizeCodeRequest();
|
||||||
|
|
||||||
|
public Builder setCode(String value) {
|
||||||
|
result.code = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setLanguage(String value) {
|
||||||
|
result.language = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setOptimizationType(String value) {
|
||||||
|
result.optimizationType = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptimizeCodeRequest build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码优化响应
|
||||||
|
public static final class OptimizeCodeResponse extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private OptimizeCodeResponse() {
|
||||||
|
this.success = false;
|
||||||
|
this.message = "";
|
||||||
|
this.optimizedCode = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private String optimizedCode;
|
||||||
|
private java.util.List<String> improvements = java.util.Collections.emptyList();
|
||||||
|
|
||||||
|
public boolean getSuccess() { return success; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public String getOptimizedCode() { return optimizedCode; }
|
||||||
|
public java.util.List<String> getImprovementsList() { return improvements; }
|
||||||
|
|
||||||
|
public static OptimizeCodeResponse newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final OptimizeCodeResponse result = new OptimizeCodeResponse();
|
||||||
|
|
||||||
|
public Builder setSuccess(boolean value) {
|
||||||
|
result.success = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMessage(String value) {
|
||||||
|
result.message = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setOptimizedCode(String value) {
|
||||||
|
result.optimizedCode = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setImprovementsList(java.util.List<String> value) {
|
||||||
|
result.improvements = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptimizeCodeResponse build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成测试用例请求
|
||||||
|
public static final class GenerateTestCasesRequest extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private GenerateTestCasesRequest() {
|
||||||
|
this.code = "";
|
||||||
|
this.language = "";
|
||||||
|
this.problemDescription = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String language;
|
||||||
|
private String problemDescription;
|
||||||
|
|
||||||
|
public String getCode() { return code; }
|
||||||
|
public String getLanguage() { return language; }
|
||||||
|
public String getProblemDescription() { return problemDescription; }
|
||||||
|
|
||||||
|
public static GenerateTestCasesRequest newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final GenerateTestCasesRequest result = new GenerateTestCasesRequest();
|
||||||
|
|
||||||
|
public Builder setCode(String value) {
|
||||||
|
result.code = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setLanguage(String value) {
|
||||||
|
result.language = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setProblemDescription(String value) {
|
||||||
|
result.problemDescription = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerateTestCasesRequest build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成测试用例响应
|
||||||
|
public static final class GenerateTestCasesResponse extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private GenerateTestCasesResponse() {
|
||||||
|
this.success = false;
|
||||||
|
this.message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private java.util.List<TestCase> testCases = java.util.Collections.emptyList();
|
||||||
|
|
||||||
|
public boolean getSuccess() { return success; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public java.util.List<TestCase> getTestCasesList() { return testCases; }
|
||||||
|
|
||||||
|
public static GenerateTestCasesResponse newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final GenerateTestCasesResponse result = new GenerateTestCasesResponse();
|
||||||
|
|
||||||
|
public Builder setSuccess(boolean value) {
|
||||||
|
result.success = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMessage(String value) {
|
||||||
|
result.message = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setTestCasesList(java.util.List<TestCase> value) {
|
||||||
|
result.testCases = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerateTestCasesResponse build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试用例
|
||||||
|
public static final class TestCase extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private TestCase() {
|
||||||
|
this.input = "";
|
||||||
|
this.expectedOutput = "";
|
||||||
|
this.description = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String input;
|
||||||
|
private String expectedOutput;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public String getInput() { return input; }
|
||||||
|
public String getExpectedOutput() { return expectedOutput; }
|
||||||
|
public String getDescription() { return description; }
|
||||||
|
|
||||||
|
public static TestCase newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final TestCase result = new TestCase();
|
||||||
|
|
||||||
|
public Builder setInput(String value) {
|
||||||
|
result.input = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setExpectedOutput(String value) {
|
||||||
|
result.expectedOutput = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDescription(String value) {
|
||||||
|
result.description = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestCase build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码解释请求
|
||||||
|
public static final class ExplainCodeRequest extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private ExplainCodeRequest() {
|
||||||
|
this.code = "";
|
||||||
|
this.language = "";
|
||||||
|
this.detailLevel = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String language;
|
||||||
|
private String detailLevel;
|
||||||
|
|
||||||
|
public String getCode() { return code; }
|
||||||
|
public String getLanguage() { return language; }
|
||||||
|
public String getDetailLevel() { return detailLevel; }
|
||||||
|
|
||||||
|
public static ExplainCodeRequest newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final ExplainCodeRequest result = new ExplainCodeRequest();
|
||||||
|
|
||||||
|
public Builder setCode(String value) {
|
||||||
|
result.code = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setLanguage(String value) {
|
||||||
|
result.language = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDetailLevel(String value) {
|
||||||
|
result.detailLevel = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExplainCodeRequest build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码解释响应
|
||||||
|
public static final class ExplainCodeResponse extends
|
||||||
|
com.google.protobuf.GeneratedMessageV3 {
|
||||||
|
|
||||||
|
private ExplainCodeResponse() {
|
||||||
|
this.success = false;
|
||||||
|
this.message = "";
|
||||||
|
this.explanation = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private String explanation;
|
||||||
|
private java.util.List<String> keyPoints = java.util.Collections.emptyList();
|
||||||
|
|
||||||
|
public boolean getSuccess() { return success; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public String getExplanation() { return explanation; }
|
||||||
|
public java.util.List<String> getKeyPointsList() { return keyPoints; }
|
||||||
|
|
||||||
|
public static ExplainCodeResponse newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final ExplainCodeResponse result = new ExplainCodeResponse();
|
||||||
|
|
||||||
|
public Builder setSuccess(boolean value) {
|
||||||
|
result.success = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMessage(String value) {
|
||||||
|
result.message = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setExplanation(String value) {
|
||||||
|
result.explanation = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyPointsList(java.util.List<String> value) {
|
||||||
|
result.keyPoints = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExplainCodeResponse build() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.service;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 服务接口
|
||||||
|
*/
|
||||||
|
public interface AIService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析代码
|
||||||
|
*/
|
||||||
|
Object analyzeCode(AnalyzeCodeReqDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化代码
|
||||||
|
*/
|
||||||
|
Object optimizeCode(OptimizeCodeReqDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成测试用例
|
||||||
|
*/
|
||||||
|
Object generateTestCases(GenerateTestCasesReqDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解释代码
|
||||||
|
*/
|
||||||
|
Object explainCode(ExplainCodeReqDTO request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package cn.meowrain.aioj.backend.aiservice.service.impl;
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.client.AIServiceGrpcClient;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.dto.resp.*;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.grpc.*;
|
||||||
|
import cn.meowrain.aioj.backend.aiservice.service.AIService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class AIServiceImpl implements AIService {
|
||||||
|
|
||||||
|
private final AIServiceGrpcClient grpcClient;
|
||||||
|
|
||||||
|
public AIServiceImpl(AIServiceGrpcClient grpcClient) {
|
||||||
|
this.grpcClient = grpcClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnalyzeCodeRespDTO analyzeCode(AnalyzeCodeReqDTO request) {
|
||||||
|
log.info("Analyzing code for language: {}, questionId: {}", request.getLanguage(), request.getQuestionId());
|
||||||
|
|
||||||
|
AnalyzeCodeResponse grpcResponse = grpcClient.analyzeCode(
|
||||||
|
request.getCode(),
|
||||||
|
request.getLanguage(),
|
||||||
|
request.getQuestionId() != null ? request.getQuestionId() : "",
|
||||||
|
request.getUserId() != null ? request.getUserId() : ""
|
||||||
|
);
|
||||||
|
|
||||||
|
CodeAnalysisRespDTO analysis = null;
|
||||||
|
if (grpcResponse.getSuccess() && grpcResponse.hasAnalysis()) {
|
||||||
|
CodeAnalysis grpcAnalysis = grpcResponse.getAnalysis();
|
||||||
|
analysis = CodeAnalysisRespDTO.builder()
|
||||||
|
.issues(grpcAnalysis.getIssuesList())
|
||||||
|
.suggestions(grpcAnalysis.getSuggestionsList())
|
||||||
|
.complexityScore(grpcAnalysis.getComplexityScore())
|
||||||
|
.performanceScore(grpcAnalysis.getPerformanceScore())
|
||||||
|
.readabilityScore(grpcAnalysis.getReadabilityScore())
|
||||||
|
.timeComplexity(grpcAnalysis.getTimeComplexity())
|
||||||
|
.spaceComplexity(grpcAnalysis.getSpaceComplexity())
|
||||||
|
.bestPractices(grpcAnalysis.getBestPracticesList())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnalyzeCodeRespDTO.builder()
|
||||||
|
.success(grpcResponse.getSuccess())
|
||||||
|
.message(grpcResponse.getMessage())
|
||||||
|
.analysis(analysis)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptimizeCodeRespDTO optimizeCode(OptimizeCodeReqDTO request) {
|
||||||
|
log.info("Optimizing code for language: {}, type: {}", request.getLanguage(), request.getOptimizationType());
|
||||||
|
|
||||||
|
OptimizeCodeResponse grpcResponse = grpcClient.optimizeCode(
|
||||||
|
request.getCode(),
|
||||||
|
request.getLanguage(),
|
||||||
|
request.getOptimizationType() != null ? request.getOptimizationType() : "performance"
|
||||||
|
);
|
||||||
|
|
||||||
|
return OptimizeCodeRespDTO.builder()
|
||||||
|
.success(grpcResponse.getSuccess())
|
||||||
|
.message(grpcResponse.getMessage())
|
||||||
|
.optimizedCode(grpcResponse.getOptimizedCode())
|
||||||
|
.improvements(grpcResponse.getImprovementsList())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GenerateTestCasesRespDTO generateTestCases(GenerateTestCasesReqDTO request) {
|
||||||
|
log.info("Generating test cases for language: {}", request.getLanguage());
|
||||||
|
|
||||||
|
GenerateTestCasesResponse grpcResponse = grpcClient.generateTestCases(
|
||||||
|
request.getCode(),
|
||||||
|
request.getLanguage(),
|
||||||
|
request.getProblemDescription()
|
||||||
|
);
|
||||||
|
|
||||||
|
var testCases = grpcResponse.getTestCasesList().stream()
|
||||||
|
.map(tc -> TestCaseDTO.builder()
|
||||||
|
.input(tc.getInput())
|
||||||
|
.expectedOutput(tc.getExpectedOutput())
|
||||||
|
.description(tc.getDescription())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return GenerateTestCasesRespDTO.builder()
|
||||||
|
.success(grpcResponse.getSuccess())
|
||||||
|
.message(grpcResponse.getMessage())
|
||||||
|
.testCases(testCases)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExplainCodeRespDTO explainCode(ExplainCodeReqDTO request) {
|
||||||
|
log.info("Explaining code for language: {}, level: {}", request.getLanguage(), request.getDetailLevel());
|
||||||
|
|
||||||
|
ExplainCodeResponse grpcResponse = grpcClient.explainCode(
|
||||||
|
request.getCode(),
|
||||||
|
request.getLanguage(),
|
||||||
|
request.getDetailLevel() != null ? request.getDetailLevel() : "normal"
|
||||||
|
);
|
||||||
|
|
||||||
|
return ExplainCodeRespDTO.builder()
|
||||||
|
.success(grpcResponse.getSuccess())
|
||||||
|
.message(grpcResponse.getMessage())
|
||||||
|
.explanation(grpcResponse.getExplanation())
|
||||||
|
.keyPoints(grpcResponse.getKeyPointsList())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
100
aioj-backend-ai-service/src/main/proto/ai_service.proto
Normal file
100
aioj-backend-ai-service/src/main/proto/ai_service.proto
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package ai.service;
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "cn.meowrain.aioj.backend.aiservice.grpc";
|
||||||
|
option java_outer_classname = "AIServiceProto";
|
||||||
|
|
||||||
|
// AI 代码分析服务
|
||||||
|
service AIService {
|
||||||
|
// 代码分析
|
||||||
|
rpc AnalyzeCode(AnalyzeCodeRequest) returns (AnalyzeCodeResponse);
|
||||||
|
|
||||||
|
// 代码优化建议
|
||||||
|
rpc OptimizeCode(OptimizeCodeRequest) returns (OptimizeCodeResponse);
|
||||||
|
|
||||||
|
// 生成测试用例
|
||||||
|
rpc GenerateTestCases(GenerateTestCasesRequest) returns (GenerateTestCasesResponse);
|
||||||
|
|
||||||
|
// 代码解释
|
||||||
|
rpc ExplainCode(ExplainCodeRequest) returns (ExplainCodeResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码分析请求
|
||||||
|
message AnalyzeCodeRequest {
|
||||||
|
string code = 1; // 代码内容
|
||||||
|
string language = 2; // 编程语言 (python, java, cpp, etc.)
|
||||||
|
string question_id = 3; // 题目ID
|
||||||
|
string user_id = 4; // 用户ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码分析响应
|
||||||
|
message AnalyzeCodeResponse {
|
||||||
|
bool success = 1; // 是否成功
|
||||||
|
string message = 2; // 响应消息
|
||||||
|
CodeAnalysis analysis = 3; // 分析结果
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码分析结果
|
||||||
|
message CodeAnalysis {
|
||||||
|
repeated string issues = 1; // 发现的问题
|
||||||
|
repeated string suggestions = 2; // 改进建议
|
||||||
|
int32 complexity_score = 3; // 复杂度评分 (0-100)
|
||||||
|
int32 performance_score = 4; // 性能评分 (0-100)
|
||||||
|
int32 readability_score = 5; // 可读性评分 (0-100)
|
||||||
|
string time_complexity = 6; // 时间复杂度
|
||||||
|
string space_complexity = 7; // 空间复杂度
|
||||||
|
repeated string best_practices = 8; // 最佳实践建议
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码优化请求
|
||||||
|
message OptimizeCodeRequest {
|
||||||
|
string code = 1; // 代码内容
|
||||||
|
string language = 2; // 编程语言
|
||||||
|
string optimization_type = 3; // 优化类型 (performance, readability, memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码优化响应
|
||||||
|
message OptimizeCodeResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
string optimized_code = 3; // 优化后的代码
|
||||||
|
repeated string improvements = 4; // 改进说明
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成测试用例请求
|
||||||
|
message GenerateTestCasesRequest {
|
||||||
|
string code = 1; // 代码内容
|
||||||
|
string language = 2; // 编程语言
|
||||||
|
string problem_description = 3; // 问题描述
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成测试用例响应
|
||||||
|
message GenerateTestCasesResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
repeated TestCase test_cases = 3; // 测试用例列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试用例
|
||||||
|
message TestCase {
|
||||||
|
string input = 1; // 输入
|
||||||
|
string expected_output = 2; // 预期输出
|
||||||
|
string description = 3; // 描述
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码解释请求
|
||||||
|
message ExplainCodeRequest {
|
||||||
|
string code = 1; // 代码内容
|
||||||
|
string language = 2; // 编程语言
|
||||||
|
string detail_level = 3; // 详细程度 (brief, normal, detailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码解释响应
|
||||||
|
message ExplainCodeResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
string explanation = 3; // 代码解释
|
||||||
|
repeated string key_points = 4; // 关键点
|
||||||
|
}
|
||||||
219
aioj-backend-blog-service/README.md
Normal file
219
aioj-backend-blog-service/README.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# AIOJ 博客服务模块
|
||||||
|
|
||||||
|
## 模块说明
|
||||||
|
|
||||||
|
`aioj-backend-blog-service` 是 AIOJ 系统的博客服务模块,用于用户发帖、分享技术经验、写文章。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 文章管理:发布、编辑、删除、草稿箱
|
||||||
|
- 分类管理:支持多级分类
|
||||||
|
- 标签系统:文章标签分类和关联
|
||||||
|
- 评论系统:支持多级评论和回复
|
||||||
|
- 点赞/收藏:文章和评论点赞、收藏功能
|
||||||
|
- 浏览统计:文章浏览记录和统计
|
||||||
|
- Markdown 支持:原生支持 Markdown 编辑和渲染
|
||||||
|
- 搜索功能:全文搜索文章标题和内容
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- Spring Boot 3.5.7
|
||||||
|
- MyBatis Plus
|
||||||
|
- MySQL 8.0
|
||||||
|
- Redis
|
||||||
|
- Nacos 服务发现
|
||||||
|
- FlexMark (Markdown 处理)
|
||||||
|
- Knife4j (API 文档)
|
||||||
|
|
||||||
|
## 端口配置
|
||||||
|
|
||||||
|
- 开发环境: 18086
|
||||||
|
- 上下文路径: /api
|
||||||
|
|
||||||
|
## 数据库
|
||||||
|
|
||||||
|
数据库名称: `aioj_blog`
|
||||||
|
|
||||||
|
初始化 SQL 脚本: `../../db/blog.sql`
|
||||||
|
|
||||||
|
### 核心表结构
|
||||||
|
|
||||||
|
| 表名 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| blog_article | 文章表 |
|
||||||
|
| blog_category | 文章分类表 |
|
||||||
|
| blog_tag | 文章标签表 |
|
||||||
|
| blog_article_tag | 文章标签关联表 |
|
||||||
|
| blog_comment | 评论表 |
|
||||||
|
| blog_like | 点赞表 |
|
||||||
|
| blog_favorite | 收藏表 |
|
||||||
|
| blog_view | 浏览记录表 |
|
||||||
|
| blog_draft | 草稿箱表 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 初始化数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -u root -p < ../../db/blog.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置数据库连接
|
||||||
|
|
||||||
|
编辑 `src/main/resources/application-dev.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/aioj_blog
|
||||||
|
username: your_username
|
||||||
|
password: your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
或直接运行主类: `BlogServiceApplication.java`
|
||||||
|
|
||||||
|
### 4. 访问 API 文档
|
||||||
|
|
||||||
|
启动服务后访问: http://localhost:18086/api/doc.html
|
||||||
|
|
||||||
|
## API 接口
|
||||||
|
|
||||||
|
### 文章相关
|
||||||
|
|
||||||
|
- `POST /api/article` - 创建文章
|
||||||
|
- `PUT /api/article/{id}` - 更新文章
|
||||||
|
- `DELETE /api/article/{id}` - 删除文章
|
||||||
|
- `GET /api/article/{id}` - 获取文章详情
|
||||||
|
- `GET /api/article/list` - 获取文章列表
|
||||||
|
- `POST /api/article/publish` - 发布文章
|
||||||
|
|
||||||
|
### 分类相关
|
||||||
|
|
||||||
|
- `GET /api/category/list` - 获取分类列表
|
||||||
|
- `POST /api/category` - 创建分类(管理员)
|
||||||
|
|
||||||
|
### 标签相关
|
||||||
|
|
||||||
|
- `GET /api/tag/list` - 获取标签列表
|
||||||
|
- `GET /api/tag/hot` - 获取热门标签
|
||||||
|
|
||||||
|
### 评论相关
|
||||||
|
|
||||||
|
- `POST /api/comment` - 发表评论
|
||||||
|
- `GET /api/comment/article/{articleId}` - 获取文章评论列表
|
||||||
|
- `DELETE /api/comment/{id}` - 删除评论
|
||||||
|
|
||||||
|
## 开发指南
|
||||||
|
|
||||||
|
### 代码结构
|
||||||
|
|
||||||
|
```
|
||||||
|
aioj-backend-blog-service/
|
||||||
|
├── src/main/java/cn/meowrain/aioj/backend/blogservice/
|
||||||
|
│ ├── controller/ # 控制器层
|
||||||
|
│ ├── service/ # 服务层
|
||||||
|
│ │ └── impl/ # 服务实现层
|
||||||
|
│ ├── dao/ # 数据访问层
|
||||||
|
│ │ ├── mapper/ # MyBatis Mapper
|
||||||
|
│ │ └── model/ # 数据模型
|
||||||
|
│ ├── dto/ # 数据传输对象
|
||||||
|
│ ├── vo/ # 视图对象
|
||||||
|
│ ├── common/ # 公共类
|
||||||
|
│ ├── config/ # 配置类
|
||||||
|
│ └── BlogServiceApplication.java
|
||||||
|
└── src/main/resources/
|
||||||
|
├── mapper/ # MyBatis XML 映射文件
|
||||||
|
├── application.yml # 主配置文件
|
||||||
|
├── application-dev.yml
|
||||||
|
├── application-test.yml
|
||||||
|
├── application-prod.yml
|
||||||
|
└── logback-spring.xml # 日志配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 待实现功能
|
||||||
|
|
||||||
|
- [ ] 文章 CRUD 接口
|
||||||
|
- [ ] 分类管理接口
|
||||||
|
- [ ] 标签管理接口
|
||||||
|
- [ ] 评论系统接口
|
||||||
|
- [ ] 点赞/收藏接口
|
||||||
|
- [ ] 文章搜索接口
|
||||||
|
- [ ] Markdown 渲染服务
|
||||||
|
- [ ] 文章定时发布
|
||||||
|
- [ ] 文章审核功能
|
||||||
|
- [ ] 用户关注和动态
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### 2025-01-20 - 初始创建
|
||||||
|
|
||||||
|
**创建者**: Claude Code
|
||||||
|
|
||||||
|
**工作内容**:
|
||||||
|
|
||||||
|
1. **模块结构搭建**
|
||||||
|
- 创建完整的 Maven 模块目录结构
|
||||||
|
- 配置 `pom.xml`,引入所需依赖
|
||||||
|
- 创建主应用类 `BlogServiceApplication.java`
|
||||||
|
- 更新父 `pom.xml`,添加新模块注册
|
||||||
|
|
||||||
|
2. **配置文件创建**
|
||||||
|
- `application.yml` - 主配置文件
|
||||||
|
- 服务端口: 18086
|
||||||
|
- 上下文路径: /api
|
||||||
|
- MyBatis Plus 配置
|
||||||
|
- Knife4j API 文档配置
|
||||||
|
- `application-dev.yml` - 开发环境配置
|
||||||
|
- `application-test.yml` - 测试环境配置
|
||||||
|
- `application-prod.yml` - 生产环境配置
|
||||||
|
- `logback-spring.xml` - 日志配置
|
||||||
|
|
||||||
|
3. **数据库设计**
|
||||||
|
- 创建 `db/blog.sql` 数据库初始化脚本
|
||||||
|
- 设计 9 张核心表:
|
||||||
|
- `blog_article` - 文章表(支持草稿、发布、下架等状态)
|
||||||
|
- `blog_category` - 分类表(支持多级分类)
|
||||||
|
- `blog_tag` - 标签表
|
||||||
|
- `blog_article_tag` - 文章标签关联表
|
||||||
|
- `blog_comment` - 评论表(支持多级回复)
|
||||||
|
- `blog_like` - 点赞表
|
||||||
|
- `blog_favorite` - 收藏表
|
||||||
|
- `blog_view` - 浏览记录表
|
||||||
|
- `blog_draft` - 草稿箱表
|
||||||
|
- 添加初始分类和标签数据
|
||||||
|
|
||||||
|
4. **依赖说明**
|
||||||
|
- Spring Boot 3.5.7
|
||||||
|
- Spring Cloud & Nacos(服务发现)
|
||||||
|
- MyBatis Plus(ORM)
|
||||||
|
- FlexMark(Markdown 处理)
|
||||||
|
- Knife4j(API 文档)
|
||||||
|
- Redis(缓存)
|
||||||
|
- MySQL(数据库)
|
||||||
|
|
||||||
|
5. **待实现功能**
|
||||||
|
- [ ] 文章 CRUD 接口开发
|
||||||
|
- [ ] 分类管理接口
|
||||||
|
- [ ] 标签管理接口
|
||||||
|
- [ ] 评论系统接口
|
||||||
|
- [ ] 点赞/收藏接口
|
||||||
|
- [ ] 文章搜索接口
|
||||||
|
- [ ] Markdown 渲染服务
|
||||||
|
- [ ] 文章定时发布
|
||||||
|
- [ ] 文章审核功能
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 确保先执行 `db/blog.sql` 初始化数据库
|
||||||
|
- 检查 `application-dev.yml` 中的数据库连接配置
|
||||||
|
- JWT 配置需与 `aioj-backend-auth` 保持一致
|
||||||
|
- 启动前确保 Nacos 服务已运行
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2025 AIOJ Project
|
||||||
99
aioj-backend-blog-service/pom.xml
Normal file
99
aioj-backend-blog-service/pom.xml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>ai-oj</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>aioj-backend-blog-service</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>AIOJ 博客服务 - 用于用户发帖、分享技术经验、写文章</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- ==================== API文档 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 内部模块 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-log</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>aioj-backend-common-feign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Web ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 工具类 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Markdown 处理 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.vladsch.flexmark</groupId>
|
||||||
|
<artifactId>flexmark-all</artifactId>
|
||||||
|
<version>0.64.8</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Redis & 缓存 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 数据库 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Spring Cloud 服务发现 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 测试 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.meowrain.aioj.backend.blogservice;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AIOJ 博客服务应用启动类
|
||||||
|
*
|
||||||
|
* @author AIOJ
|
||||||
|
*/
|
||||||
|
@MapperScan("cn.meowrain.aioj.backend.blogservice.dao.mapper")
|
||||||
|
public class BlogServiceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(BlogServiceApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* AIOJ 博客服务模块
|
||||||
|
* <p>
|
||||||
|
* 提供文章发布、分类管理、标签系统、评论互动等功能
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author AIOJ
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj.backend.blogservice;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/aioj_blog?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: root
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
database: 5
|
||||||
|
password:
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
min-idle: 0
|
||||||
|
max-idle: 8
|
||||||
|
max-active: 8
|
||||||
|
max-wait: -1ms
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: aioj-dev
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/aioj_blog?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true&allowPublicKeyRetrieval=true
|
||||||
|
username: ${MYSQL_USERNAME:root}
|
||||||
|
password: ${MYSQL_PASSWORD:prod}
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
database: ${REDIS_DB:7}
|
||||||
|
password: ${REDIS_PASSWORD:}
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
min-idle: 0
|
||||||
|
max-idle: 8
|
||||||
|
max-active: 8
|
||||||
|
max-wait: -1ms
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: ${NACOS_ADDR:localhost:8848}
|
||||||
|
namespace: ${NACOS_NAMESPACE:aioj-prod}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/aioj_blog?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: test
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
database: 6
|
||||||
|
password:
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
min-idle: 0
|
||||||
|
max-idle: 8
|
||||||
|
max-active: 8
|
||||||
|
max-wait: -1ms
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: aioj-test
|
||||||
57
aioj-backend-blog-service/src/main/resources/application.yml
Normal file
57
aioj-backend-blog-service/src/main/resources/application.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: aioj-blog-service
|
||||||
|
profiles:
|
||||||
|
active: @env@
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB
|
||||||
|
max-request-size: 10MB
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 18086
|
||||||
|
servlet:
|
||||||
|
context-path: /api
|
||||||
|
error:
|
||||||
|
include-stacktrace: never
|
||||||
|
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
default-flat-param-object: true
|
||||||
|
swagger-ui:
|
||||||
|
path: /swagger-ui.html
|
||||||
|
tags-sorter: alpha
|
||||||
|
operations-sorter: alpha
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
paths-to-match: '/api/**'
|
||||||
|
packages-to-scan: cn.meowrain.aioj.backend.blogservice.controller
|
||||||
|
|
||||||
|
knife4j:
|
||||||
|
basic:
|
||||||
|
enable: true
|
||||||
|
setting:
|
||||||
|
language: zh_cn
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
|
|
||||||
|
# JWT 配置(必须与 auth-service 保持一致)
|
||||||
|
jwt:
|
||||||
|
enabled: true
|
||||||
|
secret: "12345678901234567890123456789012" # 至少32字节
|
||||||
|
access-expire: 900000 # 15分钟
|
||||||
|
refresh-expire: 604800000 # 7天
|
||||||
|
|
||||||
|
aioj:
|
||||||
|
log:
|
||||||
|
enabled: true
|
||||||
|
max-length: 20000
|
||||||
|
|
||||||
|
logging:
|
||||||
|
file:
|
||||||
|
path: ./logs/${spring.application.name}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="10 seconds" debug="false">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
|
||||||
|
|
||||||
|
<timestamp key="time-month" datePattern="yyyy-MM"/>
|
||||||
|
<timestamp key="time-month-day" datePattern="yyyy-MM-dd"/>
|
||||||
|
<property name="LOG_FILE_PATH" value="${LOG_PATH:-./logs/${APP_NAME}}"/>
|
||||||
|
|
||||||
|
<property name="FILE_LOG_PATTERN"
|
||||||
|
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -[%X{traceId:-}] %msg%n"/>
|
||||||
|
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>100MB</maxFileSize>
|
||||||
|
<maxHistory>31</maxHistory>
|
||||||
|
<totalSizeCap>100GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>100MB</maxFileSize>
|
||||||
|
<maxHistory>31</maxHistory>
|
||||||
|
<totalSizeCap>100GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ASYNC_INFO" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<queueSize>512</queueSize>
|
||||||
|
<appender-ref ref="INFO_FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<queueSize>512</queueSize>
|
||||||
|
<appender-ref ref="ERROR_FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<springProfile name="dev">
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="ASYNC_ERROR"/>
|
||||||
|
<appender-ref ref="ASYNC_INFO"/>
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<springProfile name="prod">
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="ASYNC_ERROR"/>
|
||||||
|
<appender-ref ref="ASYNC_INFO"/>
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
</configuration>
|
||||||
224
db/blog.sql
Normal file
224
db/blog.sql
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- AIOJ 博客服务数据库表结构
|
||||||
|
-- 数据库: aioj_blog
|
||||||
|
-- 描述: 用于用户发帖、分享技术经验、写文章
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 创建数据库
|
||||||
|
CREATE DATABASE IF NOT EXISTS `aioj_blog` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE `aioj_blog`;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 1. 文章分类表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_category`;
|
||||||
|
CREATE TABLE `blog_category` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类ID',
|
||||||
|
`name` VARCHAR(50) NOT NULL COMMENT '分类名称',
|
||||||
|
`slug` VARCHAR(50) NOT NULL COMMENT '分类别名(用于URL)',
|
||||||
|
`description` VARCHAR(200) DEFAULT NULL COMMENT '分类描述',
|
||||||
|
`icon` VARCHAR(100) DEFAULT NULL COMMENT '分类图标',
|
||||||
|
`parent_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '父分类ID,0表示顶级分类',
|
||||||
|
`sort_order` INT DEFAULT 0 COMMENT '排序权重,数值越大越靠前',
|
||||||
|
`article_count` INT UNSIGNED DEFAULT 0 COMMENT '该分类下的文章数量',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_slug` (`slug`),
|
||||||
|
KEY `idx_parent_id` (`parent_id`),
|
||||||
|
KEY `idx_sort_order` (`sort_order`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章分类表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 2. 文章标签表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_tag`;
|
||||||
|
CREATE TABLE `blog_tag` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '标签ID',
|
||||||
|
`name` VARCHAR(30) NOT NULL COMMENT '标签名称',
|
||||||
|
`slug` VARCHAR(30) NOT NULL COMMENT '标签别名(用于URL)',
|
||||||
|
`description` VARCHAR(200) DEFAULT NULL COMMENT '标签描述',
|
||||||
|
`color` VARCHAR(7) DEFAULT NULL COMMENT '标签颜色(十六进制)',
|
||||||
|
`article_count` INT UNSIGNED DEFAULT 0 COMMENT '该标签下的文章数量',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_slug` (`slug`),
|
||||||
|
KEY `idx_name` (`name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章标签表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 3. 文章表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_article`;
|
||||||
|
CREATE TABLE `blog_article` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文章ID',
|
||||||
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '作者用户ID',
|
||||||
|
`category_id` BIGINT UNSIGNED NOT NULL COMMENT '分类ID',
|
||||||
|
`title` VARCHAR(200) NOT NULL COMMENT '文章标题',
|
||||||
|
`slug` VARCHAR(200) DEFAULT NULL COMMENT '文章别名(用于URL,唯一)',
|
||||||
|
`summary` VARCHAR(500) DEFAULT NULL COMMENT '文章摘要',
|
||||||
|
`cover_image` VARCHAR(500) DEFAULT NULL COMMENT '封面图片URL',
|
||||||
|
`content` MEDIUMTEXT NOT NULL COMMENT '文章内容(Markdown格式)',
|
||||||
|
`content_html` MEDIUMTEXT DEFAULT NULL COMMENT '文章内容(HTML格式,缓存用)',
|
||||||
|
`view_count` BIGINT UNSIGNED DEFAULT 0 COMMENT '浏览次数',
|
||||||
|
`like_count` INT UNSIGNED DEFAULT 0 COMMENT '点赞数',
|
||||||
|
`comment_count` INT UNSIGNED DEFAULT 0 COMMENT '评论数',
|
||||||
|
`favorite_count` INT UNSIGNED DEFAULT 0 COMMENT '收藏数',
|
||||||
|
`is_top` TINYINT(1) DEFAULT 0 COMMENT '是否置顶:0-否,1-是',
|
||||||
|
`is_featured` TINYINT(1) DEFAULT 0 COMMENT '是否精选:0-否,1-是',
|
||||||
|
`is_original` TINYINT(1) DEFAULT 1 COMMENT '是否原创:0-转载,1-原创',
|
||||||
|
`source_url` VARCHAR(500) DEFAULT NULL COMMENT '转载来源URL',
|
||||||
|
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '文章状态:0-草稿,1-已发布,2-审核中,3-已下架',
|
||||||
|
`publish_time` DATETIME DEFAULT NULL COMMENT '发布时间',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_slug` (`slug`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_category_id` (`category_id`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_is_top` (`is_top`),
|
||||||
|
KEY `idx_publish_time` (`publish_time`),
|
||||||
|
KEY `idx_view_count` (`view_count`),
|
||||||
|
FULLTEXT KEY `ft_title_content` (`title`, `content`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 4. 文章标签关联表(多对多)
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_article_tag`;
|
||||||
|
CREATE TABLE `blog_article_tag` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '关联ID',
|
||||||
|
`article_id` BIGINT UNSIGNED NOT NULL COMMENT '文章ID',
|
||||||
|
`tag_id` BIGINT UNSIGNED NOT NULL COMMENT '标签ID',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_article_tag` (`article_id`, `tag_id`),
|
||||||
|
KEY `idx_article_id` (`article_id`),
|
||||||
|
KEY `idx_tag_id` (`tag_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章标签关联表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 5. 评论表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_comment`;
|
||||||
|
CREATE TABLE `blog_comment` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '评论ID',
|
||||||
|
`article_id` BIGINT UNSIGNED NOT NULL COMMENT '文章ID',
|
||||||
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '评论用户ID',
|
||||||
|
`parent_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '父评论ID,0表示一级评论',
|
||||||
|
`reply_user_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '被回复的用户ID',
|
||||||
|
`content` TEXT NOT NULL COMMENT '评论内容',
|
||||||
|
`like_count` INT UNSIGNED DEFAULT 0 COMMENT '点赞数',
|
||||||
|
`reply_count` INT UNSIGNED DEFAULT 0 COMMENT '回复数',
|
||||||
|
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '评论状态:0-待审核,1-已通过,2-已拒绝',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_article_id` (`article_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_parent_id` (`parent_id`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 6. 点赞表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_like`;
|
||||||
|
CREATE TABLE `blog_like` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '点赞ID',
|
||||||
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||||
|
`target_id` BIGINT UNSIGNED NOT NULL COMMENT '目标ID(文章ID或评论ID)',
|
||||||
|
`target_type` TINYINT NOT NULL COMMENT '目标类型:1-文章,2-评论',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是(取消点赞)',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_target` (`user_id`, `target_id`, `target_type`, `is_deleted`),
|
||||||
|
KEY `idx_target` (`target_id`, `target_type`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='点赞表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 7. 收藏表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_favorite`;
|
||||||
|
CREATE TABLE `blog_favorite` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '收藏ID',
|
||||||
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||||
|
`article_id` BIGINT UNSIGNED NOT NULL COMMENT '文章ID',
|
||||||
|
`folder_name` VARCHAR(50) DEFAULT '默认收藏夹' COMMENT '收藏夹名称',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是(取消收藏)',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_article` (`user_id`, `article_id`, `is_deleted`),
|
||||||
|
KEY `idx_article_id` (`article_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='收藏表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 8. 浏览记录表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_view`;
|
||||||
|
CREATE TABLE `blog_view` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '浏览记录ID',
|
||||||
|
`article_id` BIGINT UNSIGNED NOT NULL COMMENT '文章ID',
|
||||||
|
`user_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '用户ID(未登录为NULL)',
|
||||||
|
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'IP地址',
|
||||||
|
`user_agent` VARCHAR(500) DEFAULT NULL COMMENT '用户代理(浏览器信息)',
|
||||||
|
`duration` INT UNSIGNED DEFAULT 0 COMMENT '浏览时长(秒)',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_article_id` (`article_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='浏览记录表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 9. 草稿箱表
|
||||||
|
-- ============================================
|
||||||
|
DROP TABLE IF EXISTS `blog_draft`;
|
||||||
|
CREATE TABLE `blog_draft` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '草稿ID',
|
||||||
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||||
|
`title` VARCHAR(200) DEFAULT NULL COMMENT '草稿标题',
|
||||||
|
`content` MEDIUMTEXT DEFAULT NULL COMMENT '草稿内容(Markdown格式)',
|
||||||
|
`category_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '分类ID',
|
||||||
|
`cover_image` VARCHAR(500) DEFAULT NULL COMMENT '封面图片URL',
|
||||||
|
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否,1-是',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_update_time` (`update_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='草稿箱表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 初始化数据
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 初始化分类
|
||||||
|
INSERT INTO `blog_category` (`name`, `slug`, `description`, `icon`, `parent_id`, `sort_order`) VALUES
|
||||||
|
('技术分享', 'tech', '分享技术经验和心得', 'icon-tech', 0, 100),
|
||||||
|
('算法学习', 'algorithm', '算法与数据结构学习笔记', 'icon-algo', 0, 90),
|
||||||
|
('项目实战', 'project', '项目开发实战经验', 'icon-project', 0, 80),
|
||||||
|
('面试经验', 'interview', '面试经验总结', 'icon-interview', 0, 70),
|
||||||
|
('学习笔记', 'note', '日常学习笔记', 'icon-note', 0, 60);
|
||||||
|
|
||||||
|
-- 初始化标签
|
||||||
|
INSERT INTO `blog_tag` (`name`, `slug`, `description`, `color`) VALUES
|
||||||
|
('Java', 'java', 'Java编程语言', '#007396'),
|
||||||
|
('Python', 'python', 'Python编程语言', '#3776AB'),
|
||||||
|
('Spring', 'spring', 'Spring框架', '#6DB33F'),
|
||||||
|
('MyBatis', 'mybatis', 'MyBatis持久层框架', '#DC382D'),
|
||||||
|
('Redis', 'redis', 'Redis缓存', '#D82C20'),
|
||||||
|
('MySQL', 'mysql', 'MySQL数据库', '#4479A1'),
|
||||||
|
('LeetCode', 'leetcode', 'LeetCode刷题', '#FFA116'),
|
||||||
|
('系统设计', 'system-design', '系统设计相关', '#FF6B6B'),
|
||||||
|
('微服务', 'microservice', '微服务架构', '#009688'),
|
||||||
|
('前端开发', 'frontend', '前端开发技术', '#F7DF1E');
|
||||||
Reference in New Issue
Block a user