feat(core): 添加SVN日志查询工具和DeepSeek AI处理功能

- 实现SVN日志查询工具,支持版本范围和用户过滤
- 添加DeepSeek API集成,用于AI分析日志内容
- 创建Excel生成器,输出工作量统计报表
- 添加日志实体类和项目配置管理功能
- 集成POI库支持Excel文件操作
- 实现Markdown格式日志导出功能
This commit is contained in:
liumangmang
2026-02-05 09:11:17 +08:00
parent 25248a0275
commit a6817fd9bf
10 changed files with 1556 additions and 1 deletions

View File

@@ -0,0 +1,587 @@
package com.svnlog;
import okhttp3.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 使用DeepSeek API处理SVN日志并生成工作量统计Excel
*/
public class DeepSeekLogProcessor {
private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions";
private static final String API_KEY = "sk-48c59012c93b43a08fecbaf3e74799e7"; // 用户需要替换为实际的API Key
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(300, java.util.concurrent.TimeUnit.SECONDS) // 5分钟读取超时
.writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
.build();
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.println("===========================================");
System.out.println(" SVN日志工作量统计工具DeepSeek版");
System.out.println(" 支持多项目汇总分析");
System.out.println("===========================================");
System.out.println();
// 读取markdown日志文件目录
System.out.print("请输入markdown日志文件所在目录路径 (回车使用当前目录): ");
String dirPath = scanner.nextLine().trim();
File dir;
if (dirPath.isEmpty()) {
dir = new File(".");
} else {
dir = new File(dirPath);
}
if (!dir.exists() || !dir.isDirectory()) {
System.err.println("错误: 目录不存在或不是有效目录!");
return;
}
// 扫描目录中的所有 .md 文件
File[] mdFiles = dir.listFiles((d, name) -> name.endsWith(".md"));
if (mdFiles == null || mdFiles.length == 0) {
System.err.println("错误: 目录中未找到任何 .md 文件!");
return;
}
System.out.println("找到 " + mdFiles.length + " 个日志文件:");
for (File file : mdFiles) {
System.out.println(" - " + file.getName());
}
System.out.println();
// 输入工作周期
SimpleDateFormat periodSdf = new SimpleDateFormat("yyyy年MM月");
String defaultPeriod = periodSdf.format(new Date());
System.out.print("请输入工作周期 (例如: 2025年12月回车使用默认: " + defaultPeriod + "): ");
String period = scanner.nextLine().trim();
if (period.isEmpty()) {
period = defaultPeriod;
System.out.println("使用默认工作周期: " + period);
}
// 读取并合并所有markdown文件
String combinedContent = readAndCombineMarkdownFiles(mdFiles);
System.out.println("成功读取并合并 " + mdFiles.length + " 个日志文件,总长度: " + combinedContent.length() + " 字符");
// 提示API Key
System.out.print("请输入DeepSeek API Key (留空使用代码中预设的): ");
String inputApiKey = scanner.nextLine().trim();
String apiKey = inputApiKey.isEmpty() ? API_KEY : inputApiKey;
if (apiKey.equals("YOUR_DEEPSEEK_API_KEY")) {
System.err.println("错误: 请提供有效的DeepSeek API Key");
return;
}
// 询问输出文件名
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String defaultOutput = sdf.format(new Date()) + "工作量统计.xlsx";
System.out.print("请输入输出Excel文件名 (回车使用默认: " + defaultOutput + "): ");
String outputPath = scanner.nextLine().trim();
if (outputPath.isEmpty()) {
outputPath = defaultOutput;
}
System.out.println();
System.out.println("正在调用DeepSeek API分析日志...");
// 调用DeepSeek API处理日志
String prompt = buildPrompt(combinedContent, period);
String aiResponse = callDeepSeekAPI(apiKey, prompt);
if (aiResponse == null) {
System.err.println("DeepSeek API调用失败请检查网络连接和API Key。");
return;
}
if (aiResponse.isEmpty()) {
System.err.println("DeepSeek API返回空响应请重试或联系技术支持。");
return;
}
System.out.println("DeepSeek分析完成正在生成Excel...");
// 生成Excel
generateExcel(outputPath, aiResponse);
System.out.println();
System.out.println("Excel文件生成成功: " + outputPath);
System.out.println();
} catch (Exception e) {
System.err.println("发生错误: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 读取文件内容
*/
private static String readFile(String path) throws IOException {
return new String(Files.readAllBytes(new File(path).toPath()), "UTF-8");
}
/**
* 读取并合并多个markdown文件的内容
*/
private static String readAndCombineMarkdownFiles(File[] mdFiles) throws IOException {
StringBuilder combinedContent = new StringBuilder();
for (File file : mdFiles) {
String projectName = extractProjectName(file.getName());
String content = readFile(file.getAbsolutePath());
combinedContent.append("\n\n");
combinedContent.append("=== 项目: ").append(projectName).append(" ===\n");
combinedContent.append(content);
}
return combinedContent.toString();
}
/**
* 从文件名中提取项目名称
* 例如: svn_log_PRS-7050场站智慧管控_20260130_093348.md -> PRS-7050场站智慧管控
*/
private static String extractProjectName(String fileName) {
// 去掉 svn_log_ 前缀
if (fileName.startsWith("svn_log_")) {
fileName = fileName.substring(8);
}
// 去掉 .md 后缀
if (fileName.endsWith(".md")) {
fileName = fileName.substring(0, fileName.length() - 3);
}
// 去掉时间戳部分 (格式: _YYYYMMDD_HHMMSS)
int lastUnderscore = fileName.lastIndexOf('_');
if (lastUnderscore > 0) {
// 检查是否是时间戳格式
String timestampPart = fileName.substring(lastUnderscore + 1);
if (timestampPart.matches("\\d{8}_\\d{6}")) {
fileName = fileName.substring(0, lastUnderscore);
}
}
return fileName;
}
/**
* 构建发送给DeepSeek的提示词
*/
private static String buildPrompt(String markdownContent, String period) {
return "你是一个专业的项目管理助手。请分析以下多个项目的SVN日志并生成工作量统计数据。\n\n" +
"日志内容包含多个项目,每个项目之间用 === 项目: xxx === 标识。\n" +
"工作周期: " + period + "\n\n" +
"SVN日志内容:\n" + markdownContent + "\n\n" +
"请按照以下JSON格式返回工作量统计数据:\n" +
"{\n" +
" \"team\": \"所属班组\",\n" +
" \"contact\": \"技术对接人\",\n" +
" \"developer\": \"开发人员\",\n" +
" \"period\": \"" + period + "\",\n" +
" \"records\": [\n" +
" {\n" +
" \"sequence\": 1,\n" +
" \"project\": \"项目1/项目2/项目3\",\n" +
" \"content\": \"# 项目1\\n1.工作内容1\\n2.工作内容2\\n\\n# 项目2\\n1.工作内容1\\n2.工作内容2\\n\\n# 项目3\\n1.工作内容1\\n2.工作内容2\"\n" +
" }\n" +
" ]\n" +
"}\n\n" +
"重要要求:\n" +
"1. 根据日志作者确定开发人员\n" +
"2. 将所有项目的工作内容合并到一条记录中\n" +
"3. 项目名称字段project使用 / 分隔多个项目,例如:\"PRS7050场站系统/PRS7950智能巡视现场问题/PRS7950电科院测试\"\n" +
"4. 具体工作内容字段content使用 # 作为项目分类标识,格式为:\"# 项目名称\\n1.工作内容\\n2.工作内容\\n\\n# 下一个项目\\n1.工作内容\"\n" +
"5. 不同项目之间用空行分隔\n" +
"6. 只返回JSON不要有其他文字\n" +
"7. 提取具体工作内容,要详细和有条理\n" +
"8. 项目名称要简洁明确,去掉多余的前缀和后缀";
}
/**
* 调用DeepSeek API流式输出
*/
private static String callDeepSeekAPI(String apiKey, String prompt) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", "deepseek-chat");
// 创建消息对象,包含 role 和 content 字段
JSONObject messageObj = new JSONObject();
messageObj.put("role", "user");
messageObj.put("content", prompt);
// 创建消息数组
com.google.gson.JsonArray messagesArray = new com.google.gson.JsonArray();
messagesArray.add(messageObj.jsonObject);
requestBody.put("messages", messagesArray);
requestBody.put("temperature", 0.7);
requestBody.put("max_tokens", 4000);
requestBody.put("stream", Optional.of(true)); // 启用流式输出
Request request = new Request.Builder()
.url(DEEPSEEK_API_URL)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(requestBody.toString(), MediaType.parse("application/json")))
.build();
StringBuilder fullResponse = new StringBuilder();
int chunkCount = 0;
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
System.err.println("API调用失败: " + response.code() + " " + response.message());
String errorResponse = response.body().string();
System.err.println("响应: " + errorResponse);
return null;
}
// 读取流式响应
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().byteStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ")) {
String data = line.substring(6);
if (data.equals("[DONE]")) {
break;
}
try {
JSONObject chunk = new JSONObject(data);
if (chunk.has("choices") && chunk.getJSONArray("choices").size() > 0) {
JSONObject choice = chunk.getJSONArray("choices").get(0);
if (choice.has("delta")) {
JSONObject delta = choice.getJSONObject("delta");
if (delta.has("content")) {
String content = delta.optString("content", "");
fullResponse.append(content);
chunkCount++;
// 实时打印处理进度
System.out.print(content);
System.out.flush();
}
}
}
} catch (Exception e) {
// 忽略解析错误,继续处理下一行
}
}
}
}
} catch (Exception e) {
System.err.println("API调用过程中发生异常: " + e.getMessage());
e.printStackTrace();
return null;
}
System.out.println(); // 换行
System.out.println("收到 " + chunkCount + " 个数据块");
if (fullResponse.length() == 0) {
System.err.println("警告: 未收到任何响应内容");
}
return fullResponse.toString();
}
/**
* 从响应中提取纯 JSON 内容
*/
private static String extractJson(String response) {
String trimmed = response.trim();
// 去除 ```json 标记
if (trimmed.startsWith("```json")) {
trimmed = trimmed.substring(7);
} else if (trimmed.startsWith("```")) {
trimmed = trimmed.substring(3);
}
// 去除 ``` 结束标记
if (trimmed.endsWith("```")) {
trimmed = trimmed.substring(0, trimmed.length() - 3);
}
return trimmed.trim();
}
/**
* 生成Excel文件
*/
private static void generateExcel(String outputPath, String jsonResponse) throws IOException {
// 提取纯 JSON 内容(去除 ```json 和 ``` 标记)
String cleanJson = extractJson(jsonResponse);
// 解析JSON响应
JSONObject data = new JSONObject(cleanJson);
// 创建工作簿
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("工作表1");
// 创建样式
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle contentStyle = createContentStyle(workbook);
CellStyle workContentStyle = createWorkContentStyle(workbook);
// 创建表头7列与参考文件一致
Row headerRow = sheet.createRow(0);
headerRow.setHeightInPoints(14.25f); // 表头行高
String[] headers = {"序号", "所属班组", "技术对接", "开发人员", "工作周期", "开发项目名称", "具体工作内容"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 设置固定列宽(与参考文件一致)
sheet.setColumnWidth(0, 2048); // 序号8.00字符
sheet.setColumnWidth(1, 3328); // 所属班组13.00字符
sheet.setColumnWidth(2, 4608); // 技术对接18.00字符
sheet.setColumnWidth(3, 3840); // 开发人员15.00字符
sheet.setColumnWidth(4, 5888); // 工作周期23.00字符
sheet.setColumnWidth(5, 14080); // 开发项目名称55.00字符
sheet.setColumnWidth(6, 43991); // 具体工作内容171.84字符
// 获取记录
String team = data.optString("team", "");
String contact = data.optString("contact", "");
String developer = data.optString("developer", "");
String period = data.optString("period", "");
if (data.has("records")) {
JSONArray recordsArray = data.getJSONArray("records");
int rowNum = 1;
for (int i = 0; i < recordsArray.size(); i++) {
JSONObject record = recordsArray.get(i);
Row row = sheet.createRow(rowNum++);
row.setHeightInPoints(16.50f); // 内容行高
// 序号
Cell cell0 = row.createCell(0);
cell0.setCellValue(record.optDouble("sequence", i + 1));
cell0.setCellStyle(contentStyle);
// 所属班组
Cell cell1 = row.createCell(1);
cell1.setCellValue(team);
cell1.setCellStyle(contentStyle);
// 技术对接
Cell cell2 = row.createCell(2);
cell2.setCellValue(contact);
cell2.setCellStyle(contentStyle);
// 开发人员
Cell cell3 = row.createCell(3);
cell3.setCellValue(developer);
cell3.setCellStyle(contentStyle);
// 工作周期
Cell cell4 = row.createCell(4);
cell4.setCellValue(period);
cell4.setCellStyle(contentStyle);
// 项目名称(多个项目用 / 分隔)
Cell cell5 = row.createCell(5);
cell5.setCellValue(record.optString("project", ""));
cell5.setCellStyle(contentStyle);
// 工作内容(支持换行,用 # 标识不同项目)
Cell cell6 = row.createCell(6);
cell6.setCellValue(record.optString("content", ""));
cell6.setCellStyle(workContentStyle); // 使用工作内容样式
}
}
// 写入文件
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
workbook.write(fos);
}
workbook.close();
}
/**
* 创建表头样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("SimSun"); // 字体名称SimSun
font.setFontHeightInPoints((short) 11); // 字体大小11磅
font.setBold(false); // 不粗体
font.setColor(IndexedColors.BLACK.getIndex()); // 黑色
style.setFont(font);
style.setAlignment(HorizontalAlignment.GENERAL); // 水平对齐:常规
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直对齐:居中
style.setFillPattern(FillPatternType.NO_FILL); // 无填充
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
style.setWrapText(false); // 不换行
return style;
}
/**
* 创建普通内容样式列A-F
*/
private static CellStyle createContentStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("宋体"); // 字体名称:宋体
font.setFontHeightInPoints((short) 11); // 字体大小11磅
font.setBold(false); // 不粗体
style.setFont(font);
style.setAlignment(HorizontalAlignment.GENERAL); // 水平对齐:常规
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直对齐:居中
style.setFillPattern(FillPatternType.NO_FILL); // 无填充
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.NONE);
style.setBorderLeft(BorderStyle.NONE);
style.setBorderRight(BorderStyle.NONE);
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
style.setWrapText(false); // 不换行
return style;
}
/**
* 创建工作内容样式列G
*/
private static CellStyle createWorkContentStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("NSimSun"); // 字体名称:新宋体
font.setFontHeightInPoints((short) 14); // 字体大小14磅
font.setBold(true); // 粗体
font.setColor(IndexedColors.BLACK.getIndex()); // 黑色
style.setFont(font);
style.setAlignment(HorizontalAlignment.LEFT); // 水平对齐:左对齐
style.setVerticalAlignment(VerticalAlignment.TOP); // 垂直对齐:顶部
style.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); // 黄色背景
style.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 实心填充
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.NONE);
style.setBorderLeft(BorderStyle.NONE);
style.setBorderRight(BorderStyle.NONE);
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
style.setWrapText(true); // 自动换行
return style;
}
/**
* 简单的JSON工具类
*/
static class JSONObject {
private final com.google.gson.JsonObject jsonObject;
public JSONObject() {
this.jsonObject = new com.google.gson.JsonObject();
}
public JSONObject(String jsonString) {
com.google.gson.Gson gson = new com.google.gson.Gson();
this.jsonObject = gson.fromJson(jsonString, com.google.gson.JsonObject.class);
}
public JSONObject(String key, String value) {
this();
put(key, value);
}
public void put(String key, String value) {
jsonObject.addProperty(key, value);
}
public void put(String key, int value) {
jsonObject.addProperty(key, String.valueOf(value));
}
public void put(String key, double value) {
jsonObject.addProperty(key, String.valueOf(value));
}
public void put(String key, Object value) {
com.google.gson.Gson gson = new com.google.gson.Gson();
jsonObject.add(key, gson.toJsonTree(value));
}
public String optString(String key, String defaultValue) {
if (jsonObject.has(key) && !jsonObject.get(key).isJsonNull()) {
return jsonObject.get(key).getAsString();
}
return defaultValue;
}
public double optDouble(String key, double defaultValue) {
if (jsonObject.has(key) && !jsonObject.get(key).isJsonNull()) {
return jsonObject.get(key).getAsDouble();
}
return defaultValue;
}
public boolean has(String key) {
return jsonObject.has(key);
}
public JSONArray getJSONArray(String key) {
return new JSONArray(jsonObject.get(key).getAsJsonArray());
}
public JSONObject getJSONObject(String key) {
return new JSONObject(jsonObject.get(key).getAsJsonObject().toString());
}
@Override
public String toString() {
return jsonObject.toString();
}
}
/**
* 简单的JSONArray工具类
*/
static class JSONArray {
private final com.google.gson.JsonArray jsonArray;
public JSONArray(com.google.gson.JsonArray jsonArray) {
this.jsonArray = jsonArray;
}
public int size() {
return jsonArray.size();
}
public JSONObject get(int index) {
return new JSONObject(jsonArray.get(index).getAsJsonObject().toString());
}
@SuppressWarnings("unchecked")
public <T> java.util.List<JSONObject> toList() {
java.util.List<JSONObject> list = new ArrayList<>();
for (int i = 0; i < jsonArray.size(); i++) {
list.add(get(i));
}
return list;
}
}
}