最近在搞自动化监控,有一项功能就是监测到异常情况时自动创建jira工单,于是乎对JIRA REST API 做了一些调研,其提供两种使用方式,一种是在项目中引入客户端封装包jira-rest-java-client-2.0.0-m2.jar,另一种是直接使用JIRA REST API 提供的curl命令行方式处理。
参考资料:https://docs.atlassian.com/jira/REST/7.0-SNAPSHOT
一。第一种方式jira-rest-java-client-2.0.0-m2.jar:
<repositories>
<repository>
<id>central</id>
<name>Atlassian Public Repository</name>
<layout>default</layout>
<url>https://maven.atlassian.com/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-rest-java-client</artifactId> <version>2.0.0-m2</version> </dependency>
这种方式参考资料不好找,我基本是通过查看api一点点摸索的,而且需要引入许多第三方依赖包,相对第二种方式过于繁琐,这里只做了常用的方法封装,相关jar包见附件:
package com.nq.common; import java.net.URI; import java.net.URISyntaxException; import com.atlassian.jira.rest.client.JiraRestClient; import com.atlassian.jira.rest.client.JiraRestClientFactory; import com.atlassian.jira.rest.client.domain.BasicIssue; import com.atlassian.jira.rest.client.domain.Issue; import com.atlassian.jira.rest.client.domain.IssueType; import com.atlassian.jira.rest.client.domain.SearchResult; import com.atlassian.jira.rest.client.domain.Transition; import com.atlassian.jira.rest.client.domain.User; import com.atlassian.jira.rest.client.domain.input.IssueInput; import com.atlassian.jira.rest.client.domain.input.IssueInputBuilder; import com.atlassian.jira.rest.client.domain.input.TransitionInput; import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; /** * jira java工具类 * jira-rest-java-client-2.0.0-m2.jar * @author hanqunfeng * */ public class JiraUtil { public static void main(String[] args) { // TODO Auto-generated method stub try { // JiraUtil.getIssue("NQCP-35"); // JiraUtil.createIssue("NQCP", 1l, "工单详细信息<br>工单详细信息", // "工单主题", "username"); // JiraUtil.printAllIssueType(); // JiraUtil.changeIssueStatus("NQCP-35", 2); // JiraUtil.getUser("username"); //查询用户负责的所有工单 JiraUtil.searchIssues("assignee=username"); System.out.println("*****************************"); JiraUtil.searchIssues("assignee=username order by duedate"); System.out.println("*****************************"); JiraUtil.searchIssues("assignee=username order by issueType"); System.out.println("*****************************"); //从0开始数,从第5条开始,取两条 // JiraUtil.searchIssues("assignee=username",5,2); // // System.out.println("*****************************"); // JiraUtil.searchIssues("project=NQCP"); } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } } static JiraRestClientFactory factory = new AsynchronousJiraRestClientFactory(); static String uri = "http://jira.local.com/jira"; static String user = "username"; static String pwd = "password"; static JiraRestClient restClient; /** * 获得jira的客户端 * * @return JiraRestClient * @throws URISyntaxException */ static JiraRestClient getJiraRestClient() throws URISyntaxException { if (restClient == null) { URI jiraServerUri = new URI(uri); restClient = factory.createWithBasicHttpAuthentication( jiraServerUri, user, pwd); } return restClient; } /** * 获得工单信息 * * @param issueKey * 工单key,比如:NQCP-5 * @throws URISyntaxException */ public static Issue getIssue(String issueKey) throws URISyntaxException { Issue issue = null; JiraRestClient restClient = getJiraRestClient(); // get issue through issueKey try { issue = restClient.getIssueClient().getIssue(issueKey).claim(); // 打印工单后续的工作流 Iterable<Transition> iter = restClient.getIssueClient() .getTransitions(issue).claim(); for (Transition transition : iter) { System.out.println(transition); } } catch (Exception e) { e.printStackTrace(); } // 打印工单明细 System.out.println(issue); return issue; } /** * 检索工单 * @param jql * * @return * @throws URISyntaxException */ public static Iterable<BasicIssue> searchIssues(String jql) throws URISyntaxException{ JiraRestClient restClient = getJiraRestClient(); SearchResult searchResutl = restClient.getSearchClient().searchJql(jql).claim(); Iterable<BasicIssue> iter = searchResutl.getIssues(); for (BasicIssue baseIssue : iter) { System.out.println(baseIssue); } return iter; } /** * 检索工单 * @param jql * @param startIndex * @param maxResults * @return * @throws URISyntaxException */ public static Iterable<BasicIssue> searchIssues(String jql,int startIndex, int maxResults) throws URISyntaxException{ JiraRestClient restClient = getJiraRestClient(); SearchResult searchResutl = restClient.getSearchClient().searchJql(jql,maxResults,startIndex).claim(); Iterable<BasicIssue> iter = searchResutl.getIssues(); for (BasicIssue baseIssue : iter) { System.out.println(baseIssue); } return iter; } /** * 打印jira系统中已经创建的全部issueType * issuetype/ * * @throws URISyntaxException */ public static Iterable<IssueType> printAllIssueType() throws URISyntaxException { JiraRestClient restClient = getJiraRestClient(); Iterable<IssueType> iter = restClient.getMetadataClient() .getIssueTypes().claim(); for (IssueType issueType : iter) { System.out.println(issueType); } return iter; } /** * 创建一个新工单 * * @param projectKey * 项目key,比如:NQCP * @param issueType * 工单类型,来源于printAllIssueType()的id * @param description * 工单描述 * @param summary * 工单主题 * @param assignee * 工单负责人 * @throws URISyntaxException */ public static BasicIssue createIssue(String projectKey, Long issueType, String description, String summary, String assignee) throws URISyntaxException { JiraRestClient restClient = getJiraRestClient(); IssueInputBuilder issueBuilder = new IssueInputBuilder(projectKey, issueType); issueBuilder.setDescription(description); issueBuilder.setSummary(summary); if (getUser(assignee) != null) { issueBuilder.setAssigneeName(assignee); } IssueInput issueInput = issueBuilder.build(); BasicIssue bIssue = null; try { bIssue = restClient.getIssueClient().createIssue(issueInput) .claim(); } catch (Exception e) { e.printStackTrace(); } System.out.println(bIssue); return bIssue; } /** * 获取用户信息 * * @param username * @return * @throws URISyntaxException */ public static User getUser(String username) throws URISyntaxException { JiraRestClient restClient = getJiraRestClient(); User user = null; try { user = restClient.getUserClient().getUser(username).claim(); } catch (Exception e) { e.printStackTrace(); } System.out.println(user); return user; } /** * 改变工单workflow状态 issue的workflow是不可以随便改变的,必须按照流程图的顺序进行改变,具体如下: * * 当前状态 :说明 变更流程id:说明 >> 变更后状态 1:open,开放 1)4:start progress >> in progerss 2)5:resolve issue >> resolved 3)2:close issue >> closed 3:in progerss 正在处理 1)301:stop progress >> open 2)5:resolve issue >> resolved 3)2:close issue >> closed 4:resolved 已解决 1)701:close issue >> closed 2)3:reopen issue >> reopened 5:reopened 重新打开 1)4:start progress >> in progerss 2)5:resolve issue >> resolved 3)2:close issue >> closed 6:closed 已关闭 1)3:reopen issue >> reopened * * * 可通过如下方式查看当前工单的后续工作流程: Iterable<Transition> iter = * restClient.getIssueClient().getTransitions(issue).claim(); * * for (Transition transition : iter) { System.out.println(transition); } * * 输出结果:当前工单状态是 5:reopened 重新打开 Transition{id=4, name=Start Progress, * fields=[]} Transition{id=5, name=Resolve Issue, * fields=[Field{id=fixVersions, isRequired=false, type=array}, * Field{id=resolution, isRequired=true, type=resolution}]} Transition{id=2, * name=Close Issue, fields=[Field{id=fixVersions, isRequired=false, * type=array}, Field{id=resolution, isRequired=true, type=resolution}]} * * * @param issuekey * 工单key * @param statusId * 变更流程id * @param fields * 随状态需要传递的参数,可以为空 * @throws URISyntaxException */ public static void changeIssueStatus(String issuekey, int statusId) throws URISyntaxException { JiraRestClient restClient = getJiraRestClient(); Issue issue = getIssue(issuekey); if (issue != null) { TransitionInput tinput = new TransitionInput(statusId); restClient.getIssueClient().transition(issue, tinput); } // try { // Thread.sleep(3000);//因为是异步处理,所以状态是延迟变更的,暂停一下可以看到状态已经变更了 // issue = getIssue(issuekey); // System.out.println(issue.getStatus()); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } }
二。JIRA REST API
这种方式使用curl命令进行处理,几乎涵盖了所有的jira处理,这里也只对常用的功能进行的封装。
接口中的 2 可以使用 latest 替代。http://localhost:9080/rest/api/latest/issue/ORCM-1 《==》 http://localhost:9080/rest/api/2/issue/ORCM-1
package com.nq.common; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * JIRA REST API 工具类 * https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials * https://docs.atlassian.com/jira/REST/7.0-SNAPSHOT/ * @author hanqunfeng * */ public class JiraAPIUtil { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // JiraAPIUtil.getIssue("NQCP-35"); // Map<String, String> map = new HashMap<String, String>(); // map.put("assignee", "{\"name\":\"username\"}"); // JiraAPIUtil.createIssue("NQCP", "Bug", "哈哈哈啊哈哈", "测试01", map); // Map<String,String> map = new HashMap<String,String>(); // map.put("assignee", "{\"name\":\"username\"}"); // map.put("summary", "\"summary00002\""); // JiraAPIUtil.editIssue("NQCP-38", map); // JiraAPIUtil.searchIssues("assignee=username"); // System.out.println("*****************************"); // JiraAPIUtil.searchIssues("assignee=username+order+by+duedate"); // System.out.println("*****************************"); // JiraAPIUtil.addComments("NQCP-39", "123456哈哈哈哈"); // JiraAPIUtil.deleteIssueByKey("NQCP-38"); JiraAPIUtil.addAttachment("NQCP-39", "d://myfile01.txt"); //linux路径: /home/boss/myfile.txt } static String uri = "http://jira.local.com/jira"; static String user = "username"; static String pwd = "password"; static String osname = System.getProperty("os.name").toLowerCase(); /** * 执行shell脚本 * * @param command * @return * @throws IOException */ private static String executeShell(String command) throws IOException { StringBuffer result = new StringBuffer(); Process process = null; InputStream is = null; BufferedReader br = null; String line = null; try { if (osname.indexOf("windows") >= 0) { process = new ProcessBuilder("cmd.exe", "/c", command).start(); System.out.println("cmd.exe /c " + command); //安装Cygwin,使windows可以执行linux命令 } else { process = new ProcessBuilder("/bin/sh", "-c", command).start(); System.out.println("/bin/sh -c " + command); } is = process.getInputStream(); br = new BufferedReader(new InputStreamReader(is, "UTF-8")); while ((line = br.readLine()) != null) { System.out.println(line); result.append(line); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { br.close(); process.destroy(); is.close(); } return result.toString(); } /** * 活动工单信息 * * @param issueKey * 工单key * @return * @throws IOException */ public static String getIssue(String issueKey) throws IOException { String command = "curl -D- -u " + user + ":" + pwd + " -X GET -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/issue/" + issueKey + "\""; String issueSt = executeShell(command); return issueSt; } /** * 创建工单 * * @param projectKey * 项目key * @param issueType * 工单类型 name * @param description * 工单描述 * @param summary * 工单主题 * @param assignee * 工单负责人 * @param map * 工单参数map,key为参数名称,value为参数值,参数值必须自带双引号 比如: map.put("assignee", * "{\"name\":\"username\"}"); map.put("summary", * "\"summary00002\""); * @return * @throws IOException */ public static String createIssue(String projectKey, String issueType, String description, String summary, Map<String, String> map) throws IOException { String fields = ""; if (map != null && map.size() > 0) { StringBuffer fieldsB = new StringBuffer(); for (Map.Entry<String, String> entry : map.entrySet()) { fieldsB.append(",\"").append(entry.getKey()).append("\":") .append(entry.getValue()); } fields = fieldsB.toString(); } String command = "curl -D- -u " + user + ":" + pwd + " -X POST --data '{\"fields\": {\"project\":{ \"key\": \"" + projectKey + "\"},\"summary\": \"" + summary + "\",\"description\": \"" + description + "\",\"issuetype\": {\"name\": \"" + issueType + "\"}" + fields + "}}' -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/issue/\""; String issueSt = executeShell(command); return issueSt; } /** * 更新工单 * * @param issueKey * 工单key * @param map * 工单参数map,key为参数名称,value为参数值,参数值必须自带双引号 比如: map.put("assignee", * "{\"name\":\"username\"}"); map.put("summary", * "\"summary00002\""); * @return * @throws IOException */ public static String editIssue(String issueKey, Map<String, String> map) throws IOException { StringBuffer fieldsB = new StringBuffer(); for (Map.Entry<String, String> entry : map.entrySet()) { fieldsB.append("\"").append(entry.getKey()).append("\":") .append(entry.getValue()).append(","); } String fields = fieldsB.toString(); fields = fields.substring(0, fields.length() - 1); String command = "curl -D- -u " + user + ":" + pwd + " -X PUT --data '{\"fields\": { " + fields + "}}' -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/issue/" + issueKey + "\""; String issueSt = executeShell(command); return issueSt; } /** * 查询工单 * @param jql * assignee=username * assignee=username&startAt=2&maxResults=2 * assignee=username+order+by+duedate * project=projectKey+order+by+duedate&fields=id,key * @return * @throws IOException */ public static String searchIssues(String jql) throws IOException{ String command = "curl -D- -u " + user + ":" + pwd + " -X GET -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/search?jql=" + jql + "\""; String issueSt = executeShell(command); return issueSt; } /** * 为工单增加注释说明 * @param issueKey 工单key * @param comment 注释说明 * @return * @throws IOException */ public static String addComments(String issueKey,String comments) throws IOException{ String command = "curl -D- -u " + user + ":" + pwd + " -X PUT --data '{\"update\": { \"comment\": [ { \"add\": { \"body\":\""+comments+"\" } } ] }}' -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/issue/" + issueKey + "\""; String issueSt = executeShell(command); return issueSt; } /** * 删除工单 * @param issueKey 工单key * @return * @throws IOException */ public static String deleteIssueByKey(String issueKey) throws IOException{ String command = "curl -D- -u " + user + ":" + pwd + " -X DELETE -H \"Content-Type: application/json\" \"" + uri + "/rest/api/2/issue/" + issueKey + "\""; String issueSt = executeShell(command); return issueSt; } /** * 上传附件 * @param issueKey 工单key * @param filepath 文件路径 * @return * @throws IOException */ public static String addAttachment(String issueKey,String filepath) throws IOException{ String command = "curl -D- -u " + user + ":" + pwd + " -X POST -H \"X-Atlassian-Token: nocheck\" -F \"file=@"+filepath+"\" \"" + uri + "/rest/api/2/issue/" + issueKey + "/attachments\""; String issueSt = executeShell(command); return issueSt; } }