Jira的集成有两种方式
第一种:直接使用现有的轮子,也就是封装好的Jira API客户端代码
第二种:通过Http调用Jira的Restful接口
两者相比第一种功能更丰富一些,但是要掌握Jira的API学习成本比较高,第二种上手更快一些,但是需要自己编程处理各种Response。
我的应用场景和需求:
需要在Jira上配置两个webhook:
第一个WebHook:当主任务的某个环节提交时自动创建子任务,而且子任务的部分属性需要从主任务中来获取。
第二个WebHook:当任务提交时按照我自己的邮件模板向jira中的指定人发送和抄送html格式的邮件。
拆分下具体的功能点:
1 与Jira的鉴权认证
2 根据条件查询Jira任务
3 创建Jira任务
4 html邮件模板
5 发送邮件
下面我们来一一分析下具体的代码实现
Jira鉴权认证:
对认证信息进行加密:
@Service
public class JiraAuthServiceImpl implements JiraAuthService{
@Override
public String getBasicAuth(String userName, String password) {
String basic_auth = "Basic ";
basic_auth += Base64.getEncoder().encodeToString((userName + ":" + password).getBytes());
return basic_auth;
}
}
在发送httl请求时将加密后的认证信息放在请求头:
HttpPost httpPost = new HttpPost(restUrl);
httpPost.setHeader("Authorization", authInfo);
查询Jira任务
根据自己Jira属性定制实例对象:
public class JiraEntity {
private String id;
private String key;
private String reporter;
private String summary;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getReporter() {
return reporter;
}
public void setReporter(String reporter) {
this.reporter = reporter;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
public class JiraReleaseEntity extends JiraEntity{
private String gitProject;
private String onlineGitTag;
private String rollbackGitTag;
private String detail;
private String[] notifyEmail;
private String operator;
private String tester;
public String getGitProject() {
return gitProject;
}
public void setGitProject(String gitProject) {
this.gitProject = gitProject;
}
public String getOnlineGitTag() {
return onlineGitTag;
}
public void setOnlineGitTag(String onlineGitTag) {
this.onlineGitTag = onlineGitTag;
}
public String getRollbackGitTag() {
return rollbackGitTag;
}
public void setRollbackGitTag(String rollbackGitTag) {
this.rollbackGitTag = rollbackGitTag;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String[] getNotifyEmail() {
return notifyEmail;
}
public void setNotifyEmail(String[] notifyEmail) {
this.notifyEmail = notifyEmail;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getTester() {
return tester;
}
public void setTester(String tester) {
this.tester = tester;
}
}
查询Jira并解析response消息体,封装为任务实例对象:
@Override
public JiraReleaseEntity findReleaseIssueById(String restUrl, String projectKey, String issueId, String authInfo) {
String postBody = "{\"jql\":\"project = "+ projectKey
+"&id="+issueId
+ "\",\"startAt\":0,\"maxResults\":1,\"expand\":[\"renderedFields\"],\"fields\":[\"id\",\"key\",\"reporter\",\"customfield_10151\",\"customfield_10208\", \"customfield_10223\", \"customfield_10245\", \"customfield_10241\", \"summary\", \"customfield_10147\", \"customfield_10148\" ]}";
ResponseHandler<String> handler = new BasicResponseHandler();
HttpPost httpPost = new HttpPost(restUrl);
httpPost.setHeader("Authorization", authInfo);
httpPost.setHeader("Content-type", "application/json");
StringEntity entity = new StringEntity(postBody, "UTF-8");
entity.setContentType("application/json;charset=UTF-8");
httpPost.setEntity(entity);
String responseBody = null;
HttpResponse response = null;
try {
CloseableHttpClient httpclient = HttpClients.createDefault();
response = httpclient.execute(httpPost);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
responseBody = handler.handleResponse(response);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
JiraReleaseEntity jiraEntity = null;
try
{
if (responseBody != null)
{
int entityNum = new JSONObject(responseBody).getInt("total");
if(entityNum > 0) {
String issuesString = new JSONObject(responseBody).getString("issues");
JSONArray jsonArray = JSONArray.fromObject(issuesString);
if (jsonArray != null && jsonArray.size() > 0) {
jiraEntity = new JiraReleaseEntity();
jiraEntity.setId(jsonArray.getJSONObject(0).getString("id"));
jiraEntity.setKey(jsonArray.getJSONObject(0).getString("key"));
jiraEntity.setSummary(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("summary"));
jiraEntity.setGitProject(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10208"));
jiraEntity.setOnlineGitTag(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10151"));
jiraEntity.setRollbackGitTag(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10223"));
jiraEntity.setReporter(new JSONObject(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("reporter")).getString("name"));
jiraEntity.setOperator(new JSONObject(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10147")).getString("name"));
jiraEntity.setTester(new JSONObject(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10148")).getString("name"));
jiraEntity.setDetail(new JSONObject(jsonArray.getJSONObject(0).getString("renderedFields")).getString("customfield_10241"));
String emailObject = new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10245");
if(emailObject!=null) {
JSONArray emailArray = JSONArray.fromObject(new JSONObject(jsonArray.getJSONObject(0).getString("fields")).getString("customfield_10245"));
if(emailArray!=null && emailArray.size()>0) {
String[] emails = new String[emailArray.size()];
for(int i=0; i<emailArray.size(); i++) {
emails[i]= emailArray.getJSONObject(i).getString("value");
}
jiraEntity.setNotifyEmail(emails);
}
}
}
}
}
} catch (JSONException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return jiraEntity;
}
重点:
1 jql就像hsql、sql意义一样,发送查询条件的,startAt、maxResults做分页,fields指定返回属性,expand讲邮件时再说。
2 jira中自定义的字段在代码中是以customfield_XXXX这种形式唯一标识的
3 在对返回值做Json解析时要特别留意时间、人员、下拉列表框等各种复杂类型,并不像文本类型那么简单
4 如果请求报错bad request 404,基本就是jql或fields有问题,可以现在jira页面上尝试操作一次。例如字段在界面不存在但是你代码中又使用到了,就会bad reqeust。
创建Jira任务
构造post请求的报文:
@Override
public String constructFindIssuePostBody(Map<String,String> map) {
String postBody = "{\"fields\": { \"project\": { \"key\": \"" + map.get("projectKey") + "\" }, \"parent\":{\"key\": \""+ map.get("parentIssueKey") +"\"},"
+ "\"reporter\": {\"name\": \""+ map.get("reporter") +"\"}, "
+ "\"summary\": \""+map.get("summary")+"\", "
+ "\"customfield_10151\": \""+ map.get("gitOnlineTag") +"\", "
+ "\"customfield_10208\": \""+ map.get("gitProject") +"\", "
+ "\"customfield_10223\": \""+ map.get("gitRollbackTag") +"\", "
+ "\"issuetype\": {\"id\": \""+ map.get("issuetype") +"\"}"
+ "}}";
return postBody;
}
将报文推送给Jira:
@Override
public String createIssue(String restUrl, String postBody, String authInfo) {
ResponseHandler<String> handler = new BasicResponseHandler();
HttpPost httpPost = new HttpPost(restUrl);
httpPost.setHeader("Authorization", authInfo);
httpPost.setHeader("Content-type", "application/json");
StringEntity entity = new StringEntity(postBody, "UTF-8");
entity.setContentType("application/json;charset=UTF-8");
httpPost.setEntity(entity);
String responseBody = null;
HttpResponse response = null;
try {
CloseableHttpClient httpclient = HttpClients.createDefault();
response = httpclient.execute(httpPost);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
responseBody = handler.handleResponse(response);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String ticketNum = null;
try
{
if (responseBody != null)
{
ticketNum = new JSONObject(responseBody).getString("key");
}
} catch (JSONException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Ticket Number: " + ticketNum);
return ticketNum;
}
这里同样需要注意bad reqeust和自定义字段唯一标识的问题。
Html邮件模板
Python常用的模板是Jinjia2,Jiava中我们使用VelocityEngine
private String mergeMessage(String velocity, Map<String, Object> params) {
VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
// 载入(获取)模板对象
Template t = ve.getTemplate(velocity);
VelocityContext ctx = new VelocityContext(params);
StringWriter sw = new StringWriter();
t.merge(ctx, sw);
return sw.toString();
}
velocity文件release_html.vm
<div id="container" style="width:500px">
<div id="header" style="background-color:#FFFFFF;text-align:center;">
<h3 style="margin-bottom:0;">${title}</h3></div>
<div id="content" style="background-color:#FFFFFF;float:center;">
${detail}
<p>
项目地址: ${git}
</p>
<p>
新版本: ${version}
</p>
<p>
jira链接:<a href="http://jira.imgo.tv/browse/${key}">${key}</a>
</p>
</div>
<div id="footer" style="background-color:#FFFFFF;clear:both;text-align:right;">
<i>该邮件由上线助手自动生成</i></div>
</div>
pom引用:
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
发送邮件
采用最通用的spring boot email
@Override
public void sendSimpleEmail(String eFrom, String[] eTo, String[] eCc, String title, String velocity, Map<String, Object> params) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(eFrom);
message.setTo(eTo);
if (!StringUtils.isEmpty(eCc)) {
message.setCc(eCc);
}
message.setSubject(title);
message.setText(mergeMessage(velocity, params));
mailSender.send(message);
}
@Override
public void sendHtmlEmail(String eFrom, String[] eTo, String[] eCc, String title, String velocity,
Map<String, Object> params) {
MimeMessage message=mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message,true);
helper.setFrom(eFrom);
helper.setTo(eTo);
helper.setSubject(title);
if (!StringUtils.isEmpty(eCc)) {
helper.setCc(eCc);
}
helper.setText(mergeMessage(velocity, params),true);
mailSender.send(message);
} catch (MessagingException e) {
logger.error(e.getMessage());
e.printStackTrace();
}
}
前面留了一个坑:查询jira的请求中有一个expend设置了一个renderedFields属性,这个是干什么用的呢?Jira的复杂文本框属性(例如描述、common)支持自带的渲染器,可以对文字颜色、大小、段落等进行编辑和渲染,但是它使用的是Jira内置的格式并不是标准的html,所以需要借助renderedFields对需要渲染的内容进行一次html的转换。如果你只发送text的邮件内容可以把这里的expend去掉。
pom引用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
关于Jira集成的核心是要掌握Restful接口,常用的查询相关是baseUrl+"/rest/api/2/search"这种格式开头,任务相关是baseUrl+"/rest/api/2/issue"开头。
本文完成代码下载地址:https://github.com/yejingtao/ci-jiratool