背景:
为提高公司开发人员的代码开发质量会对项目进行监控并且给出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);
}
}
}
}