背景:系统需要调用禅道的接口进行工单的创建,并对工单进行附件上传等信息的操作。禅道接口为http接口,每次请求都需要带上zentaosid进行请求。
由于专业版禅道升级为旗舰版禅道版本变更,请求的URL有所变化,变化最大为常量类配置,如下(实体类在文章末尾):
1.配置常量类
/**
* @ClassName ZenTaoConstants
* @Description 禅道接口调用地址常量
* @Author hyh
* @Date 2022/6/29 8:56
* @Version 1.0
*/
public class ZenTaoConstants {
/**
* http get请求
*/
public static final String HTTP_GET_METHOD = "GET";
/**
* http post请求
*/
public static final String HTTP_POST_METHOD = "POST";
/**
* 禅道服务网地址
*/
public static final String ZENTAO_DOMAIN_URL = "https://task.zzvcom.com";
/**
* 获取禅道session
*/
public static final String ZENTAO_GET_SESSION_URL = ZENTAO_DOMAIN_URL + "/api-getsessionid.json";
/**
* 禅道登录
*/
public static final String ZENTAO_POST_LOGIN_URL = ZENTAO_DOMAIN_URL + "/user-login-account-password.json?account={0}&password={1}&zentaosid={2}";
/**
* 获取团队用户列表
*/
public static final String ZENTAO_GET_USERLIST_URL = ZENTAO_DOMAIN_URL + "/project-team-{0}.json?zentaosid={1}";
/**
* 创建禅道任务单
*/
public static final String ZENTAO_POST_CREATE_ORDER_URL = ZENTAO_DOMAIN_URL + "/task-create-{0}-{1}.json?zentaosid={2}";
/**
* 给工单添加备注
*/
public static final String ZENTAO_POST_ADD_ORDER_COMMENT_URL = ZENTAO_DOMAIN_URL + "/action-comment-task-{0}.json?zentaosid={1}";
/**
* 关闭工单,并添加备注
*/
public static final String ZENTAO_POST_CLOSE_ORDER_URL = ZENTAO_DOMAIN_URL + "/task-close-{0}.json?zentaosid={1}";
/**
* 批量关闭工单
*/
public static final String ZENTAO_GET_CLOSE_BATCH_ORDER_URL = ZENTAO_DOMAIN_URL + "&m=task&f=batchClose&skipTaskIdList={0}&zentaosid={1}";
/**
* 删除工单
*/
public static final String ZENTAO_GET_DELETE_ORDER_URL = ZENTAO_DOMAIN_URL + "/task-delete-{0}-{1}.json?confirm=yes&zentaosid={2}";
/**
* 根据参数请求禅道搜索框
*/
public static final String ZENTAO_POST_ORDER_SEARCH_URL = ZENTAO_DOMAIN_URL + "/search-buildQuery.json?zentaosid={0}";
/**
* 根据搜索框返回结果查询禅道搜索列表结果
*/
public static final String ZENTAO_GET_ORDER_LIST_URL = "?zentaosid={0}";
/**
* 条件查询禅道任务模块信息
*/
public static final String ZENTAO_GET_TASK_MODULE_LIST_URL = ZENTAO_DOMAIN_URL + "/tree-ajaxGetOptionMenu-{0}-task-0-0-json--0-.json?zentaosid={1}";
}
2.封装请求方法
/**
* 禅道api
*
* @author wangshuxue
* @date 2021/3/17
*/
public interface IZenTaoService {
/**
* @param
* @return String
* @description: 禅道登录
* @author hyh
* @date 2022/7/13 9:43
*/
String loginZenTao() throws IOException;
/**
* 获取禅道用户信息
*
* @return List
* @author hyh
* @params []
* @date 2022/7/13 9:43
*/
List<ZenTaoUserVo> listZentaoUsers(String projectCode) throws IOException;
/**
* @param
* @return String
* @description: 获取禅道session,默认时间是24分钟
* @author hyh
* @date 2022/7/13 9:50
*/
String getZenTaoSession();
/**
* @param acceptOrder
* @return String
* @description: 创建禅道任务单
* @author hyh
* @date 2022/7/13 9:53
*/
String createOrder(MultipartFile[] files, AcceptOrder acceptOrder) throws IOException;
/**
* @param acceptOrderRemark
* @return Boolean
* @description: 给工单添加备注
* @author hyh
* @date 2022/7/13 14:10
*/
Boolean addComment(AcceptOrderRemark acceptOrderRemark) throws IOException;
/**
* @param orderRemark
* @return Boolean
* @description: 关闭工单,并添加备注
* @author hyh
* @date 2022/7/13 13:34
*/
Boolean closeOrder(AcceptOrderRemark orderRemark) throws IOException;
/**
* @param ids
* @return Boolean
* @description: 批量关闭工单
* @author hyh
* @date 2022/7/13 13:34
*/
Boolean closeBatchOrder(String ids) throws IOException;
/**
* @param acceptOrder
* @return Boolean
* @description: 删除工单
* @author hyh
* @date 2022/7/13 13:34
*/
Boolean delOrder(AcceptOrder acceptOrder) throws IOException;
/**
* @param order
* @return String
* @description: 根据参数请求禅道搜索框
* @author hyh
* @date 2022/8/16 14:17
*/
String getZenTaoSearchLocalUrl(AcceptOrder order) throws IOException;
/**
* @param locateUrl
* @return String
* @description: 根据搜索框返回结果查询禅道搜索列表结果
* @author hyh
* @date 2022/8/16 14:17
*/
String getZenTaoSearchList(String locateUrl) throws IOException;
/**
* 条件查询禅道任务模块信息
*
* @return List
* @author hyh
* @params []
* @date 2022/7/13 9:43
*/
List<ZenTaoTaskModuleVo> getZenTaoModuleList(String projectCode) throws IOException;
}
3.创建方法实现类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.vcom.pmfr.enums.ZenTaoOptionEnum;
import com.vcom.pmfr.enums.ZenTaoTaskStatusEnum;
import com.vcom.pmfr.exception.BaseException;
import com.vcom.pmfr.mapper.SDictDataMapper;
import com.vcom.pmfr.model.common.Constant;
import com.vcom.pmfr.model.common.ZenTaoConstants;
import com.vcom.pmfr.model.entity.AcceptOrder;
import com.vcom.pmfr.model.entity.AcceptOrderRemark;
import com.vcom.pmfr.model.entity.SDictData;
import com.vcom.pmfr.model.entity.SysUser;
import com.vcom.pmfr.model.zentao.ZenTaoClientVo;
import com.vcom.pmfr.model.zentao.ZenTaoTaskModuleVo;
import com.vcom.pmfr.model.zentao.ZenTaoUserVo;
import com.vcom.pmfr.service.IZenTaoService;
import com.vcom.pmfr.util.RedisUtil;
import com.vcom.pmfr.util.UserUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Service
public class ZenTaoServiceImpl implements IZenTaoService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private UserUtil userUtil;
private static final Long ZENTAO_SESSION_EXPIRE_TIME = 20 * 60L;
//禅道客服运维管理账号
@Value("${zentao.account}")
private String ZEN_TAO_ADMIN_ACCOUNT;
//禅道客服运维管理密码
@Value("${zentao.password}")
private String ZEN_TAO_ADMIN_PASSWORD;
/**
* 设置超时时间
*/
private static final int CONNECT_TIMEOUT = 100;
private static final int READ_TIMEOUT = 45;
private static final int WRITE_TIMEOUT = 45;
@Autowired
private SDictDataMapper sDictDataMapper;
//img Matcher 对象
private static Pattern p_image = Pattern.compile("<img.*src\\s*=\\s*(.*?)[^>]*?>", Pattern.CASE_INSENSITIVE);
//img src Matcher 对象
private static Pattern r_image = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)");
//禅道工单受理内容富文本内容图片存储位置
private static final String ZEN_TAO_ACCEPTCONTEXT_IMGS_PATH = "/localfile/data/acceptContextImgs/";
/**
* 获取禅道用户信息
*
* @return com.vcom.vpms.model.dto.ResponseBean
* @author wangshuxue
* @params []
* @date 2021/3/17
*/
@Override
public List<ZenTaoUserVo> listZentaoUsers(String projectCode) throws IOException {
List<ZenTaoUserVo> jsonList = new ArrayList<ZenTaoUserVo>();
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接获取禅道用户地址参数
String userUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_GET_USERLIST_URL, projectCode, zentaosid);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(userUrl);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_GETUSER.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
JSONObject data = jsonObject.getJSONObject("data");
JSONObject resultArray = data.getJSONObject("teamMembers");
if (CollectionUtils.isNotEmpty(resultArray)) {
for (Map.Entry entry : resultArray.entrySet()) {
JSONObject obj = (JSONObject) entry.getValue();
ZenTaoUserVo vo = new ZenTaoUserVo();
vo.setId(Integer.parseInt(obj.getString("id")));
vo.setAccount(obj.getString("account"));
vo.setRealname(obj.getString("realname"));
jsonList.add(vo);
}
}
}
return jsonList;
}
/**
* @param
* @return String
* @description: 获取禅道session,默认时间是24分钟
* @author hyh
* @date 2022/7/13 9:50
*/
@Override
public String getZenTaoSession() {
String zentaosid = null;
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(ZenTaoConstants.ZENTAO_GET_SESSION_URL);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_SESSION.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
String tmp = StringEscapeUtils.unescapeEcmaScript(jsonObject.getString("data"));
JSONObject object = JSONObject.parseObject(tmp);
zentaosid = object.getString("sessionID");
//将禅道sessionid放入缓存,设置平台20分钟失效,禅道24分钟失效
redisUtil.set(getZenTaoKey(), zentaosid, ZENTAO_SESSION_EXPIRE_TIME);
}
return zentaosid;
}
/**
* @param
* @return String
* @description: 禅道登录
* @author hyh
* @date 2022/7/13 9:43
*/
@Override
public String loginZenTao() throws IOException {
//获取禅道session
String zentaosid = getZentaoSid();
//创建请求
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
//获取用户禅道账户密码
SysUser sysUser = getLoginUserInfo();
//拼接登录地址参数
String loginUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_POST_LOGIN_URL,
StringUtils.isNotEmpty(sysUser.getChandaoAccount()) ? sysUser.getChandaoAccount() : ZEN_TAO_ADMIN_ACCOUNT,
StringUtils.isNotEmpty(sysUser.getChandaoPassword()) ? sysUser.getChandaoPassword() : ZEN_TAO_ADMIN_PASSWORD, zentaosid);
log.info(loginUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(loginUrl);
zenTaoClientVo.setBody(body);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_POST_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_LOGIN.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
log.info("发起登录");
if (ObjectUtils.isEmpty(jsonObject)) {
log.info("禅道token未获取到");
redisUtil.del(getZenTaoKey());
throw new BaseException("禅道token未获取到");
}
if (jsonObject.containsKey("status") && "failed".equals(jsonObject.getString("status"))) {
log.info("禅道token未获取到:" + jsonObject.getString("reason"));
redisUtil.del(getZenTaoKey());
throw new BaseException(jsonObject.getString("reason"));
}
log.info("登陆成功");
return zentaosid;
}
/**
* @param
* @return String
* @description: 创建禅道任务单
* @author hyh
* @date 2022/7/13 9:53
*/
@Override
public String createOrder(MultipartFile[] files, AcceptOrder acceptOrder) throws IOException {
log.info("创建禅道工单,请求参数{}", acceptOrder.toString());
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//禅道任务id
String id = null;
//获取文件map
Map<MultipartFile, String> multipartFilesMap = getFilesObject(files);
//获取处理内容富文本中的图片,并传入禅道附件
Map<File, String> fileMap = getAcceptContextImgs(acceptOrder.getAcceptContext());
log.info("创建禅道工单,拼装请求参数");
//拼装请求参数
RequestBody body = packageCreateOrderObject(acceptOrder, multipartFilesMap, fileMap);
//拼接创建工单地址参数
String createUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_POST_CREATE_ORDER_URL, acceptOrder.getProjectCode(), acceptOrder.getTaskModule(), zentaosid);//项目id
log.info(createUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(createUrl);
zenTaoClientVo.setBody(body);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_POST_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_ADD.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
log.info("请求成功--" + jsonObject.toJSONString());
if (ObjectUtils.isNotEmpty(jsonObject)) {
//超时处理
if (jsonObject.containsKey("overTime") && jsonObject.getBoolean("overTime")) {
//超时处理
log.info("禅道sid:{},超时处理" + zentaosid);
return "工单创建中";
}
//成功
//判断是否有失败请求
if ("fail".equals(jsonObject.getString("result"))) {
String msg = "";
JSONObject messageObj = jsonObject.getJSONObject("message");
for (String key : messageObj.keySet()) {
log.error("错误值{},原因{}", key, messageObj.get(key).toString());
msg = messageObj.get(key).toString();
}
//删除缓存
redisUtil.del(getZenTaoKey());
throw new BaseException(msg);
}
id = jsonObject.getString("id");
}
log.info("禅道id=" + zentaosid);
return id;
}
/**
* @param
* @return
* @description: 给工单添加备注
* @author hyh
* @date 2022/7/13 14:10
*/
@Override
public Boolean addComment(AcceptOrderRemark acceptOrderRemark) throws IOException {
Boolean isSuccess = false;
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("comment", acceptOrderRemark.getContext()).build();
//拼接增加工单评论url
String addCommentUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_POST_ADD_ORDER_COMMENT_URL, acceptOrderRemark.getSourceOrderNum(), zentaosid);
log.info("添加工单备注:" + addCommentUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(addCommentUrl);
zenTaoClientVo.setBody(body);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_POST_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_ADD_COMMENT.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
//成功
isSuccess = true;
}
return isSuccess;
}
/**
* @param
* @return
* @description: 关闭工单,并添加备注
* @author hyh
* @date 2022/7/13 13:34
*/
@Override
public Boolean closeOrder(AcceptOrderRemark orderRemark) throws IOException {
Boolean isSuccess = false;
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接关闭工单地址参数
String closeUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_POST_CLOSE_ORDER_URL, orderRemark.getSourceOrderNum(), zentaosid);
log.info("关闭工单:" + closeUrl);
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("comment", orderRemark.getContext())
// .addFormDataPart("status", ZenTaoTaskStatusEnum.ZEN_TAO_TASK_STATUS_CLOSE.getCode())
// .addFormDataPart("deadline", orderRemark.getDeadline()) //必传 预计结束日期
.build();
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(closeUrl);
zenTaoClientVo.setBody(body);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_POST_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_CLOSE.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
if ("fail".equals(jsonObject.getJSONObject("data").getString("result"))) {
log.info("工单关闭失败,原因:", jsonObject.getJSONObject("data").getString("message"));
} else {
//成功
isSuccess = true;
}
}
return isSuccess;
}
/**
* @param ids
* @return
* @description: 批量关闭工单
* @author hyh
* @date 2022/7/13 13:34
*/
@Override
public Boolean closeBatchOrder(String ids) throws IOException {
Boolean isSuccess = false;
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接关闭工单地址参数
String closeUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_GET_CLOSE_BATCH_ORDER_URL, ids, zentaosid);
log.info("批量关闭工单:" + closeUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(closeUrl);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_BATCH_CLOSE.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
//成功
isSuccess = true;
}
return isSuccess;
}
/**
* @param
* @return
* @description: 删除工单
* @author hyh
* @date 2022/7/13 13:34
*/
@Override
public Boolean delOrder(AcceptOrder acceptOrder) throws IOException {
Boolean isSuccess = false;
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接删除工单地址参数
String delUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_GET_DELETE_ORDER_URL, acceptOrder.getProjectCode(), acceptOrder.getSourceOrderNum(), zentaosid);
log.info("删除工单:" + delUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(delUrl);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_DEL.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
//成功
isSuccess = true;
}
return isSuccess;
}
/**
* @param order
* @return String
* @description: 根据参数请求禅道搜索框
* @author hyh
* @date 2022/8/16 14:17
*/
@Override
public String getZenTaoSearchLocalUrl(AcceptOrder order) throws IOException {
String locateUrl = "";
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接 根据参数请求禅道搜索框 地址参数
String searchUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_POST_ORDER_SEARCH_URL, zentaosid);
log.info("根据参数请求禅道搜索框:" + searchUrl);
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("field1", "name")//搜索参数 标题
.addFormDataPart("operator1", "=")//搜索限制 include 包含 ,=等于
.addFormDataPart("value1", order.getTaskName()) //搜索值
.addFormDataPart("module", "workTask") //模块
.addFormDataPart("actionURL", "/my-work-task-bySearch-myQueryID.json") //响应返回地址
.build();
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(searchUrl);
zenTaoClientVo.setBody(body);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_POST_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_SEARCH_TASK.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
String tmp = StringEscapeUtils.unescapeEcmaScript(jsonObject.getString("data"));
JSONObject object = JSONObject.parseObject(tmp);
//成功
locateUrl = object.getString("locate");
}
return locateUrl;
}
/**
* @param locateUrl
* @return String
* @description: 根据搜索框返回结果查询禅道搜索列表结果
* @author hyh
* @date 2022/8/16 14:17
*/
@Override
public String getZenTaoSearchList(String locateUrl) throws IOException {
String id = "";
if (StringUtils.isEmpty(locateUrl)) {
return id;
}
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接 根据搜索框返回结果查询禅道搜索列表结果 地址参数
String searchListUrl = locateUrl + MessageFormat.format(ZenTaoConstants.ZENTAO_GET_ORDER_LIST_URL, zentaosid);
log.info("根据搜索框返回结果查询禅道搜索列表结果:" + searchListUrl);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(searchListUrl);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_TASK_LIST.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
JSONObject data = jsonObject.getJSONObject("data");
JSONObject taskObj = new JSONObject();
Object object = JSON.parse(data.getString("tasks"));
//专业版 只有符合条件可以查出来一条并且是JSONObject格式,若为空或多条则是JSONArray
//旗舰版 符合条件查出来一条是JSONArray格式
if (object instanceof JSONObject) {
taskObj = (JSONObject) object;
} else if (object instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) object;
log.info(" jsonArray:" + jsonArray.toJSONString());
if (CollectionUtils.isNotEmpty(jsonArray)) {
log.info(" 请求禅道用户任务列表返回的数据-jsonArray-开始");
for (int i = 0; i < jsonArray.size(); i++) {
taskObj = jsonArray.getJSONObject(i);
}
log.info(" 请求禅道用户任务列表返回的数据-jsonArray-结束,若不打印则转换失败");
}
}
//成功
if (ObjectUtils.isNotEmpty(taskObj)) {
//将jsonObj转换成Map
Map<String, Object> itemMap = JSONObject.toJavaObject(taskObj, Map.class);
for (Object key : itemMap.keySet()) {
//判断map中任务的id
if("id".equals(key)){
id=itemMap.get(key).toString();
break;
}
}
log.info("执行根据任务名称查询禅道未同步的数据获取到的禅道任务数据:{}", JSON.toJSONString(itemMap));
}
}
return id;
}
@Override
public List<ZenTaoTaskModuleVo> getZenTaoModuleList(String projectCode) throws IOException {
List<ZenTaoTaskModuleVo> jsonList = new ArrayList<ZenTaoTaskModuleVo>();
//获取禅道session,每次刷新登录
String zentaosid = loginZenTao();
//拼接获取禅道用户地址参数
//TODO 暂时修改为 "635".equals(acceptOrder.getProjectCode()) ? "1" : "627"
String userUrl = MessageFormat.format(ZenTaoConstants.ZENTAO_GET_TASK_MODULE_LIST_URL, "635".equals(projectCode) ? "1" : "627", zentaosid);
//拼装请求客户端参数
ZenTaoClientVo zenTaoClientVo = new ZenTaoClientVo();
zenTaoClientVo.setZenTaoUrl(userUrl);
zenTaoClientVo.setBody(null);
zenTaoClientVo.setMethodType(ZenTaoConstants.HTTP_GET_METHOD);
zenTaoClientVo.setOption(ZenTaoOptionEnum.ZEN_TAO_OPTION_TASK_MODULE_LIST.getInfo());
JSONObject jsonObject = packageZenTaoHttpClient(zenTaoClientVo);
if (ObjectUtils.isNotEmpty(jsonObject)) {
Iterator it = jsonObject.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();// 获得key
String value = jsonObject.getString(key);// 获得value
ZenTaoTaskModuleVo taskModuleVo = new ZenTaoTaskModuleVo();
taskModuleVo.setModuleCode(key).setModuleName(value);
jsonList.add(taskModuleVo);
}
}
return jsonList;
}
/**
* @param
* @return String zentaosid
* @description: 获取zentaosid
* @author hyh
* @date 2022/7/13 17:24
*/
private String getZentaoSid() throws IOException {
String zentaosid = null;
String zentaoKey = getZenTaoKey();
//判断缓存值是否存在
if (ObjectUtils.isEmpty(redisUtil.get(zentaoKey))) {
zentaosid = getZenTaoSession();
} else {
zentaosid = redisUtil.get(zentaoKey).toString();
}
if (StringUtils.isEmpty(zentaosid)) {
log.error("禅道获取zentaosid失败");
throw new BaseException("禅道获取zentaosid失败");
}
return zentaosid;
}
/**
* @param
* @return
* @description: 获取 ZenTaoKey
* @author hyh
* @date 2022/7/14 9:01
*/
private String getZenTaoKey() {
// 获取当前登录用户
SysUser userCurrent = userUtil.getCurrentUser();
if (ObjectUtils.isEmpty(userCurrent)) {
log.error("获取当前登录用户失败");
throw new BaseException("获取当前登录用户失败");
}
return Constant.PREFIX_ZENTAO_SESSION + userCurrent.getId();
}
/**
* @param
* @return
* @description: 获取 登录人信息
* @author hyh
* @date 2022/7/14 9:01
*/
private SysUser getLoginUserInfo() {
// 获取当前登录用户
SysUser userCurrent = userUtil.getCurrentUser();
if (ObjectUtils.isEmpty(userCurrent)) {
log.error("获取当前登录用户失败");
throw new BaseException("获取当前登录用户失败");
}
return userCurrent;
}
/**
* @param
* @return
* @description: 封装禅道创建工单请求体
* @author hyh
* @date 2022/7/14 9:17
*/
private RequestBody packageCreateOrderObject(AcceptOrder acceptOrder, Map<MultipartFile, String> multipartFileMap, Map<File, String> fileMap) {
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
String status = "";
//判断是否有处理结果,如果有结果直接关闭
if (StringUtils.isNotEmpty(acceptOrder.getAcceptResult())) {
status = ZenTaoTaskStatusEnum.ZEN_TAO_TASK_STATUS_CLOSE.getCode();
} else {
status = ZenTaoTaskStatusEnum.ZEN_TAO_TASK_STATUS_DOING.getCode();
}
//TODO 暂时修改为 "635".equals(acceptOrder.getProjectCode()) ? "1" : "627"
builder.addFormDataPart("execution", "635".equals(acceptOrder.getProjectCode()) ? "1" : "627") //必传 项目id
.addFormDataPart("type", getZnTaoDataByData("taskType", acceptOrder.getZenTaoType())) //必传 任务类型
.addFormDataPart("assignedTo[]", acceptOrder.getAssignedTo()) //必传 指派给谁,必传
.addFormDataPart("status", status) //后台必传 任务状态
.addFormDataPart("name", acceptOrder.getTaskName()) //必传 任务名称,
.addFormDataPart("pri", getZnTaoDataByData("pri", acceptOrder.getPri())) //必传 任务优先级,1-4,最高,较高,一般,最低
.addFormDataPart("estimate", acceptOrder.getAssessTime().toString()) //必传 预计工时
.addFormDataPart("desc", stripHtml(acceptOrder.getAcceptContext())) //必传 任务描述
.addFormDataPart("estStarted", acceptOrder.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) //必传 预计开始日期
.addFormDataPart("deadline", acceptOrder.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) //必传 预计结束日期
// .addFormDataPart("story", "") //非必传 关联需求
// .addFormDataPart("color", "") //非必传 标题颜色
.addFormDataPart("module", acceptOrder.getTaskModule()); //非必传 所属模块
log.info("组装时的参数:execution:{},type:{},assignedTo:{},status:{},name:{},pri:{},estimate:{},desc:{},estStarted:{},deadline:{}",
acceptOrder.getProjectCode(), getZnTaoDataByData("taskType", acceptOrder.getZenTaoType()), acceptOrder.getAssignedTo(), status,
acceptOrder.getTaskName(), getZnTaoDataByData("pri", acceptOrder.getPri()),
acceptOrder.getAssessTime().toString(), acceptOrder.getAcceptContext(), acceptOrder.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
acceptOrder.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//添加文件
if (CollectionUtils.isNotEmpty(multipartFileMap)) {
for (Map.Entry<MultipartFile, String> entry : multipartFileMap.entrySet()) {
builder.addFormDataPart("files[]", entry.getValue(), RequestBody.create(MediaType.parse("application/octet-stream"), multipartFileToFile(entry.getKey())))//非必传 附件
.addFormDataPart("labels[]", entry.getValue()); //非必传 附件名称;
}
}
//处理内容富文本中的图片,并传入禅道附件
if (CollectionUtils.isNotEmpty(fileMap)) {
for (Map.Entry<File, String> entry : fileMap.entrySet()) {
builder.addFormDataPart("files[]", entry.getValue(), RequestBody.create(MediaType.parse("application/octet-stream"), entry.getKey()))//非必传 附件
.addFormDataPart("labels[]", entry.getValue());
}
}
RequestBody body = builder.build();
return body;
}
/**
* 去除富文本内容的html标签
*
* @param content
* @return
*/
public static String stripHtml(String content) {
//定义style的正则表达式
String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>";
//开始转换
content = content.replaceAll(regEx_style, "").replaceAll("<[a-zA-Z]+[1-9]?[^><]*>", "").replaceAll("</[a-zA-Z]+[1-9]?>", "").replaceAll("<!--[\\w\\W\\r\\n]*?-->", "");
return content;
}
/**
* @param type 字典类型 dataId 字典值id
* @return String
* @description: 根据字典类型字典值id获取所需字段
* @author hyh
* @date 2022/7/28 9:58
*/
private String getZnTaoDataByData(String type, String dataId) {
String code = "";
SDictData data = sDictDataMapper.selectById(dataId);
if (ObjectUtils.isNotEmpty(data)) {
if ("pri".equals(type)) {
code = data.getSort().toString();
}
if ("taskType".equals(type)) {
code = data.getDataCode();
}
}
return code;
}
/**
* @param
* @return
* @description: 根据文件查询文件绝对地址和名称并返回
* @author hyh
* @date 2022/7/14 10:35
*/
private Map<MultipartFile, String> getFilesObject(MultipartFile[] files) {
Map<MultipartFile, String> map = new HashMap<>(1 << 4);
if (files.length > 0) {
for (MultipartFile file : files) {
if (file.isEmpty()) {
throw new BaseException("文件不存在");
}
//文件名称
String fileName = file.getOriginalFilename();
//保存平台工单信息
map.put(file, fileName);
}
}
return map;
}
/**
* MultipartFile 转 File
*
* @param multipartFile
* @throws Exception
*/
public static File multipartFileToFile(MultipartFile multipartFile) {
File file = null;
//判断是否为null
if (multipartFile.equals("") || multipartFile.getSize() <= 0) {
return file;
}
//MultipartFile转换为File
InputStream ins = null;
OutputStream os = null;
try {
ins = multipartFile.getInputStream();
file = new File(multipartFile.getOriginalFilename());
os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ins != null) {
try {
ins.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return file;
}
/**
* @param zenTaoClientVo
* @return
* @description: 封装禅道请求客户端
* @author hyh
* @date 2022/7/20 17:25
*/
public JSONObject packageZenTaoHttpClient(ZenTaoClientVo zenTaoClientVo) {
Response response = null;
JSONObject jsonObject = new JSONObject();
try {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.build();
MediaType mediaType = MediaType.parse("text/plain");
//拼接增加工单评论url
Request request = new Request.Builder()
.url(zenTaoClientVo.getZenTaoUrl())
.method(zenTaoClientVo.getMethodType(), zenTaoClientVo.getBody())
.build();
response = client.newCall(request).execute();
if (response.isSuccessful()) {
String res = response.body().string();
log.info("操作:" + zenTaoClientVo.getOption() + ",请求客户端返回信息:{}", res);
if (isJSON2(res)) {
jsonObject = JSONObject.parseObject(res);
log.info("如果是json,此时解析返回的数据:{}", jsonObject.toJSONString());
} else {
jsonObject.put("flag", true);
}
}
} catch (SocketTimeoutException e) {
// 超时
log.error("executeHttpRequest TimeOut, e: " + e);
//超时处理
jsonObject.put("overTime", true);
// throw new BaseException("禅道" + zenTaoClientVo.getOption() + "超时");
} catch (IOException e) {
// 网络IO异常
log.error("executeHttpRequest IOException, e: " + e);
throw new BaseException("禅道" + zenTaoClientVo.getOption() + "网络IO异常");
} catch (Exception e) {
// 其他异常
log.error("禅道" + zenTaoClientVo.getOption() + "失败");
throw new BaseException("禅道" + zenTaoClientVo.getOption() + "失败");
} finally {
if (response != null) {
// body 必须被关闭,否则会发生资源泄漏;
response.body().close();
}
}
return jsonObject;
}
/**
* @param str
* @return boolean
* @description: 判断是否是json格式
* @author hyh
* @date 2022/7/21 15:21
*/
public static boolean isJSON2(String str) {
boolean result = false;
try {
Object obj = JSON.parse(str);
result = true;
} catch (Exception e) {
result = false;
}
return result;
}
/**
* @param acceptContext 受理内容
* @return Map<MultipartFile, String>
* @description: 获取处理内容富文本中的图片,并传入禅道附件
* @author hyh
* @date 2022/8/5 9:12
*/
public static Map<File, String> getAcceptContextImgs(String acceptContext) {
Map<File, String> fileMap = new HashMap<>(1 << 4);
String num = String.format("%04d", new Random().nextInt(10000));
if (StringUtils.isNotEmpty(acceptContext)) {
List<String> imgList = getImgStr(acceptContext);
if (!org.springframework.util.CollectionUtils.isEmpty(imgList)) {
for (int i = 0; i < imgList.size(); i++) {
if (imgList.get(i).contains("base64,")) {
String fileName = String.format("%s%s%s", num, i, ".jpg");
fileMap.put(base64ToFile(imgList.get(i).split("base64,")[1], fileName), fileName);
}
}
}
}
return fileMap;
}
//BASE64解码成File文件
public static File base64ToFile(String base64, String fileName) {
File file = null;
//创建文件目录
String filePath = ZEN_TAO_ACCEPTCONTEXT_IMGS_PATH;
File dir = new File(filePath);
if (!dir.exists() && !dir.isDirectory()) {
dir.mkdirs();
}
BufferedOutputStream bos = null;
java.io.FileOutputStream fos = null;
try {
byte[] bytes = Base64.getDecoder().decode(base64);
file = new File(filePath + "/" + fileName);
fos = new java.io.FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bytes);
} catch (Exception e) {
log.error("禅道工单富文本图片存储失败0", e);
throw new BaseException("禅道工单富文本图片存储失败");
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
log.error("禅道工单富文本图片存储失败1", e);
throw new BaseException("禅道工单富文本图片存储失败");
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
log.error("禅道工单富文本图片存储失败2", e);
throw new BaseException("禅道工单富文本图片存储失败");
}
}
}
return file;
}
/**
* @return java.util.List<java.lang.String>
* @Author shipj
* @Description 提取富文本图片(img标签src属性)
* @Date 20:49 2021-04-12
* @Param [htmlStr]
**/
public static List<String> getImgStr(String htmlStr) {
List<String> list = new ArrayList<>();
Matcher m_image = p_image.matcher(htmlStr);
while (m_image.find()) {
// 得到<img />数据
String img = m_image.group();
// System.out.println(img);
// 匹配<img>中的src数据
Matcher m = r_image.matcher(img);
while (m.find()) {
list.add(m.group(1));
}
}
return list;
}
}
实体类:
/**
* @ClassName ZenTaoUserVo
* @Description 禅道客户端请求 VO
* @Version 1.0
*/
@Data
@Accessors(chain = true)
public class ZenTaoClientVo {
/**
* 请求体
* */
private RequestBody body;
/**
* 请求地址
* */
private String zenTaoUrl;
/**
* 方法类型
* */
private String methodType;
/**
* 操作类型
* */
private String option;
}
@Data
@Accessors(chain = true)
public class ZenTaoUserVo {
/**
* 用户id
* */
private Integer id;
/**
* 用户部门
* */
// private Integer dept;
/**
* 用户账户名称
* */
private String account;
/**
* 用户真实名称
* */
private String realname;
/**
* 用户角色
* */
// private String role;
/**
* 用户pinyin
* */
// private String pinyin;
/**
* 用户email
* */
// private String email;
}