最近在搞自动化监控,有一项功能就是监测到异常情况时自动创建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;
}
}