news 2026/2/11 2:58:55

(开源项目)xsun_workflow_jira

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(开源项目)xsun_workflow_jira

JIRA 工时自动填报 Agent

xsun_workflow_agent

项目简介

这是一个基于 AI 的 JIRA 工时自动填报系统,能够根据用户的 Git 提交记录自动分析并填写 JIRA 工作日志。该系统通过集成 LangChain4j 框架,利用大语言模型的能力,智能地将 Git 提交内容与 JIRA 问题进行匹配,并自动生成符合要求的工作日志。

功能特性

  • 自动获取用户当天的 Git 提交记录
  • 查询用户在 JIRA 上未完成的任务
  • 智能匹配 Git 提交与 JIRA 问题
  • 自动计算所需填写的工时
  • 自动生成工作日志并提交至 JIRA 系统

技术架构

本项目采用以下技术栈:

  • Java 21
  • Spring Boot 3.2.3
  • LangChain4j 0.27.1
  • JIRA REST Java Client 7.0.1
  • Eclipse JGit 6.8.0

项目结构

src/main/java/com/xsun/jira/jira_workflor_agent/ ├── JiraWorkflorAgentApplication.java // 应用入口类 ├── config/ │ ├── AgentConfig.java // AI Agent 配置类 │ └── GitConfig.java // Git 配置类 ├── controller/ │ └── WorklogController.java // 工作日志控制器 ├── model/dto/ │ ├── GitCommit.java // Git 提交数据传输对象 │ ├── JiraIssue.java // JIRA 问题数据传输对象 │ └── WorklogEntry.java // 工作日志条目数据传输对象 ├── service/ │ ├── WorklogAgent.java // 工作日志 AI Agent 接口 │ └── WorklogService.java // 工作日志服务类 └── tools/ ├── DateTimeTools.java // 日期时间工具类 ├── GitTools.java // Git 操作工具类 └── JiraTools.java // JIRA 操作工具类

核心代码展示

主应用类

类名:JiraWorkflorAgentApplication.java

packagecom.xsun.jira.jira_workflor_agent;importcom.xsun.jira.jira_workflor_agent.service.WorklogService;importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.boot.CommandLineRunner;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.scheduling.annotation.EnableScheduling;importorg.springframework.scheduling.annotation.Scheduled;@Slf4j@SpringBootApplication@EnableScheduling@RequiredArgsConstructorpublicclassJiraWorkflorAgentApplicationimplementsCommandLineRunner{publicstaticvoidmain(String[]args){SpringApplication.run(JiraWorkflorAgentApplication.class,args);}@Overridepublicvoidrun(String...args)throwsException{log.info("工作日志Agent启动成功");}}

AI Agent 配置

类名:AgentConfig.java

packagecom.xsun.jira.jira_workflor_agent.config;importcom.xsun.jira.jira_workflor_agent.service.WorklogAgent;importcom.xsun.jira.jira_workflor_agent.tools.DateTimeTools;importcom.xsun.jira.jira_workflor_agent.tools.GitTools;importcom.xsun.jira.jira_workflor_agent.tools.JiraTools;importdev.langchain4j.memory.chat.MessageWindowChatMemory;importdev.langchain4j.model.chat.ChatLanguageModel;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.service.AiServices;importlombok.RequiredArgsConstructor;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.time.Duration;/** * @program: AgentConfig.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Configuration@RequiredArgsConstructorpublicclassAgentConfig{@Value("${openai.api-key}")privateStringopenaiApiKey;@Value("${openai.model}")privateStringopenaiModel;@Value("${openai.base_url}")privateStringbaseUrl;privatefinalJiraToolsjiraTools;privatefinalGitToolsgitTools;privatefinalDateTimeToolsdateTimeTools;@BeanpublicChatLanguageModelchatLanguageModel(){returnOpenAiChatModel.builder().apiKey(openaiApiKey).modelName(openaiModel).baseUrl(baseUrl).temperature(0.5).timeout(Duration.ofSeconds(60)).build();}@BeanpublicWorklogAgentworklogAgent(ChatLanguageModelchatLanguageModel){returnAiServices.builder(WorklogAgent.class).chatLanguageModel(chatLanguageModel).chatMemory(MessageWindowChatMemory.withMaxMessages(10)).tools(jiraTools,gitTools,dateTimeTools).build();}}

工作日志 AI Agent 接口

类名:WorklogAgent.java

packagecom.xsun.jira.jira_workflor_agent.service;importdev.langchain4j.service.SystemMessage;importdev.langchain4j.service.UserMessage;publicinterfaceWorklogAgent{@SystemMessage(""" 你是一个工作日志助手。你的任务是帮助用户自动记录JIRA工作时间。 工作流程: 1. 获取当前日期,判断今天是星期几 2. 根据星期判断需要工作的时间(周一/三/五:8小时,周二/四:10小时) 3. 获取用户今天已经记录的JIRA工作时间 4. 如果工作时间不足,需要: - 获取用户今天的Git提交记录 - 获取用户未完成的JIRA问题列表 - 使用这些信息匹配并补充工作时间(git提交记录最匹配的jira问题,使用余弦相似度) 5. 确认后记录工作时间 注意: - 时间以0.5小时为最小单位 - 最终的总工作时间必须等于要求的时间 - 工作说明要基于Git提交的comment总结 - 工作说明不需要明写是根据git总结的,只需要返回具体的总结信息即可 - 如果不需要记录,只需要返回已经填满,不缺时间 - 选择一个你觉得最好的方案进行填写jira,不必找我二次确认,直接填写 """)Stringchat(@UserMessageStringmessage);}

Git 操作工具

类名:GitTools.java

packagecom.xsun.jira.jira_workflor_agent.tools;importcom.xsun.jira.jira_workflor_agent.config.GitConfig;importcom.xsun.jira.jira_workflor_agent.model.dto.GitCommit;importdev.langchain4j.agent.tool.Tool;importlombok.extern.slf4j.Slf4j;importorg.eclipse.jgit.api.Git;importorg.eclipse.jgit.lib.PersonIdent;importorg.eclipse.jgit.revwalk.RevCommit;importorg.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.io.File;importjava.time.LocalDate;importjava.time.LocalDateTime;importjava.time.ZoneId;importjava.util.ArrayList;importjava.util.List;/** * @program: GitTools.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Slf4j@ComponentpublicclassGitTools{@Value("${git.username}")privateStringgitUsername;@AutowiredprivateGitConfiggitConfig;@Tool("从Git仓库获取指定用户今天的提交记录")publicList<GitCommit>getTodayCommitsByUser(){log.info("正在获取用户 {} 今天的Git提交记录...",gitUsername);List<GitCommit>commits=newArrayList<>();LocalDatetoday=LocalDate.now();for(GitConfig.Repositoryrepo:gitConfig.getRepositories()){try{FiletempDir=File.createTempFile("git-","-repo");tempDir.delete();tempDir.mkdirs();log.info("克隆仓库: {}",repo.getUrl());Gitgit=Git.cloneRepository().setURI(repo.getUrl()).setDirectory(tempDir).setCredentialsProvider(newUsernamePasswordCredentialsProvider(gitUsername,repo.getToken())).setBranch(repo.getBranch()).call();Iterable<RevCommit>logs=git.log().call();for(RevCommitcommit:logs){PersonIdentauthor=commit.getAuthorIdent();LocalDateTimecommitTime=LocalDateTime.ofInstant(author.getWhen().toInstant(),ZoneId.systemDefault());// 只获取今天的提交if(commitTime.toLocalDate().equals(today)&&author.getName().equals(gitUsername)){GitCommitgitCommit=newGitCommit();gitCommit.setCommitId(commit.getName());gitCommit.setAuthor(author.getName());gitCommit.setMessage(commit.getFullMessage());gitCommit.setCommitTime(commitTime);gitCommit.setRepository(repo.getUrl());commits.add(gitCommit);}}git.close();deleteDirectory(tempDir);}catch(Exceptione){log.error("获取仓库 {} 的提交记录失败",repo.getUrl(),e);}}log.info("共找到 {} 条今天的提交记录",commits.size());returncommits;}privatevoiddeleteDirectory(Filedirectory){File[]files=directory.listFiles();if(files!=null){for(Filefile:files){if(file.isDirectory()){deleteDirectory(file);}else{file.delete();}}}directory.delete();}}

JIRA 操作工具

类名:JiraTools.java

packagecom.xsun.jira.jira_workflor_agent.tools;importai.djl.util.JsonUtils;importcom.atlassian.jira.rest.client.api.JiraRestClient;importcom.atlassian.jira.rest.client.api.JiraRestClientFactory;importcom.atlassian.jira.rest.client.api.domain.SearchResult;importcom.atlassian.jira.rest.client.api.domain.input.WorklogInput;importcom.atlassian.jira.rest.client.api.domain.input.WorklogInputBuilder;importcom.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;importcom.google.gson.JsonNull;importcom.xsun.jira.jira_workflor_agent.model.dto.JiraIssue;importcom.xsun.jira.jira_workflor_agent.model.dto.WorklogEntry;importdev.langchain4j.agent.tool.Tool;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.net.URI;importjava.time.LocalDate;importjava.time.LocalDateTime;importjava.time.ZoneId;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.StreamSupport;/** * @program: JiraTools.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Slf4j@ComponentpublicclassJiraTools{@Value("${jira.url}")privateStringjiraUrl;@Value("${jira.username}")privateStringusername;@Value("${jira.api-token}")privateStringapiToken;privateJiraRestClientjiraClient;privateJiraRestClientgetClient(){if(jiraClient==null){JiraRestClientFactoryfactory=newAsynchronousJiraRestClientFactory();URIjiraServerUri=URI.create(jiraUrl);jiraClient=factory.createWithBasicHttpAuthentication(jiraServerUri,username,apiToken);}returnjiraClient;}@Tool("从JIRA获取我未完成的问题列表")publicList<JiraIssue>getMyUnfinishedIssues(){log.info("正在获取未完成的JIRA问题...");List<JiraIssue>issues=newArrayList<>();try{Stringjql=String.format("assignee = currentUser() AND resolution = Unresolved ");varsearchResult=getClient().getSearchClient().searchJql(jql).claim();searchResult.getIssues().forEach(issue->{JiraIssuejiraIssue=newJiraIssue();jiraIssue.setKey(issue.getKey());jiraIssue.setSummary(issue.getSummary());jiraIssue.setDescription(issue.getDescription()!=null?issue.getDescription():"");jiraIssue.setStatus(issue.getStatus().getName());jiraIssue.setCreated(LocalDateTime.ofInstant(issue.getCreationDate().toDate().toInstant(),ZoneId.systemDefault()));jiraIssue.setUpdated(LocalDateTime.ofInstant(issue.getUpdateDate().toDate().toInstant(),ZoneId.systemDefault()));issues.add(jiraIssue);});log.info("找到 {} 个未完成的问题",issues.size());}catch(Exceptione){log.error("获取JIRA问题失败",e);}returnissues;}@Tool("从JIRA获取我今天的工作记录")publicList<WorklogEntry>getMyTodayWorklogs(){log.info("正在获取今天的工作记录...");List<WorklogEntry>worklogs=newArrayList<>();try{LocalDatetoday=LocalDate.now();Stringjql=String.format("worklogAuthor = currentUser() AND worklogDate = '%s'",today.toString());SearchResultsearchResult=getClient().getSearchClient().searchJql(jql).claim();log.info("找到 {} 个问题",searchResult.getTotal());searchResult.getIssues().forEach(issue->{varissueWorklogs=getClient().getIssueClient().getIssue(issue.getKey()).claim().getWorklogs();StreamSupport.stream(issueWorklogs.spliterator(),false).filter(wl->{LocalDateworklogDate=LocalDateTime.ofInstant(wl.getUpdateDate().toDate().toInstant(),ZoneId.systemDefault()).toLocalDate();returnworklogDate.equals(today);}).forEach(wl->{WorklogEntryentry=newWorklogEntry();entry.setIssueKey(issue.getKey());entry.setTimeSpentHours(wl.getMinutesSpent()/60.0);entry.setComment(wl.getComment());entry.setStarted(LocalDateTime.ofInstant(wl.getStartDate().toDate().toInstant(),ZoneId.systemDefault()));worklogs.add(entry);});});log.info("找到 {} 条今天的工作记录",worklogs.size());}catch(Exceptione){log.error("获取工作记录失败",e);}returnworklogs;}@Tool("记录JIRA工作时间,参数:issueKey-问题编号, timeSpentHours-工作时长(小时), comment-工作说明")publicbooleanlogWork(StringissueKey,doubletimeSpentHours,Stringcomment){log.info("正在记录工作时间: {} - {}小时 - {}",issueKey,timeSpentHours,comment);try{intminutes=(int)(timeSpentHours*60);WorklogInputBuilderworklogInputBuilder=newWorklogInputBuilder(URI.create(jiraUrl+"/rest/api/2/issue/"+issueKey+"/worklog"));WorklogInputbuild=worklogInputBuilder.setMinutesSpent(minutes).setComment(comment).build();getClient().getIssueClient().addWorklog(URI.create(jiraUrl+"/rest/api/2/issue/"+issueKey+"/worklog"),build).claim();log.info("工作时间记录成功");returntrue;}catch(Exceptione){log.error("记录工作时间失败",e);returnfalse;}}}

数据传输对象

类名:GitCommit.java

packagecom.xsun.jira.jira_workflor_agent.model.dto;importlombok.Getter;importlombok.Setter;importlombok.experimental.Accessors;importjava.time.LocalDateTime;/** * @program: GitCommit.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Getter@Setter@Accessors(chain=true)publicclassGitCommit{privateStringcommitId;privateStringauthor;privateStringmessage;privateLocalDateTimecommitTime;privateStringrepository;privateintfilesChanged;privateintadditions;privateintdeletions;}

类名:JiraIssue.java

packagecom.xsun.jira.jira_workflor_agent.model.dto;importlombok.Getter;importlombok.Setter;importlombok.experimental.Accessors;importjava.time.LocalDateTime;/** * @program: JiraIssue.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Getter@Setter@Accessors(chain=true)publicclassJiraIssue{privateStringkey;privateStringsummary;privateStringdescription;privateStringstatus;privateLocalDateTimecreated;privateLocalDateTimeupdated;}

类名:WorklogEntry.java

packagecom.xsun.jira.jira_workflor_agent.model.dto;importlombok.Getter;importlombok.Setter;importlombok.experimental.Accessors;importjava.time.LocalDateTime;/** * @program: WorklogEntry.java * @description: * @author: sunmouren * @create: 2025-12-14 **/@Getter@Setter@Accessors(chain=true)publicclassWorklogEntry{privateStringissueKey;privatedoubletimeSpentHours;privateStringcomment;privateLocalDateTimestarted;/** * 相似度分数 */privatedoublesimilarityScore;}

配置文件

配置文件路径:application.yml

jira:url:http://jira***# 用户名username:xxx# 密码api-token:xxxgit:repositories:# 仓库地址-url:https://gitlab.xsun.com/***# 密码token:xxx# 统计分支branch:xxx# 账号username:xxx# 相关ai的key等openai:api-key:sk-YOUR-API-KEYmodel:qwen-plusbase_url:https://dashscope.aliyuncs.com/compatible-mode/v1# 每天需要填写时长work-hours:monday:8.0tuesday:10.0wednesday:8.0thursday:10.0friday:8.0minimum-unit:0.5# 最小时间单位(小时)

使用方法

  1. 配置 JIRA 和 Git 相关信息到 application.yml 文件中
  2. 启动应用程序
  3. 访问/api/worklog/chat?message=请帮我填写今日工时接口触发工时填报流程

工作原理

  1. 当用户请求填写工时时,系统首先判断今天是星期几,确定应填写的工时数(周一/三/五为8小时,周二/四为10小时)
  2. 检查用户今天已填写的工时总数
  3. 如果还需要补填工时,则:
    • 获取用户今天的所有 Git 提交记录
    • 获取用户所有的未完成 JIRA 任务
    • 利用 AI 模型对 Git 提交内容和 JIRA 任务描述进行语义匹配
    • 自动分配剩余工时到最相关的 JIRA 任务上
    • 自动生成合适的工作说明并提交到 JIRA

依赖管理

类名:pom.xml(部分)

<!-- LangChain4j Core --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version></dependency><!-- LangChain4j OpenAI --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>${langchain4j.version}</version></dependency><!-- JIRA REST Client --><dependency><groupId>com.atlassian.jira</groupId><artifactId>jira-rest-java-client-core</artifactId><version>7.0.1</version></dependency><!-- JGit for Git operations --><dependency><groupId>org.eclipse.jgit</groupId><artifactId>org.eclipse.jgit</artifactId><version>6.8.0.202311291450-r</version></dependency>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/8 17:04:12

【农业产量数据分析实战】:手把手教你用R语言完成方差分析全流程

第一章&#xff1a;农业产量数据分析与方差分析概述在现代农业科学中&#xff0c;准确评估不同种植条件对作物产量的影响至关重要。通过对多组实验数据进行系统分析&#xff0c;研究人员能够识别出显著影响产量的关键因素&#xff0c;如施肥方案、灌溉频率或种子品种。方差分析…

作者头像 李华
网站建设 2026/2/7 9:15:26

智能运维(AIOps)平台综合评测与选型指南(2025)

在数字化转型与信创替代双重浪潮下&#xff0c;企业IT架构日益复杂&#xff0c;传统监控工具已难以应对海量数据与动态业务需求。智能运维&#xff08;AIOps&#xff09;平台可以实现从“被动响应”到“主动预测”的运维模式变革&#xff0c;成为企业提升运维效率、保障业务稳定…

作者头像 李华
网站建设 2026/2/8 11:59:40

美国银行可以“炒币”了?加密货币公司“持证”开启金融新玩法!

在加密货币世界里&#xff0c;监管的风向永远是牵动市场神经的最关键因素。就在2025年年末&#xff0c;美国金融监管领域接连投下两枚重磅炸弹&#xff0c;不仅为加密产业长期面临的“去银行化”&#xff08;Debanking&#xff09;困境画上了一个转折号&#xff0c;更为传统银行…

作者头像 李华
网站建设 2026/2/6 18:00:10

comsol声波阵面调控 涉及压力声学、固体力学模块 3258-3824hz扫频 comsol6

comsol声波阵面调控 涉及压力声学、固体力学模块 3258-3824hz扫频 comsol6.1版本在COMSOL里玩声波阵面调控就像搭乐高——参数调对了就能让声波乖乖听话。这次咱们用6.1版本折腾3258-3824Hz频段的声场操控&#xff0c;主要涉及压力声学模块和它的老搭档固体力学模块。先看模型搭…

作者头像 李华
网站建设 2026/2/3 16:00:44

基于VDLL的矢量型GPS信号跟踪算法MATLAB仿真,包括程序+word设计文档

基于VDLL的矢量型GPS信号跟踪算法MATLAB仿真&#xff0c;包括程序word设计文档GPS接收机最怕啥&#xff1f;不是信号弱&#xff0c;是动态场景下跟踪不稳。传统DLL&#xff08;延迟锁定环&#xff09;在车载导航这种剧烈运动场景里&#xff0c;环路参数调到头秃也容易跟丢。今天…

作者头像 李华
网站建设 2026/2/4 6:03:25

【同济】C++汉诺塔(90-b1)[2025-12-08]

【同济】C汉诺塔(90-b1)[2025-12-08] .综合题 – I 【注意:】 1&#xff64; 白名单同第5 章-Part3 2&#xff64; 本次作业不允许使用尚未讲授过的任何后续课程的知识点,包括但不限于指针&#xff64;引用&#xff64;结构体&#xff64; 类等概念!!! 3&#xff64; 已学过的…

作者头像 李华