1912 words
10 minutes
DDD Architecture - OpenAI-Powered Code Review Tool

Write CodeReview Content to Log#

为了把Code Review的内容写到Log中(log仓库的markdown文件形式),我们现在的主函数是

    public static void main(String[] args) throws Exception {

        System.out.println("openai 代码评审测试执行");

      // 新加代码:为了能有权限向log仓库创建文件
        String token = System.getenv("GITHUB_TOKEN");
        if (token == null || token.isEmpty()) {
            throw new RuntimeException("token is null");
        }

        // 1.代码检出
        ProcessBuilder processBuilder = new ProcessBuilder("git", "diff", "HEAD~1", "HEAD");
        processBuilder.directory(new File("."));

        Process process = processBuilder.start();

        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;

        StringBuilder diffCode = new StringBuilder();
        while ((line = reader.readLine()) != null){
            diffCode.append(line);
        }

        int exitCode = process.waitFor();
        System.out.println("Exited with code: " + exitCode);
        System.out.println("Diff_Code " + diffCode.toString());

        //2. chatGLM 代码评审
        String log = codeReview(diffCode.toString());
        System.out.println("code Review:" + log);

        //3. 写日志
        String logUrl = writeLog(token, log);
        System.out.println("logUrl:" + logUrl);

    }

实现writeLog Function#

   private static String writeLog(String token, String log) throws Exception {
        Git git = Git.cloneRepository()
                .setURI("https://github.com/nanshanvv/openai-code-review-log.git")
                .setDirectory(new File("repo"))
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(token, ""))
                .call();

        String dateFolderName = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File dateFolder = new File("repo/" + dateFolderName);
        if (!dateFolder.exists()) {
            dateFolder.mkdirs();
        }

        String filename = generateRandomString(12) + ".md";
        File newFile = new File(dateFolder, filename);
        try(FileWriter filewriter = new FileWriter(newFile)) {
            filewriter.write(log);
        }

        git.add().addFilepattern(dateFolderName + "/" + filename).call();
        git.commit().setMessage("Add new File").call();
        git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(token, "")).call();
        return "https://github.com/nanshanvv/openai-code-review-log/blob/main/" + dateFolderName + "/" + filename;
    }

我们主要用Git命令来实现

主要流程就是

[开始执行 writeLog 方法]
        |
        v
[1] 使用 Git.cloneRepository() 克隆远程仓库
    └── URI: https://github.com/xxx/openai-code-review-log.git
    └── 目录: 创建本地 "repo/" 文件夹
        |
        v
[2] 获取当前日期 → yyyy-MM-dd
    └── 示例: "2025-04-25"
        |
        v
[3] 构造本地路径 repo/yyyy-MM-dd/
    └── 如果不存在则 mkdirs()
        |
        v
[4] 生成随机文件名
    └── 示例: "MpxxRiRPtK7Q.md"
        |
        v
[5] 将 log 内容写入 markdown 文件
    └── 路径: repo/2025-04-25/MpxxRiRPtK7Q.md
        |
        v
[6] 使用 git.add() 添加文件
    └── 相对路径: 2025-04-25/MpxxRiRPtK7Q.md
        |
        v
[7] 使用 git.commit() 提交更改
    └── 提交信息: "Add new File"
        |
        v
[8] 使用 git.push() 推送到 GitHub 远程仓库
    └── 使用 token 授权
        |
        v
[9] 返回日志文件访问链接
    └── 示例: https://github.com/xxx/openai-code-review-log/blob/main/2025-04-25/MpxxRiRPtK7Q.md
        |
        v
[结束]

代码重构 DDD#

GitCommand部分#

public String diff()#

这一段代码主要是把之前提取diff代码的功能给封装了

   public String diff() throws IOException, InterruptedException {
        ProcessBuilder logprocessBuilder = new ProcessBuilder("git", "log", "-1", "--pretty=format:%H");
        logprocessBuilder.directory(new File("."));
        Process logprocess = logprocessBuilder.start();

        BufferedReader logReader = new BufferedReader(new InputStreamReader(logprocess.getInputStream()));
        String latestCommitHash = logReader.readLine();
        logReader.close();
        logprocess.waitFor();

        ProcessBuilder diffProcessBuilder = new ProcessBuilder("git", "diff", latestCommitHash + "^", latestCommitHash);
        diffProcessBuilder.directory(new File("."));
        Process diffProcess = diffProcessBuilder.start();

        StringBuilder diffCode = new StringBuilder();
        BufferedReader diffReader = new BufferedReader(new InputStreamReader(diffProcess.getInputStream()));
        String line;
        while ((line = diffReader.readLine()) != null){
            diffCode.append(line);
        }
        diffReader.close();
        int exitCode = diffProcess.waitFor();
        if (exitCode != 0){
            throw new RuntimeException("diff failed");
        }
        return diffCode.toString();
    }
知识补充#
  1. ProcessBuilder

→ 让 Java 帮你在电脑上执行一条命令。

比如,正常我们在命令行(Terminal)里会打:

git diff HEAD^ HEAD

现在你在 Java 里面想让程序自动去执行这条命令,就要用 ProcessBuilder

简单理解就是:

  • ProcessBuilder = Java 帮你敲命令行
  • 然后 Java 可以拿到命令行返回的结果(比如 diff 的输出)

举个例子:

java


CopyEdit
ProcessBuilder pb = new ProcessBuilder("git", "diff", "HEAD^", "HEAD");
Process p = pb.start();

这就相当于:

  • 让电脑去命令行执行 git diff HEAD^ HEAD
  • 然后拿到执行结果。

  1. InputStreamReader

→ 把程序运行后输出的“字节流”,转换成“文字”。

因为 ProcessBuilder 启动的程序,会把结果输出到一种叫**InputStream(输入流)**的东西里。

  • 流(Stream) = 一堆数据(可能是字节、二进制数据)
  • 但是字节我们人看不懂,需要转成文字

所以用 InputStreamReader 来做翻译:

  • InputStreamReader = 字节 → 文字(比如 UTF-8 编码)

举个例子:

InputStreamReader isr = new InputStreamReader(p.getInputStream());
  • 这里 p.getInputStream() 是程序的输出
  • InputStreamReader 帮你把它变成我们能读的文字。

  1. BufferedReader

→ 让你一行一行地读取文字,速度快,还方便。

InputStreamReader 虽然能读文字,但每次只能读一个字符(一个字母、一个数字)——很慢也很烦。

所以我们用 BufferedReader 包一下,让它能:

  • 一行一行读取(readLine)
  • 内部有缓存,读取速度也快

举个例子:

BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = reader.readLine();
  • 这样可以一整行一整行地拿到 diff 的内容。

  1. StringBuilder

→ 把很多段文字快速拼接成一整个字符串。

如果你每次都用 String a = a + b; 这样拼字符串,Java 会很慢(因为每次都重新开辟内存)。

所以用 StringBuilder

  • 它内部有一块连续的内存
  • 可以很快地一段一段地拼起来
  • 最后一次性拿到整个大字符串

举个例子:

StringBuilder sb = new StringBuilder();
sb.append("Hello ");
sb.append("World");
String result = sb.toString(); // "Hello World"

在你的代码里,就是一行一行地把 diff 内容加到 StringBuilder 里,最后得到完整的 diff。


简化版流程图#
ProcessBuilder → 启动一个 git 命令

Process.getInputStream() → 获取命令的输出(字节流)

InputStreamReader → 把字节流翻译成文字

BufferedReader → 一行一行读出来

StringBuilder → 每一行加起来,变成一个完整的大字符串

总结一下,简单记忆法:

名字简单理解
ProcessBuilder帮 Java 执行一条命令(比如 git diff
InputStreamReader把程序的输出“字节流”翻译成“文字”
BufferedReader一行一行快速地读文字
StringBuilder很快地拼接很多字符串

public String commitAndPush(String recommand)#

    public String commitAndPush(String recommand) throws Exception {
        Git git = Git.cloneRepository()
                .setURI(githubReviewLogUrl + ".git")
                .setDirectory(new File("repo"))
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(githubToken, ""))
                .call();
        String dateFolderName = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File dateFolder = new File("repo/" + dateFolderName);
        if (!dateFolder.exists()) {
            dateFolder.mkdirs();
        }
        String filename = project + "-" + branch + "-" + author + System.currentTimeMillis() + RandomStringUtils.randomAlphanumeric(4)+".md";
        File newFile = new File(dateFolder, filename);
        try(FileWriter writer = new FileWriter(newFile)) {
            writer.write(recommand);
        }

        git.add().addFilepattern(dateFolderName + "/" + filename).call();
        git.commit().setMessage("Add new File").call();
        git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(githubToken, "")).call();

        logger.info("openai-code-review-log commit successful");
        return githubReviewLogUrl+ "/blob/main/"+dateFolderName + "/" + filename;
    }

OpenAI部分#

接口文件:IOpenAI

package com.github.nanshanvv.sdk.infrastructure.openai;

import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionRequestDTO;
import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionSyncResponseDTO;

public interface IOpenAI {
    ChatCompletionSyncResponseDTO completions(ChatCompletionRequestDTO requestDTO) throws Exception;
}

实现类:impl/ChatGLM

package com.github.nanshanvv.sdk.infrastructure.openai.impl;

import com.alibaba.fastjson2.JSON;
import com.github.nanshanvv.sdk.infrastructure.openai.IOpenAI;
import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionRequestDTO;
import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionSyncResponseDTO;
import com.github.nanshanvv.sdk.types.utils.BearerTokenUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class ChatGLM implements IOpenAI {

    private final String apiHost;
    private final String apiKeySecret;

    public ChatGLM(String apiHost, String apiKeySecret) {
        this.apiHost = apiHost;
        this.apiKeySecret = apiKeySecret;
    }

    @Override
    public ChatCompletionSyncResponseDTO completions(ChatCompletionRequestDTO requestDTO) throws Exception {
        String token = BearerTokenUtils.getToken(apiKeySecret);
        URL url = new URL(apiHost);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod("POST");
        connection.setRequestProperty("Authorization", "Bearer " + token);
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
        connection.setDoOutput(true);

        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = JSON.toJSONString(requestDTO).getBytes(StandardCharsets.UTF_8);
            os.write(input,0,input.length);
        }

        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;

        StringBuilder content = new StringBuilder();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }

        in.close();
        connection.disconnect();
        return JSON.parseObject(content.toString(), ChatCompletionSyncResponseDTO.class);
    }
}

Main运行#

package com.github.nanshanvv.sdk;

import com.alibaba.fastjson2.JSON;
import com.github.nanshanvv.sdk.domain.service.impl.OpenAiCodeReviewService;
import com.github.nanshanvv.sdk.infrastructure.git.GitCommand;
import com.github.nanshanvv.sdk.infrastructure.openai.IOpenAI;
import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionRequestDTO;
import com.github.nanshanvv.sdk.infrastructure.openai.dto.ChatCompletionSyncResponseDTO;
import com.github.nanshanvv.sdk.domain.model.Model;
import com.github.nanshanvv.sdk.infrastructure.openai.impl.ChatGLM;
import com.github.nanshanvv.sdk.types.utils.BearerTokenUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;

public class OpenAiCodeReview {

    private static final Logger logger = LoggerFactory.getLogger(OpenAiCodeReview.class);
    // ChatGLM 配置
    private String chatglm_apiHost = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
    private String chatglm_apiKeySecret = "";

    // Github 配置
    private String github_review_log_uri;
    private String github_token;

    // 工程配置 - 自动获取
    private String github_project;
    private String github_branch;
    private String github_author;

    public static void main(String[] args) throws Exception {
        GitCommand gitCommand = new GitCommand(
                getEnv("GITHUB_REVIEW_LOG_URI"),
                getEnv("GITHUB_TOKEN"),
                getEnv("COMMIT_PROJECT"),
                getEnv("COMMIT_BRANCH"),
                getEnv("COMMIT_AUTHOR"),
                getEnv("COMMIT_MESSAGE")
        );

        IOpenAI openAI = new ChatGLM(getEnv("CHATGLM_APIHOST"), getEnv("CHATGLM_APIKEYSECRET"));

        OpenAiCodeReviewService openAiCodeReviewService = new OpenAiCodeReviewService(gitCommand, openAI);
        openAiCodeReviewService.exec();

        logger.info("openai-code-review done!");
    }

    private static String getEnv(String key) {
        String value = System.getenv(key);
        if (null == value || value.isEmpty()) {
            throw new RuntimeException("value is null");
        }
        return value;
    }

}


层级结构#

最后文件结构是

1

1. 层次分布 (Layers)#

  • Application Layer (应用层)
    • 起动入口,组合实体,经营流程
  • Domain Layer (领域层)
    • 根据项目核心逻辑,定义服务策略
  • Infrastructure Layer (基础设施层)
    • 连接外部系统,实现具体进程,如接口请求,git操作
  • Utils Layer (工具层)
    • 公用功能,如 Token 获取,随机字符串生成

2. 主要类分工 (Class Responsibilities)#

类名层级任务
OpenAiCodeReviewApplicationmain(),起动入口,组合依赖,执行 exec
OpenAiCodeReviewServiceDomain核心服务,执行领域逻辑:Diff -> 评审 -> 提交
AbstractOpenAiCodeReviewServiceDomain定义领域服务骤序框架,拟类
GitCommandInfrastructure跟 Git 交互,执行 clone/diff/commit/push
ChatGLMInfrastructure跟模型进行 HTTP 请求,实现 IOpenAI
IOpenAIInfrastructure Interface定义大模型调用规范,公用接口
BearerTokenUtilsUtils根据 API Key 生成认证标识 Token
RandomStringUtilsUtils生成随机数字串

3. 类之间连接 (Class Relations)#

OpenAiCodeReview
    ├─→ GitCommand
    ├─→ ChatGLM (implements IOpenAI)
    └─→ OpenAiCodeReviewService (extends AbstractOpenAiCodeReviewService)
           ├─→ GitCommand.diff()
           ├─→ ChatGLM.completions()
           └─→ GitCommand.commitAndPush()
  • OpenAiCodeReview 起动入口,组合 Git 操作 + 模型对话 + 领域服务
  • OpenAiCodeReviewService 依赖基础服务,执行评审逻辑
  • 全线通过接口 IOpenAI 分隔了依赖,无绑定
DDD Architecture - OpenAI-Powered Code Review Tool
https://nanshanvv.github.io/shuchangwen-webpage/posts/openai-code-review-/note2/note3/
Author
Shuchang Wen
Published at
2025-04-24