AI之gitlab代码分析之按迭代评审

背景:

为提高公司开发人员的代码开发质量会对项目进行监控并且给出AI的分析建议和筛查相关指标。

1:进入jira,进入项目主界面,点击【发布版本】,点击【生成审查报告】;

2、配置并生成报告

配置当前版本映射的GitLab项目,保存配置,点击生成AI评审报告。

等待报告生成,生成后会在企微通知;

gitLabApi工具类

java 复制代码
package com.cjhx.dify.git.api;

import com.alibaba.druid.util.StringUtils;
import com.cjhx.dify.git.report.version.model.Commit;
import com.cjhx.dify.git.report.version.service.GitLabService;
import com.system.comm.utils.FrameSpringBeanUtil;
import lombok.extern.log4j.Log4j2;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Log4j2
public class GitLabAPI {
    private static GitLabService gitLabService = FrameSpringBeanUtil.getBean(GitLabService.class);

    private static HashMap<String,String> projectNameAndId = new HashMap<String,String>();

    private static final String GITLAB_API_URL = "http://xxxx";
    private static final String PRIVATE_TOKEN = "sdsdsdsds";

    public static String getCommitDiff(String commitSha,String projectName) throws Exception {
        String projectId = projectNameAndId.get(projectName.toUpperCase());
        String url = String.format("%s/projects/%s/repository/commits/%s/diff", GITLAB_API_URL, projectId, commitSha);
        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("PRIVATE-TOKEN", PRIVATE_TOKEN);

        int responseCode = connection.getResponseCode();
        if (responseCode != HttpURLConnection.HTTP_OK) {
            throw new Exception("Error: Unable to get diff from GitLab API, Response code: " + responseCode);
        }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            return response.toString();
        }
    }

    /**
     * 获取所有项目并将项目的 name 和 id 存储到 Map 中
     * @return 返回一个 Map,其中 key 是项目名称,value 是项目 id
     * @throws Exception 异常
     */
    public static void initAllProjectsNameAndId() throws Exception {
        for (int ii = 1; ii <= 100; ii++) {
            String url = String.format("%s/projects?page="+ii+"&per_page=100", GITLAB_API_URL); // 获取所有项目
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("PRIVATE-TOKEN", PRIVATE_TOKEN);

            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new Exception("Error: Unable to get projects from GitLab API, Response code: " + responseCode);
            }

            StringBuilder response = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
            }

            // 解析 JSON 响应并提取 project name 和 id 到 Map 中
            String jsonResponse = response.toString();
            if("[]".equals(jsonResponse)){
                return;
            }
            org.json.JSONArray projects = new org.json.JSONArray(jsonResponse);

            // 遍历所有项目并填充 Map
            for (int i = 0; i < projects.length(); i++) {
                org.json.JSONObject project = projects.getJSONObject(i);
                // 检查是否有 forked_from_project 字段且该字段不为空
                if (project.has("forked_from_project") || !project.isNull("forked_from_project")) {
                    // 如果是 fork 项目,则跳过
                    continue;
                }
                String name = project.getStri

获取git的项目列表

java 复制代码
public Map<String, Object> queryGitProjectsByJiraCode(@RequestParam("jiraCode") String jiraCode) {
        List<Map<String, Object>> projects = new ArrayList<>();
        List<Map<String, Object>> projectsBack = new ArrayList<>();
        HashMap<String, String> projectNameAndId = GitLabAPI.getProjectNameAndId();
        Set<String> gitProjectCodes = projectNameAndId.keySet();
        HashSet<String> gitCodeSet = new HashSet<>();
        String gitCodes = gitLabService.getGitCodesByJiraCode(jiraCode);
        if(!StringUtils.isEmpty(gitCodes)){
            gitCodeSet = new HashSet<String>(Arrays.asList(gitCodes.toUpperCase(Locale.ROOT).split(",")));
        }

        for (String gitProjectCode : gitProjectCodes) {
            Map<String, Object> project = new HashMap<>();
            project.put("gitProjectCode",gitProjectCode); // Git 项目代码
            boolean isCheck = gitCodeSet.contains(gitProjectCode) ? true : false;
            project.put("isCheck",isCheck); // 随机 true/false
            if(isCheck){
                projects.add(project);
            }else {
                projectsBack.add(project);
            }
        }
        projects.addAll(projectsBack);
        // 返回标准 JSON 格式
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("repositories", projects); // 项目列表
        return response;
    }

保存项目并生产html文件

java 复制代码
public ResponseEntity<Map<String, Object>> sendVersionReportByJiraCodePlus(@RequestBody Map<String, String> requestBody) {

        catjCodeAiLogService.setCatjCodeAiLog(CatjCodeAiLogEnum.VERSION_CODE_REVIEW.getCode());

        boolean success = true;
        String message = null;

        String username = requestBody.get("username");
        String jiraCode = requestBody.get("jiraCode");
        String versionName = requestBody.get("versionName");
        String issues = requestBody.get("issues");
        if(!currentJiraCodeSet.contains(jiraCode)) {
            // 处理业务逻辑...
            FrameSpringBeanUtil.getBean(ThreadPoolTaskExecutor.class).submit(() -> {
                try {
                    currentJiraCodeSet.add(jiraCode);
                    log.info("\n_____________生成版本代码审查_______________参数:username={},jiraCode={},versioName={},issues={}", username, jiraCode, versionName, issues);
                    // 1. 准备数据
                    Map<String, List<Map<String, Object>>> commitsMap = gitLabService.getAllCommitDiffsByIssueId(username, jiraCode, versionName, Arrays.asList(issues.split("@.@")));
                    // 2. 生成HTML文件
                    Map<String, Object> variables = new HashMap<>();
                    variables.put("commitsMap", commitsMap);
                    variables.put("jiraCode", jiraCode);
                    variables.put("versionName", versionName);
                    //String outputPath = "./reports/" + jiraCode + "_" + System.currentTimeMillis() + ".html";
                    String outputPath = "./reports/" + jiraCode + ".html";
                    generateHtmlFile("git/report/version/index", variables, outputPath);

                    log.info("__________  生成完毕 ___________{}", jiraCode);

                    //落库
                    gitLabService.saveAiVerReview(username,jiraCode,versionName,commitsMap);

                } catch (Exception e) {
                    e.printStackTrace();
                    currentJiraCodeSet.remove(jiraCode);
                }finally {
                    currentJiraCodeSet.remove(jiraCode);
                }
            });
        }else{
            success = false;
            message = "发送失败,已存在当前Jira项目报告生成任务,资源有限请耐心等待。";
        }
        Map<String, Object> response = new HashMap<>();
        response.put("success", success);
        response.put("message", message);
        return ResponseEntity.ok(response);
    }
    /**
     * 多线程模式
     * @param username
     * @param jiraCode
     * @param versionName
     * @param issueIdAndNames
     * @return
     * @throws Exception
     */
    public Map<String, List<Map<String, Object>>> getAllCommitDiffsByIssueId(String username,String jiraCode,String versionName,List<String> issueIdAndNames) throws Exception {
        int totalScore = 0;
        int totalCommit = 0;

        Map<String, List<Map<String, Object>>> commitsMap = new LinkedHashMap<>();
        String title = "版本代码审查-"+jiraCode;
        String content = "\n\t状态:开始生成...";
        content += "\n\t版本:"+versionName;
        String mentioned = username;
        wechatService.sendTextToWeChatWork(title,content,mentioned);
        try {
            Map<String, Commit> projectCommits = GitLabAPI.getProjectCommits(jiraCode);
            Set<String> idAndMessages = projectCommits.keySet();
            // 创建LinkedHashMap来按顺序存储提交记录
            for (String issueIdsAndName : issueIdAndNames) {
                String issueId = issueIdsAndName.split("@@")[0];
                String issueName = issueIdsAndName.split("@@")[1];
                ArrayList<Map<String, Object>> commitList = new ArrayList<>();
                for (String idAndMessage : idAndMessages) {
                    String commitSha = idAndMessage.split("&&")[0];
                    String message = idAndMessage.split("&&")[1];
                    if (!message.startsWith("Merge branch") && message.toUpperCase(Locale.ROOT).contains(issueId.toUpperCase(Locale.ROOT))) {
                        Commit commit = projectCommits.get(idAndMessage);
                        List<CommitDiff> commitDiffs = getCommitDiffs(commitSha, commit.getProductCode());
                        // 构造 commit 结构
                        Map<String, Object> commitData = new HashMap<>();
                        commitData.put("committerName", commit.getCommitterName());
                        commitData.put("committedDate", formatDateToYyMmDdHhMm(commit.getCommittedDate()));
                        commitData.put("message", commit.getMessage());
                        commitData.put("commitDiffs", commitDiffs);
                        commitData.put("projectCode", commit.getProductCode());
                        // 创建固定大小的线程池
                        ExecutorService executor = Executors.newFixedThreadPool(10);
                        List<Future<?>> futures = new ArrayList<>();

                        // 用于线程安全操作的计数器
                        AtomicInteger threadSafeDiffCount = new AtomicInteger(0);
                        AtomicInteger threadSafeDiffTotalScore = new AtomicInteger(0);

                        for (CommitDiff commitDiff : commitDiffs) {
                            futures.add(executor.submit(() -> {
                                try {
                                    String remarkAndScore = DifyApiUtils.callDifyChatApiByBlocking(
                                            "#代码评审这段代码提交,给出一个分数和评论:" + commitDiff.getDiff());
                                    String score = extractScore(remarkAndScore);
                                    remarkAndScore = remarkAndScore.replace("<|im_start|>","");
                                    remarkAndScore = remarkAndScore.trim();

                                    // 注意:CommitDiff对象是非线程安全的,需要同步操作
                                    synchronized(commitDiff) {
                                        commitDiff.setAiScore(score);
                                        commitDiff.setAiRemark(remarkAndScore);
                                    }

                                    log.info(jiraCode + "-分数生成中:_______________score={}", score);

                                    if (!"-".equals(score) && !StringUtils.isEmpty(score)) {
                                        threadSafeDiffCount.incrementAndGet();
                                        threadSafeDiffTotalScore.addAndGet(Integer.valueOf(score));
                                    }
                                } catch (Exception e) {
                                    log.error("处理CommitDiff时出错", e);
                                }
                            }));
                        }

                        // 等待所有任务完成
                        for (Future<?> future : futures) {
                            try {
                                future.get();
                            } catch (InterruptedException | ExecutionException e) {
                                log.error("任务执行异常", e);
                            }
                        }

                        // 关闭线程池
                        executor.shutdown();

                        // 获取最终结果
                        int diffCount = threadSafeDiffCount.get();
                        int diffTotalScore = threadSafeDiffTotalScore.get();
                        //本次提交总分数
                        String commitScore = diffCount > 0 && diffTotalScore > 0 ? (diffTotalScore / diffCount) + "" : "-";
                        commitData.put("commitScore", commitScore);//有效评分汇总后得出总分
                        commitList.add(commitData);

                        //报告总分及总提交次数
                        if(diffCount > 0 && !StringUtils.isEmpty(commitScore) && !"-".equals(commitScore) && Integer.valueOf(commitScore) > 0){
                            totalCommit += 1;
                            totalScore += Integer.valueOf(commitScore);
                        }
                    }
                }

                // 1. 使用 Collections.sort() 并自定义 Comparator
                Collections.sort(commitList, new Comparator<Map<String, Object>>() {
                    @Override
                    public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                        // 获取 committedDate 字符串
                        String date1 = (String) o1.get("committedDate");
                        String date2 = (String) o2.get("committedDate");

                        // 如果日期是 "N/A",则排到最后
                        if ("N/A".equals(date1)) return 1;
                        if ("N/A".equals(date2)) return -1;

                        // 解析日期字符串为 Date 对象进行比较
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm");
                        try {
                            Date d1 = dateFormat.parse(date1);
                            Date d2 = dateFormat.parse(date2);
                            // 降序排序(最近的在前)
                            return d2.compareTo(d1);
                        } catch (ParseException e) {
                            e.printStackTrace();
                            return 0; // 如果解析失败,保持原顺序
                        }
                    }
                });

                commitsMap.put(issueId + "   " + issueName, commitList);
            }

            title = "版本代码审查-" + jiraCode;
            content  = "\n\t状态:生成完毕!";
            //content += "\n\t总分:" +"提交次数:"+totalCommit+" 总分:"+totalScore+ " = "+ (totalCommit > 0 && totalScore > 0 ? (totalScore / totalCommit) + "" : "-");
            content += "\n\t总分:" + (totalCommit > 0 && totalScore > 0 ? (totalScore / totalCommit) + "" : "-");
            content += "\n\t版本:"+versionName;
            content += "\n\t报告:" + REPORT_VIEW_URL + jiraCode;
            mentioned = username;
            wechatService.sendTextToWeChatWork(title, content, mentioned);
        }catch (Exception e){
            title = "版本代码审查-" + jiraCode;
            content = "\n\t状态:生成失败,请联系管理员!!";
            content += "\n\t版本:"+versionName;
            mentioned = username;
            wechatService.sendTextToWeChatWork(title, content, mentioned);
            log.info("_____________版本代码审查 报告生成失败,错误信息如下:_______________");
            e.printStackTrace();
        }
 return commitsMap;
    }
       public ResponseFrame sendTextToWeChatWork(String title, String content, String mentioned) throws IOException {
        // 1. 构造请求体
        ChatMessageSendReqVO request = new ChatMessageSendReqVO();
        request.setAppId("AI"); // 占位参数
        request.setChannel(ChatMessageSendReqVO.Channel.WECOM_GROUP_BOT);
        request.setToConversations(Collections.singletonList("0")); // 占位数组
        request.setMessageType(ChatMessageSendReqVO.MessageType.TEXT);
        request.setFromSubject("sas-fefe-545"); // Webhook Key

        // 2. 设置消息内容
        ChatMessageSendReqVO.MessageData messageData = new ChatMessageSendReqVO.MessageData();
        String formattedTitle = "【" + title + "】"; // 自动添加标题括号
        messageData.setContent(formattedTitle + content);
        messageData.setMentionedList(Arrays.asList(mentioned.split(","))); // 提及成员
        request.setMessageData(messageData);

        // 3. 记录发送内容(调试用)
        log.info("\n---------- 企业微信通知内容 ----------\n{}", formattedTitle + content + "\n@" + mentioned);

        // 4. 发送请求
        RestTemplate restTemplate = new RestTemplate();
        String url = "ip/common-send";

        try {
            ResponseFrame response = new ResponseFrame();
            if (isLinux) {
                response = restTemplate.postForObject(
                        url,
                        request,
                        ResponseFrame.class
                );
            }
            // 5. 记录完整请求和响应
            log.info(
                    "\n---------- 企业微信群机器人消息发送 ----------\n" +
                            "请求:\n{}\n" +
                            "响应:\n{}",
                    JsonUtils.objToJsonStr(request),
                    JsonUtils.objToJsonStr(response)
            );

            return response;
        } catch (RestClientException e) {
            log.error("企业微信消息发送失败: {}", e.getMessage());
            throw new RuntimeException("企业微信消息发送失败", e);
        }
    } 
     @Autowired
    private TemplateEngine templateEngine;
/**
     * 生成HTML文件
     * @param templateName 模板名称(如"git/report/version/index")
     * @param variables 模板变量
     * @param outputPath 输出文件路径(如"/reports/result.html")
     */
    public void generateHtmlFile(String templateName, Map<String, Object> variables, String outputPath) throws IOException {
        Context context = new Context();
        context.setVariables(variables);

        Path path = Paths.get(outputPath);
        // 确保目录存在
        if (!path.getParent().toFile().exists()) {
            path.getParent().toFile().mkdirs();
        }

        try (FileWriter writer = new FileWriter(path.toFile())) {
            templateEngine.process(templateName, context, writer);
        }
    }
    public void saveAiVerReview(String username,String jiraCode,String versionName,Map<String, List<Map<String, Object>>> commitsMap) {
        Set<String> commitMapKey = commitsMap.keySet();
        for (String issueCodeAndName : commitMapKey) {
            AiVerReview rev = new AiVerReview();
            rev.setRequirementName(issueCodeAndName);
            rev.setReviewDate(Integer.parseInt(getCurrentDateAsYyyyMmDd()));
            rev.setJiraCode(jiraCode);
            rev.setReviewInitiator(WecomUserUtil.getNameByUsername(username));
            rev.setVersionIterationName(versionName);
            List<Map<String, Object>> commitFiles = commitsMap.get(issueCodeAndName);
            for (Map<String, Object> commitFile : commitFiles) {
                rev.setRepositoryName(commitFile.get("projectCode").toString());
                rev.setFileSubmitter(commitFile.get("committerName").toString());
                rev.setFileSubmitMassage(commitFile.get("message").toString());
                rev.setFileSubmissionTime(convertStringToDate(commitFile.get("committedDate").toString()));

                List<CommitDiff> commitDiffs = (List<CommitDiff>)commitFile.get("commitDiffs");
                for (CommitDiff commitDiff : commitDiffs) {
                    String aiScore = commitDiff.getAiScore();
                    if(StringUtils.isEmpty(aiScore) || "-".equals(aiScore)){
                        continue;
                    }
                    rev.setAiScore(aiScore);
                    rev.setSubmittedFileName(commitDiff.getFileName());
                    hisDao.insert(rev);
                }
            }
        }
    }
相关推荐
可爱又迷人的反派角色“yang”1 天前
GitLab配置与git集成实践
linux·网络·git·docker·云计算·gitlab
LucidX1 天前
从零搭建Jenkins+GitLab持续集成环境:详细教程
ci/cd·gitlab·jenkins
m0_485614671 天前
GitLab基础管理
gitlab
不爱学习的笨蛋1 天前
ubuntu安装gitlab
linux·ubuntu·gitlab
梁萌1 天前
Jenkins构建的触发方式
运维·svn·gitlab·jenkins·webhook·job触发方式·自动触发构建
叫致寒吧1 天前
GitLab详解
gitlab
linweidong2 天前
解决gitlab配置Webhooks,提示 Invalid url given的问题
gitlab·notepad++
隔壁阿布都2 天前
Docker 离线安装 GitLab 完整步骤
docker·gitlab
深紫色的三北六号3 天前
基于 Git 某个分支创建一个全新的仓库(GitHub / GitLab)
git·gitlab·github