feat(测试跟踪): 缺陷管理附件同步功能

--story=1008034 --user=宋昌昌 【Bug转需求】[缺陷管理]-github#9580-jira集成,缺陷模版使用 jira 缺陷模版,字段没有同步全 https://www.tapd.cn/55049933/
This commit is contained in:
song-cc-rock 2022-07-11 17:29:53 +08:00 committed by jianxing
parent 863b8caf0e
commit ec1434964c
10 changed files with 145 additions and 23 deletions

View File

@ -27,8 +27,7 @@ public class FileUtils {
public static final String MD_IMAGE_DIR = "/opt/metersphere/data/image/markdown";
public static final String UI_IMAGE_DIR = "/opt/metersphere/data/image/ui/screenshots";
public static final String ATTACHMENT_DIR = "/opt/metersphere/data/attachment";
public static final String TEST_CASE_ATTACHMENT_DIR = "/opt/metersphere/data/attachment/testcase";
public static final String ISSUE_ATTACHMENT_DIR = "/opt/metersphere/data/attachment/issue";
public static final String ATTACHMENT_TMP_DIR = "/opt/metersphere/data/attachment/tmp";
public static byte[] listBytesToZip(Map<String, byte[]> mapReport) {

View File

@ -14,8 +14,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -137,6 +136,38 @@ public class FileService {
return fileAttachmentMetadata;
}
public FileAttachmentMetadata saveAttachmentByBytes(byte[] bytes, String attachmentType, String belongId, String attachmentName) {
String uploadPath = FileUtils.ATTACHMENT_DIR + "/" + attachmentType + "/" + belongId;
File parentFile = new File(uploadPath);
if (!parentFile.exists()) {
parentFile.mkdir();
}
try (OutputStream os = new FileOutputStream(uploadPath + "/" + attachmentName)){
InputStream in = new ByteArrayInputStream(bytes);
int len = 0;
byte[] buf = new byte[1024];
while ((len = in.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.flush();
final FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata();
fileAttachmentMetadata.setId(UUID.randomUUID().toString());
fileAttachmentMetadata.setName(attachmentName);
fileAttachmentMetadata.setType(getFileType(attachmentName).name());
fileAttachmentMetadata.setSize(Integer.valueOf(bytes.length).longValue());
fileAttachmentMetadata.setCreateTime(System.currentTimeMillis());
fileAttachmentMetadata.setUpdateTime(System.currentTimeMillis());
fileAttachmentMetadata.setCreator(SessionUtils.getUser().getName());
fileAttachmentMetadata.setFilePath(uploadPath);
fileAttachmentMetadataMapper.insert(fileAttachmentMetadata);
return fileAttachmentMetadata;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void deleteAttachment(List<String> ids) {
for (String id : ids) {
FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(id);

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.IssueFileMapper;
import io.metersphere.base.mapper.IssuesMapper;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.TestCaseIssuesMapper;
@ -64,6 +65,9 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
protected boolean isThirdPartTemplate;
protected CustomFieldIssuesService customFieldIssuesService;
protected CustomFieldService customFieldService;
protected IssuesService issuesService;
protected FileService fileService;
protected IssueFileMapper issueFileMapper;
public String getKey() {
return key;
@ -90,6 +94,9 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
this.testCaseIssueService = CommonBeanFactory.getBean(TestCaseIssueService.class);
this.customFieldIssuesService = CommonBeanFactory.getBean(CustomFieldIssuesService.class);
this.customFieldService = CommonBeanFactory.getBean(CustomFieldService.class);
this.issuesService = CommonBeanFactory.getBean(IssuesService.class);
this.fileService = CommonBeanFactory.getBean(FileService.class);
this.issueFileMapper = CommonBeanFactory.getBean(IssueFileMapper.class);
}
protected String getPlatformConfig(String platform) {

View File

@ -11,6 +11,7 @@ import io.metersphere.track.request.testcase.EditTestCaseRequest;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@ -31,13 +32,13 @@ public interface IssuesPlatform {
*
* @param issuesRequest issueRequest
*/
IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest);
IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List<MultipartFile> files);
/**
* 更新缺陷
* @param request
*/
void updateIssue(IssuesUpdateRequest request);
void updateIssue(IssuesUpdateRequest request, List<MultipartFile> files);
/**
* 删除缺陷平台缺陷

View File

@ -2,15 +2,15 @@ package io.metersphere.track.issue;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.base.domain.IssuesWithBLOBs;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.*;
import io.metersphere.base.domain.ext.CustomFieldResource;
import io.metersphere.commons.constants.AttachmentType;
import io.metersphere.commons.constants.CustomFieldType;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.dto.CustomFieldItemDTO;
@ -31,8 +31,10 @@ import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@ -225,7 +227,7 @@ public class JiraPlatform extends AbstractIssuePlatform {
}
@Override
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) {
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List<MultipartFile> files) {
setUserConfig();
Project project = getProject();
@ -237,6 +239,20 @@ public class JiraPlatform extends AbstractIssuePlatform {
// 上传附件
imageFiles.forEach(img -> jiraClientV2.uploadAttachment(result.getKey(), img));
if (files != null) {
files.forEach(multipartFile -> {
try {
File file = new File(FileUtils.ATTACHMENT_TMP_DIR + "/" + multipartFile.getOriginalFilename());
org.apache.commons.io.FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
jiraClientV2.uploadAttachment(result.getKey(), file);
if (file.exists()) {
file.delete();
}
} catch (IOException e) {
LogUtil.error(e);
}
});
}
String status = getStatus(issues.getFields());
issuesRequest.setPlatformStatus(status);
@ -463,7 +479,7 @@ public class JiraPlatform extends AbstractIssuePlatform {
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
public void updateIssue(IssuesUpdateRequest request, List<MultipartFile> files) {
setUserConfig();
Project project = getProject();
List<File> imageFiles = getImageFiles(request);
@ -471,18 +487,27 @@ public class JiraPlatform extends AbstractIssuePlatform {
JSONObject param = buildUpdateParam(request, getIssueType(project.getIssueConfig()), project.getJiraKey());
jiraClientV2.updateIssue(request.getPlatformId(), JSONObject.toJSONString(param));
List<FileMetadata> updatedFiles = request.getUpdatedFileList();
List<String> updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
List<FileAttachmentMetadata> originFiles = fileService.getFileAttachmentMetadataByIssueId(request.getId());
List<String> deleteAttachmentNames = new ArrayList<String>();
originFiles.forEach(originFile -> {
if (!updatedFileIds.contains(originFile.getId())) {
deleteAttachmentNames.add(originFile.getName());
}
});
Set<String> attachmentNames = new HashSet<>();
// 更新附件
JiraIssue jiraIssue = jiraClientV2.getIssues(request.getPlatformId());
JSONObject fields = jiraIssue.getFields();
JSONArray attachments = fields.getJSONArray("attachment");
if (!attachments.isEmpty() && attachments.size() > 0) {
// 删除旧附件若缺陷描述中存在则不删除
// 删除旧附件若缺陷描述中不存在且附件上传的删除列表中存在则删除
for (int i = 0; i < attachments.size(); i++) {
JSONObject attachment = attachments.getJSONObject(i);
String filename = attachment.getString("filename");
attachmentNames.add(filename);
if (!request.getDescription().contains(filename)) {
if (!request.getDescription().contains(filename) && deleteAttachmentNames.contains(filename)) {
String fileId = attachment.getString("id");
jiraClientV2.deleteAttachment(fileId);
}
@ -496,6 +521,20 @@ public class JiraPlatform extends AbstractIssuePlatform {
jiraClientV2.uploadAttachment(request.getPlatformId(), img);
}
});
if (files != null) {
files.forEach(multipartFile -> {
try {
File file = new File(FileUtils.ATTACHMENT_TMP_DIR + "/" + multipartFile.getOriginalFilename());
org.apache.commons.io.FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
jiraClientV2.uploadAttachment(request.getPlatformId(), file);
if (file.exists()) {
file.delete();
}
} catch (IOException e) {
LogUtil.error(e);
}
});
}
if (request.getTransitions() != null) {
try {
@ -554,6 +593,8 @@ public class JiraPlatform extends AbstractIssuePlatform {
List<CustomFieldResource> customFieldResource = customFieldService.getCustomFieldResource(customFields);
customFieldMap.put(item.getId(), customFieldResource);
issuesMapper.updateByPrimaryKeySelective(item);
// 同步第三方平台系统附件字段
syncJiraIssueAttachments(item, jiraClientV2.getIssues(item.getPlatformId()));
} catch (HttpClientErrorException e) {
if (e.getRawStatusCode() == 404) {
// 标记成删除
@ -854,4 +895,23 @@ public class JiraPlatform extends AbstractIssuePlatform {
public ResponseEntity proxyForGet(String url, Class responseEntityClazz) {
return jiraClientV2.proxyForGet(url, responseEntityClazz);
}
public void syncJiraIssueAttachments(IssuesWithBLOBs issue, JiraIssue jiraIssue) {
issuesService.deleteIssueAttachments(issue.getId());
JSONArray attachments = jiraIssue.getFields().getJSONArray("attachment");
if (CollectionUtils.isEmpty(attachments)) {
return;
}
for (int i = 0; i < attachments.size(); i++) {
JSONObject attachment = attachments.getJSONObject(i);
String id = attachment.getString("id");
byte[] content = jiraClientV2.getAttachmentContent(id);
String filename = attachment.getString("filename");
FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachmentByBytes(content, AttachmentType.ISSUE.type(), issue.getId(), filename);
IssueFile issueFile = new IssueFile();
issueFile.setIssueId(issue.getId());
issueFile.setFileId(fileAttachmentMetadata.getId());
issueFileMapper.insert(issueFile);
}
}
}

View File

@ -13,6 +13,7 @@ import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.UUID;
@ -41,7 +42,7 @@ public class LocalPlatform extends LocalAbstractPlatform {
}
@Override
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) {
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List<MultipartFile> files) {
String issueStatus = "new";
if (StringUtils.isNotBlank(issuesRequest.getCustomFields())) {
List<CustomFieldItemDTO> customFields = issuesRequest.getRequestFields();
@ -75,7 +76,7 @@ public class LocalPlatform extends LocalAbstractPlatform {
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
public void updateIssue(IssuesUpdateRequest request, List<MultipartFile> files) {
handleIssueUpdate(request);
}
}

View File

@ -28,6 +28,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.stream.Collectors;
@ -88,7 +89,7 @@ public class TapdPlatform extends AbstractIssuePlatform {
}
@Override
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) {
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List<MultipartFile> files) {
MultiValueMap<String, Object> param = buildUpdateParam(issuesRequest);
TapdBug bug = tapdClient.addIssue(param);
@ -109,7 +110,7 @@ public class TapdPlatform extends AbstractIssuePlatform {
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
public void updateIssue(IssuesUpdateRequest request, List<MultipartFile> files) {
MultiValueMap<String, Object> param = buildUpdateParam(request);
param.add("id", request.getPlatformId());
handleIssueUpdate(request);

View File

@ -37,6 +37,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLDecoder;
import java.net.URLEncoder;
@ -197,7 +198,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
@Override
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) {
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List<MultipartFile> files) {
setUserConfig();
MultiValueMap<String, Object> param = buildUpdateParam(issuesRequest);
@ -228,7 +229,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
public void updateIssue(IssuesUpdateRequest request, List<MultipartFile> files) {
setUserConfig();
MultiValueMap<String, Object> param = buildUpdateParam(request);
if (request.getTransitions() != null) {

View File

@ -257,6 +257,14 @@ public abstract class JiraAbstractClient extends BaseClient {
return (JiraIssueListResponse)getResultForObject(JiraIssueListResponse.class, responseEntity);
}
public byte[] getAttachmentContent(String contentId) {
ResponseEntity<byte[]> responseEntity;
String url = getBaseUrl() + "/attachment/content/{1}";
responseEntity = restTemplate.exchange(url,
HttpMethod.GET, getAuthHttpEntity(), byte[].class, contentId);
return responseEntity.getBody();
}
public JiraIssueListResponse getProjectIssuesAttachment(int startAt, int maxResults, String projectKey, String issueType) {
return getProjectIssues(startAt, maxResults, projectKey, issueType, "attachment");

View File

@ -112,7 +112,7 @@ public class IssuesService {
List<AbstractIssuePlatform> platformList = getAddPlatforms(issuesRequest);
IssuesWithBLOBs issues = null;
for (AbstractIssuePlatform platform : platformList) {
issues = platform.addIssue(issuesRequest);
issues = platform.addIssue(issuesRequest, null);
}
if (issuesRequest.getIsPlanEdit()) {
issuesRequest.getAddResourceIds().forEach(l -> {
@ -131,7 +131,7 @@ public class IssuesService {
issuesRequest.getId();
List<AbstractIssuePlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.updateIssue(issuesRequest);
platform.updateIssue(issuesRequest, null);
});
customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields());
customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields());
@ -142,7 +142,7 @@ public class IssuesService {
List<AbstractIssuePlatform> platformList = getAddPlatforms(issuesRequest);
IssuesWithBLOBs issues = null;
for (AbstractIssuePlatform platform : platformList) {
issues = platform.addIssue(issuesRequest);
issues = platform.addIssue(issuesRequest, files);
}
if (issuesRequest.getIsPlanEdit()) {
issuesRequest.getAddResourceIds().forEach(l -> {
@ -187,7 +187,7 @@ public class IssuesService {
issuesRequest.getId();
List<AbstractIssuePlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.updateIssue(issuesRequest);
platform.updateIssue(issuesRequest, files);
});
customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields());
customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields());
@ -929,4 +929,17 @@ public class IssuesService {
return platformStatusDTOS;
}
public void deleteIssueAttachments(String issueId) {
fileService.deleteAttachment(AttachmentType.ISSUE.type(), issueId);
IssueFileExample example = new IssueFileExample();
example.createCriteria().andIssueIdEqualTo(issueId);
List<IssueFile> issueFiles = issueFileMapper.selectByExample(example);
if (issueFiles.size() == 0) {
return;
}
List<String> ids = issueFiles.stream().map(IssueFile::getFileId).collect(Collectors.toList());
fileService.deleteFileAttachmentByIds(ids);
issueFileMapper.deleteByExample(example);
}
}