chore(svn-log-tool): 移除svm日志工具及示例文件,更新编码配置
- 删除DeepSeekLogProcessor日志处理类及相关依赖文件 - 删除Main主程序及辅助工具类LogEntry和ExcelAnalyzer - 移除示例日志文件example_log.md - 更新.idea/encodings.xml,添加src/main/java和src/main/resources的UTF-8编码配置 - 添加标准.gitignore配置,忽略Maven、IDEA、日志、打包文件及部分系统文件
This commit is contained in:
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# Compiled class files
|
||||
*.class
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Package Files
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# Generated files
|
||||
md/
|
||||
*.xlsx
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Virtual machine crash logs
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
2
.idea/encodings.xml
generated
2
.idea/encodings.xml
generated
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/svn-log-tool/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/svn-log-tool/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
|
||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -6,6 +6,7 @@
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/svn-log-tool/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/日志.iml" filepath="$PROJECT_DIR$/.idea/日志.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1 +0,0 @@
|
||||
{"mcpServers": {"context7": {"command": "npx", "args": ["-y", "@iflow-mcp/context7-mcp@1.0.0"]}, "excel-edit-server": {"command": "uvx", "args": ["--python", "3.12.q7", "iflow-mcp_excel-edit-server@latest", "--workspace-path", "."]}}}
|
||||
@@ -1,121 +0,0 @@
|
||||
# SVN日志工作量统计工具(DeepSeek版)
|
||||
|
||||
## 功能说明
|
||||
|
||||
这个工具可以根据SVN日志的markdown文件,调用DeepSeek API分析日志内容,并生成符合格式要求的工作量统计Excel文件。
|
||||
|
||||
## 使用步骤
|
||||
|
||||
### 1. 准备SVN日志markdown文件
|
||||
|
||||
使用原有的SVN日志工具生成markdown文件:
|
||||
```bash
|
||||
java -jar svn-log-tool-1.0.0-jar-with-dependencies.jar
|
||||
```
|
||||
|
||||
按照提示输入SVN仓库地址、账号、密码等信息,生成markdown格式的日志文件。
|
||||
|
||||
### 2. 运行DeepSeek日志处理工具
|
||||
|
||||
```bash
|
||||
java -cp target/svn-log-tool-1.0.0-jar-with-dependencies.jar com.svnlog.DeepSeekLogProcessor
|
||||
```
|
||||
|
||||
或者使用Maven运行:
|
||||
```bash
|
||||
mvn exec:java -Dexec.mainClass="com.svnlog.DeepSeekLogProcessor"
|
||||
```
|
||||
|
||||
### 3. 按照提示输入信息
|
||||
|
||||
程序会依次提示输入:
|
||||
- **markdown日志文件路径**:可以直接回车使用当前目录下最新的`svn_log_*.md`文件
|
||||
- **DeepSeek API Key**:请提供有效的DeepSeek API Key(也可以直接在代码中修改`API_KEY`常量)
|
||||
- **输出Excel文件名**:可以直接回车使用默认文件名(格式:`YYYYMM工作量统计.xlsx`)
|
||||
|
||||
### 4. 等待处理完成
|
||||
|
||||
程序会自动:
|
||||
1. 读取markdown日志文件
|
||||
2. 调用DeepSeek API分析日志内容
|
||||
3. 根据分析结果生成Excel文件
|
||||
|
||||
生成的Excel文件格式与`202512工作量统计_刘靖.xlsx`保持一致。
|
||||
|
||||
## Excel文件格式说明
|
||||
|
||||
生成的Excel文件包含以下列:
|
||||
- 序号
|
||||
- 所属班组
|
||||
- 技术对接
|
||||
- 开发人员
|
||||
- 工作周期
|
||||
- 开发项目名称
|
||||
- 具体工作内容
|
||||
- 空列(4个)
|
||||
|
||||
## DeepSeek API配置
|
||||
|
||||
在`DeepSeekLogProcessor.java`中,可以修改以下配置:
|
||||
|
||||
```java
|
||||
private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions";
|
||||
private static final String API_KEY = "YOUR_DEEPSEEK_API_KEY"; // 请替换为实际的API Key
|
||||
```
|
||||
|
||||
## 提示词说明
|
||||
|
||||
工具会向DeepSeek发送以下提示词,要求AI以JSON格式返回工作量统计:
|
||||
|
||||
```json
|
||||
{
|
||||
"team": "所属班组",
|
||||
"contact": "技术对接人",
|
||||
"developer": "开发人员",
|
||||
"period": "工作周期 (例如: 2025年12月)",
|
||||
"records": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"project": "项目名称",
|
||||
"content": "具体工作内容"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **API Key安全**:请妥善保管您的DeepSeek API Key,不要将其提交到代码仓库中
|
||||
2. **网络连接**:需要能够访问DeepSeek API服务器
|
||||
3. **日志格式**:markdown文件需要由SVN日志工具生成,包含完整的日志信息
|
||||
4. **成本控制**:DeepSeek API可能产生费用,请注意控制使用频率
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 编译错误
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 运行时找不到主类
|
||||
确保使用正确的jar文件:
|
||||
```bash
|
||||
java -cp target/svn-log-tool-1.0.0-jar-with-dependencies.jar com.svnlog.DeepSeekLogProcessor
|
||||
```
|
||||
|
||||
### API调用失败
|
||||
- 检查API Key是否正确
|
||||
- 检查网络连接是否正常
|
||||
- 检查DeepSeek API服务是否可用
|
||||
|
||||
## 依赖说明
|
||||
|
||||
项目使用以下主要依赖:
|
||||
- SVNKit 1.10.11:SVN操作
|
||||
- Apache POI 5.2.5:Excel文件读写
|
||||
- OkHttp 4.12.0:HTTP客户端
|
||||
- Gson 2.10.1:JSON处理
|
||||
|
||||
## 许可证
|
||||
|
||||
本工具仅供内部使用。
|
||||
@@ -1,164 +0,0 @@
|
||||
# SVN 日志报告
|
||||
|
||||
## 查询条件
|
||||
|
||||
- **SVN地址**: `https://svn.example.com/project`
|
||||
- **账号**: `testuser`
|
||||
- **版本范围**: r1000 - r1050
|
||||
- **生成时间**: 2025-01-30 10:00:00
|
||||
|
||||
## 统计信息
|
||||
|
||||
- **总记录数**: 5 条
|
||||
|
||||
### 按作者统计
|
||||
|
||||
| 作者 | 提交次数 |
|
||||
|------|----------|
|
||||
| `zhangsan` | 3 |
|
||||
| `lisi` | 2 |
|
||||
|
||||
## 日志详情
|
||||
|
||||
### r1050
|
||||
|
||||
**作者**: `zhangsan`
|
||||
**时间**: 2025-01-30 09:30:00
|
||||
**版本**: r1050
|
||||
|
||||
**变更文件**:
|
||||
|
||||
```
|
||||
/src/main/java/com/example/Service.java
|
||||
/src/test/java/com/example/ServiceTest.java
|
||||
```
|
||||
|
||||
**提交信息**:
|
||||
|
||||
```
|
||||
修复用户登录时的空指针异常问题
|
||||
|
||||
1. 修复用户服务中的空指针检查
|
||||
2. 添加单元测试验证修复
|
||||
3. 更新相关文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### r1049
|
||||
|
||||
**作者**: `zhangsan`
|
||||
**时间**: 2025-01-29 16:45:00
|
||||
**版本**: r1049
|
||||
|
||||
**变更文件**:
|
||||
|
||||
```
|
||||
/src/main/java/com/example/Controller.java
|
||||
```
|
||||
|
||||
**提交信息**:
|
||||
|
||||
```
|
||||
优化API接口响应速度
|
||||
|
||||
# 性能优化
|
||||
1. 添加数据库查询缓存
|
||||
2. 优化SQL查询语句
|
||||
3. 减少不必要的对象创建
|
||||
|
||||
# 测试验证
|
||||
- 响应时间从500ms降低到200ms
|
||||
- 通过所有单元测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### r1048
|
||||
|
||||
**作者**: `lisi`
|
||||
**时间**: 2025-01-29 14:20:00
|
||||
**版本**: r1048
|
||||
|
||||
**变更文件**:
|
||||
|
||||
```
|
||||
/src/main/java/com/example/Dao.java
|
||||
/src/main/resources/mapper/UserMapper.xml
|
||||
```
|
||||
|
||||
**提交信息**:
|
||||
|
||||
```
|
||||
实现用户数据批量导入功能
|
||||
|
||||
# 核心功能
|
||||
1. 支持Excel文件上传
|
||||
2. 数据验证和错误处理
|
||||
3. 批量插入数据库
|
||||
|
||||
# 配置变更
|
||||
- 添加文件上传大小限制
|
||||
- 配置批量插入批次大小
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### r1047
|
||||
|
||||
**作者**: `zhangsan`
|
||||
**时间**: 2025-01-28 11:00:00
|
||||
**版本**: r1047
|
||||
|
||||
**变更文件**:
|
||||
|
||||
```
|
||||
/src/main/java/com/example/Util.java
|
||||
/src/main/resources/application.yml
|
||||
```
|
||||
|
||||
**提交信息**:
|
||||
|
||||
```
|
||||
添加日志记录功能
|
||||
|
||||
# 新增功能
|
||||
1. 集成Log4j2日志框架
|
||||
2. 配置日志输出格式
|
||||
3. 添加关键操作日志记录
|
||||
|
||||
# 配置更新
|
||||
- 设置日志级别为INFO
|
||||
- 配置日志文件滚动策略
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### r1046
|
||||
|
||||
**作者**: `lisi`
|
||||
**时间**: 2025-01-27 15:30:00
|
||||
**版本**: r1046
|
||||
|
||||
**变更文件**:
|
||||
|
||||
```
|
||||
/src/main/java/com/example/Model.java
|
||||
```
|
||||
|
||||
**提交信息**:
|
||||
|
||||
```
|
||||
重构数据模型类
|
||||
|
||||
# 重构内容
|
||||
1. 优化字段命名规范
|
||||
2. 添加数据验证注解
|
||||
3. 实现序列化接口
|
||||
|
||||
# 兼容性
|
||||
- 保持向后兼容
|
||||
- 更新相关测试用例
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,109 +0,0 @@
|
||||
<?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>
|
||||
|
||||
<groupId>com.svnlog</groupId>
|
||||
<artifactId>svn-log-tool</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>SVN Log Tool</name>
|
||||
<description>SVN日志查询工具,支持版本范围过滤和用户名过滤,可导出Markdown格式</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- SVNKit for SVN operations -->
|
||||
<dependency>
|
||||
<groupId>org.tmatesoft.svnkit</groupId>
|
||||
<artifactId>svnkit</artifactId>
|
||||
<version>1.10.11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI for Excel operations -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- HTTP Client for API calls -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON parsing -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.svnlog.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.svnlog.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,587 +0,0 @@
|
||||
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", 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, value);
|
||||
}
|
||||
|
||||
public void put(String key, double value) {
|
||||
jsonObject.addProperty(key, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.svnlog;
|
||||
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 临时工具类,用于分析现有Excel文件格式
|
||||
*/
|
||||
public class ExcelAnalyzer {
|
||||
public static void main(String[] args) {
|
||||
String excelPath = "/home/liumangmang/opencode/日志/202512工作量统计_刘靖.xlsx";
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(excelPath);
|
||||
Workbook workbook = new XSSFWorkbook(fis)) {
|
||||
|
||||
System.out.println("工作表数量: " + workbook.getNumberOfSheets());
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
||||
System.out.println("工作表 " + i + ": " + workbook.getSheetName(i));
|
||||
}
|
||||
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
System.out.println("\n工作表名称: " + sheet.getSheetName());
|
||||
System.out.println("总行数: " + sheet.getPhysicalNumberOfRows());
|
||||
System.out.println("最后一行索引: " + sheet.getLastRowNum());
|
||||
|
||||
// 读取前20行数据
|
||||
System.out.println("\n前20行数据:");
|
||||
for (int i = 0; i <= Math.min(19, sheet.getLastRowNum()); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row != null) {
|
||||
System.out.print("第" + (i + 1) + "行: ");
|
||||
for (Cell cell : row) {
|
||||
String value = getCellValueAsString(cell);
|
||||
System.out.print("[" + value + "] ");
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
// 读取表头
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow != null) {
|
||||
System.out.println("\n表头列数: " + headerRow.getLastCellNum());
|
||||
System.out.print("表头: ");
|
||||
for (Cell cell : headerRow) {
|
||||
System.out.print("[" + getCellValueAsString(cell) + "] ");
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("读取Excel文件出错: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getCellValueAsString(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue().trim();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf(cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
case BLANK:
|
||||
return "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.svnlog;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class LogEntry {
|
||||
private long revision;
|
||||
private String author;
|
||||
private Date date;
|
||||
private String message;
|
||||
private String[] changedPaths;
|
||||
|
||||
public LogEntry() {
|
||||
}
|
||||
|
||||
public LogEntry(long revision, String author, Date date, String message) {
|
||||
this.revision = revision;
|
||||
this.author = author;
|
||||
this.date = date;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void setRevision(long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String[] getChangedPaths() {
|
||||
return changedPaths;
|
||||
}
|
||||
|
||||
public void setChangedPaths(String[] changedPaths) {
|
||||
this.changedPaths = changedPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogEntry{" +
|
||||
"revision=" + revision +
|
||||
", author='" + author + '\'' +
|
||||
", date=" + date +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package com.svnlog;
|
||||
|
||||
import org.tmatesoft.svn.core.SVNException;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
private static final Scanner scanner = new Scanner(System.in);
|
||||
private static final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||
|
||||
// 预设项目列表
|
||||
private static final Project[] PRESET_PROJECTS = {
|
||||
new Project("PRS-7050场站智慧管控", "https://10.6.220.216:48080/svn/houtai/001_后台软件/PRS-7050场站智慧管控/01_开发库/V1.00/src_java"),
|
||||
new Project("PRS-7950在线巡视", "https://10.6.220.216:48080/svn/houtai/001_后台软件/PRS-7950在线巡视/01_开发库/V2.00/src_java"),
|
||||
new Project("PRS-7950在线巡视电科院测试版", "https://10.6.220.216:48080/svn/houtai/001_后台软件/PRS-7950在线巡视/01_开发库/V1.00_2024/src_java")
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("===========================================");
|
||||
System.out.println(" SVN 日志查询工具 v1.0");
|
||||
System.out.println("===========================================");
|
||||
System.out.println();
|
||||
|
||||
try {
|
||||
// 创建 md 目录
|
||||
File mdDir = new File("md");
|
||||
if (!mdDir.exists()) {
|
||||
boolean created = mdDir.mkdir();
|
||||
if (created) {
|
||||
System.out.println("已创建 md 目录用于存放日志文件");
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
// 选择项目
|
||||
Project selectedProject = selectProject();
|
||||
String url = selectedProject.getUrl();
|
||||
System.out.println("已选择项目: " + selectedProject.getName());
|
||||
System.out.println("SVN地址: " + url);
|
||||
System.out.println();
|
||||
|
||||
String username = readInput("请输入SVN账号: ");
|
||||
String password = readPassword("请输入SVN密码: ");
|
||||
|
||||
System.out.println("正在连接SVN仓库...");
|
||||
SVNLogFetcher fetcher = new SVNLogFetcher(url, username, password);
|
||||
fetcher.testConnection();
|
||||
System.out.println("连接成功!");
|
||||
System.out.println();
|
||||
|
||||
long latestRevision = fetcher.getLatestRevision();
|
||||
System.out.println("最新版本号: " + latestRevision);
|
||||
System.out.println();
|
||||
|
||||
long startRevision = readLongInput("请输入开始版本号 (回车使用最新版本): ", latestRevision);
|
||||
long endRevision = readLongInput("请输入结束版本号 (回车使用最新版本): ", latestRevision);
|
||||
String filterUser = readInput("请输入过滤用户名 (包含匹配,回车跳过过滤): ");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("正在获取日志...");
|
||||
List<LogEntry> logs = fetcher.fetchLogs(startRevision, endRevision, filterUser);
|
||||
|
||||
if (logs.isEmpty()) {
|
||||
System.out.println("没有找到符合条件的日志记录。");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("获取到 " + logs.size() + " 条日志记录。");
|
||||
System.out.println();
|
||||
|
||||
// 生成Markdown文件(保存到 md 目录)
|
||||
String fileName = "md/svn_log_" + selectedProject.getName() + "_" + fileNameDateFormat.format(new Date()) + ".md";
|
||||
generateMarkdown(fileName, url, username, startRevision, endRevision, filterUser, logs, fetcher);
|
||||
|
||||
System.out.println();
|
||||
System.out.println("日志已成功导出到: " + fileName);
|
||||
System.out.println();
|
||||
|
||||
} catch (SVNException e) {
|
||||
System.err.println("SVN错误: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
System.err.println("发生错误: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 让用户选择项目
|
||||
*/
|
||||
private static Project selectProject() {
|
||||
System.out.println("请选择SVN项目:");
|
||||
for (int i = 0; i < PRESET_PROJECTS.length; i++) {
|
||||
System.out.println(" " + (i + 1) + ". " + PRESET_PROJECTS[i].getName());
|
||||
}
|
||||
System.out.println(" 0. 自定义SVN地址");
|
||||
System.out.println();
|
||||
|
||||
while (true) {
|
||||
System.out.print("请输入项目编号 (1-" + PRESET_PROJECTS.length + ", 0为自定义): ");
|
||||
String input = scanner.nextLine().trim();
|
||||
|
||||
try {
|
||||
int choice = Integer.parseInt(input);
|
||||
|
||||
if (choice == 0) {
|
||||
String customUrl = readInput("请输入SVN仓库地址: ");
|
||||
return new Project("自定义项目", customUrl);
|
||||
} else if (choice >= 1 && choice <= PRESET_PROJECTS.length) {
|
||||
return PRESET_PROJECTS[choice - 1];
|
||||
} else {
|
||||
System.out.println("输入无效,请重新选择!");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("输入无效,请输入数字!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String readInput(String prompt) {
|
||||
System.out.print(prompt);
|
||||
return scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
private static String readPassword(String prompt) {
|
||||
if (System.console() != null) {
|
||||
char[] password = System.console().readPassword("%s", prompt);
|
||||
return new String(password);
|
||||
} else {
|
||||
System.out.print(prompt);
|
||||
return scanner.nextLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static long readLongInput(String prompt, long defaultValue) {
|
||||
System.out.print(prompt);
|
||||
String input = scanner.nextLine().trim();
|
||||
|
||||
if (input.isEmpty()) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(input);
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("输入无效,使用默认值: " + defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateMarkdown(String fileName, String url, String username,
|
||||
long startRevision, long endRevision, String filterUser,
|
||||
List<LogEntry> logs, SVNLogFetcher fetcher) throws IOException {
|
||||
StringBuilder markdown = new StringBuilder();
|
||||
|
||||
// 标题
|
||||
markdown.append("# SVN 日志报告\n\n");
|
||||
|
||||
// 查询条件(简化版)
|
||||
markdown.append("## 查询条件\n\n");
|
||||
markdown.append("- **SVN地址**: `").append(url).append("`\n");
|
||||
markdown.append("- **版本范围**: r").append(startRevision).append(" - r").append(endRevision).append("\n");
|
||||
if (filterUser != null && !filterUser.isEmpty()) {
|
||||
markdown.append("- **过滤用户**: `").append(filterUser).append("`\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
|
||||
// 日志详情(简化版,只包含作者、时间、版本、提交信息)
|
||||
markdown.append("## 日志详情\n\n");
|
||||
|
||||
for (LogEntry entry : logs) {
|
||||
markdown.append("### r").append(entry.getRevision()).append("\n\n");
|
||||
markdown.append("**作者**: `").append(entry.getAuthor()).append("` \n");
|
||||
markdown.append("**时间**: ").append(fetcher.formatDate(entry.getDate())).append(" \n");
|
||||
markdown.append("**版本**: r").append(entry.getRevision()).append("\n\n");
|
||||
|
||||
String message = entry.getMessage();
|
||||
if (message != null && !message.isEmpty()) {
|
||||
markdown.append("**提交信息**:\n\n");
|
||||
markdown.append("```\n").append(message).append("\n```\n\n");
|
||||
} else {
|
||||
markdown.append("**提交信息**: (无)\n\n");
|
||||
}
|
||||
|
||||
markdown.append("---\n\n");
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
|
||||
writer.write(markdown.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目信息类
|
||||
*/
|
||||
private static class Project {
|
||||
private String name;
|
||||
private String url;
|
||||
|
||||
public Project(String name, String url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.svnlog;
|
||||
|
||||
import org.tmatesoft.svn.core.*;
|
||||
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
|
||||
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
|
||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
|
||||
import org.tmatesoft.svn.core.wc.SVNWCUtil;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
public class SVNLogFetcher {
|
||||
private String url;
|
||||
private String username;
|
||||
private String password;
|
||||
private SVNRepository repository;
|
||||
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public SVNLogFetcher(String url, String username, String password) throws SVNException {
|
||||
this.url = url;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
SVNRepositoryFactoryImpl.setup();
|
||||
this.repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
|
||||
|
||||
ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password.toCharArray());
|
||||
repository.setAuthenticationManager(authManager);
|
||||
}
|
||||
|
||||
public List<LogEntry> fetchLogs(long startRevision, long endRevision) throws SVNException {
|
||||
return fetchLogs(startRevision, endRevision, null);
|
||||
}
|
||||
|
||||
public List<LogEntry> fetchLogs(long startRevision, long endRevision, String filterUser) throws SVNException {
|
||||
List<LogEntry> entries = new ArrayList<>();
|
||||
|
||||
if (startRevision < 0) {
|
||||
startRevision = repository.getLatestRevision();
|
||||
}
|
||||
|
||||
if (endRevision < 0) {
|
||||
endRevision = repository.getLatestRevision();
|
||||
}
|
||||
|
||||
if (startRevision > endRevision) {
|
||||
long temp = startRevision;
|
||||
startRevision = endRevision;
|
||||
endRevision = temp;
|
||||
}
|
||||
|
||||
Collection<SVNLogEntry> logEntries = repository.log(new String[]{""}, null, startRevision, endRevision, true, true);
|
||||
|
||||
for (SVNLogEntry logEntry : logEntries) {
|
||||
String author = logEntry.getAuthor();
|
||||
|
||||
// 如果设置了用户名过滤器,则跳过不匹配的记录(包含匹配,不区分大小写)
|
||||
if (filterUser != null && !filterUser.isEmpty() && (author == null || !author.toLowerCase().contains(filterUser.toLowerCase()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LogEntry entry = new LogEntry();
|
||||
entry.setRevision(logEntry.getRevision());
|
||||
entry.setAuthor(author != null ? author : "(无作者)");
|
||||
entry.setDate(logEntry.getDate());
|
||||
entry.setMessage(logEntry.getMessage() != null ? logEntry.getMessage().trim() : "");
|
||||
|
||||
// 获取变更的文件路径
|
||||
if (logEntry.getChangedPaths() != null) {
|
||||
List<String> paths = new ArrayList<>();
|
||||
for (Map.Entry<String, SVNLogEntryPath> pathEntry : logEntry.getChangedPaths().entrySet()) {
|
||||
paths.add(pathEntry.getKey());
|
||||
}
|
||||
entry.setChangedPaths(paths.toArray(new String[0]));
|
||||
}
|
||||
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
// 按版本号降序排序
|
||||
entries.sort((e1, e2) -> Long.compare(e2.getRevision(), e1.getRevision()));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public long getLatestRevision() throws SVNException {
|
||||
return repository.getLatestRevision();
|
||||
}
|
||||
|
||||
public String formatDate(Date date) {
|
||||
return dateFormat.format(date);
|
||||
}
|
||||
|
||||
public void testConnection() throws SVNException {
|
||||
repository.testConnection();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
artifactId=svn-log-tool
|
||||
groupId=com.svnlog
|
||||
version=1.0.0
|
||||
@@ -1,9 +0,0 @@
|
||||
com/svnlog/DeepSeekLogProcessor.class
|
||||
com/svnlog/ExcelAnalyzer.class
|
||||
com/svnlog/DeepSeekLogProcessor$JSONArray.class
|
||||
com/svnlog/SVNLogFetcher.class
|
||||
com/svnlog/Main$Project.class
|
||||
com/svnlog/DeepSeekLogProcessor$JSONObject.class
|
||||
com/svnlog/Main.class
|
||||
com/svnlog/LogEntry.class
|
||||
com/svnlog/ExcelAnalyzer$1.class
|
||||
@@ -1,5 +0,0 @@
|
||||
/home/liumangmang/opencode/日志/svn-log-tool/src/main/java/com/svnlog/LogEntry.java
|
||||
/home/liumangmang/opencode/日志/svn-log-tool/src/main/java/com/svnlog/Main.java
|
||||
/home/liumangmang/opencode/日志/svn-log-tool/src/main/java/com/svnlog/ExcelAnalyzer.java
|
||||
/home/liumangmang/opencode/日志/svn-log-tool/src/main/java/com/svnlog/DeepSeekLogProcessor.java
|
||||
/home/liumangmang/opencode/日志/svn-log-tool/src/main/java/com/svnlog/SVNLogFetcher.java
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,106 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 测试多文件处理功能
|
||||
# 注意:此脚本需要有效的 DeepSeek API Key 才能完成测试
|
||||
|
||||
cd /home/liumangmang/opencode/日志
|
||||
|
||||
echo "==========================================="
|
||||
echo " 测试 DeepSeek 日志分析工具"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
|
||||
# 检查是否有日志文件
|
||||
md_files=$(find . -maxdepth 1 -name "svn_log_*.md" | wc -l)
|
||||
|
||||
if [ "$md_files" -eq 0 ]; then
|
||||
echo "警告: 当前目录没有找到 svn_log_*.md 文件"
|
||||
echo "请先使用 Main.java 生成日志文件"
|
||||
echo ""
|
||||
echo "或者手动创建测试文件..."
|
||||
# 创建测试文件
|
||||
cat > test_project1.md << 'EOF'
|
||||
# SVN 日志报告
|
||||
|
||||
## 查询条件
|
||||
- **SVN地址**: `https://test.svn.com/project1`
|
||||
- **账号**: `testuser`
|
||||
- **版本范围**: r1 - r10
|
||||
- **生成时间**: 2026-01-30
|
||||
|
||||
## 统计信息
|
||||
- **总记录数**: 2 条
|
||||
|
||||
## 日志详情
|
||||
|
||||
### r10
|
||||
**作者**: `liujing@SZNARI`
|
||||
**时间**: 2026-01-27 10:00:00
|
||||
**版本**: r10
|
||||
|
||||
**提交信息**:
|
||||
feat: 添加用户登录功能
|
||||
|
||||
### r9
|
||||
**作者**: `liujing@SZNARI`
|
||||
**时间**: 2026-01-26 15:00:00
|
||||
**版本**: r9
|
||||
|
||||
**提交信息**:
|
||||
fix: 修复登录页面样式问题
|
||||
EOF
|
||||
|
||||
cat > test_project2.md << 'EOF'
|
||||
# SVN 日志报告
|
||||
|
||||
## 查询条件
|
||||
- **SVN地址**: `https://test.svn.com/project2`
|
||||
- **账号**: `testuser`
|
||||
- **版本范围**: r1 - r10
|
||||
- **生成时间**: 2026-01-30
|
||||
|
||||
## 统计信息
|
||||
- **总记录数**: 1 条
|
||||
|
||||
## 日志详情
|
||||
|
||||
### r8
|
||||
**作者**: `liujing@SZNARI`
|
||||
**时间**: 2026-01-25 14:00:00
|
||||
**版本**: r8
|
||||
|
||||
**提交信息**:
|
||||
refactor: 优化数据库查询性能
|
||||
EOF
|
||||
|
||||
echo "已创建测试文件: test_project1.md, test_project2.md"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "当前目录下的日志文件:"
|
||||
ls -lh svn_log_*.md test_*.md 2>/dev/null || echo " (无文件)"
|
||||
echo ""
|
||||
|
||||
echo "==========================================="
|
||||
echo " 程序使用说明"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
echo "要运行 DeepSeek 日志分析工具,请执行:"
|
||||
echo ""
|
||||
echo " cd /home/liumangmang/opencode/日志"
|
||||
echo " java -jar svn-log-tool/target/svn-log-tool-1.0.0-jar-with-dependencies.jar"
|
||||
echo ""
|
||||
echo "然后按提示输入:"
|
||||
echo " 1. 日志文件所在目录路径(回车使用当前目录)"
|
||||
echo " 2. 工作周期(例如:2025年12月)"
|
||||
echo " 3. DeepSeek API Key"
|
||||
echo " 4. 输出 Excel 文件名(回车使用默认)"
|
||||
echo ""
|
||||
echo "程序将自动读取目录中的所有 .md 文件,合并后发送给 DeepSeek API 分析,"
|
||||
echo "并生成包含多项目工作内容的 Excel 文件。"
|
||||
echo ""
|
||||
echo "Excel 输出格式(与参考文件一致):"
|
||||
echo " - 7列:序号、所属班组、技术对接、开发人员、工作周期、开发项目名称、具体工作内容"
|
||||
echo " - 项目名称用 / 分隔(如:PRS7050/PRS7950)"
|
||||
echo " - 工作内容用 # 标识不同项目"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user