Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
MeterSphere
metersphere
提交
8ce89cf4
M
metersphere
项目概览
MeterSphere
/
metersphere
上一次同步 3 年多
通知
25
Star
1
Fork
1
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
metersphere
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
8ce89cf4
编写于
10月 14, 2020
作者:
S
shiziyuan9527
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat(测试跟踪): 测试用例上传附件
上级
69a29e2c
变更
14
隐藏空白更改
内联
并排
Showing
14 changed file
with
847 addition
and
13 deletion
+847
-13
backend/src/main/java/io/metersphere/base/domain/TestCaseFile.java
...rc/main/java/io/metersphere/base/domain/TestCaseFile.java
+13
-0
backend/src/main/java/io/metersphere/base/domain/TestCaseFileExample.java
.../java/io/metersphere/base/domain/TestCaseFileExample.java
+340
-0
backend/src/main/java/io/metersphere/base/mapper/TestCaseFileMapper.java
...n/java/io/metersphere/base/mapper/TestCaseFileMapper.java
+22
-0
backend/src/main/java/io/metersphere/base/mapper/TestCaseFileMapper.xml
...in/java/io/metersphere/base/mapper/TestCaseFileMapper.xml
+140
-0
backend/src/main/java/io/metersphere/commons/constants/FileType.java
.../main/java/io/metersphere/commons/constants/FileType.java
+1
-1
backend/src/main/java/io/metersphere/service/FileService.java
...end/src/main/java/io/metersphere/service/FileService.java
+31
-0
backend/src/main/java/io/metersphere/track/controller/TestCaseController.java
...a/io/metersphere/track/controller/TestCaseController.java
+29
-6
backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java
...tersphere/track/request/testcase/EditTestCaseRequest.java
+14
-0
backend/src/main/java/io/metersphere/track/service/TestCaseService.java
...in/java/io/metersphere/track/service/TestCaseService.java
+60
-1
backend/src/main/java/io/metersphere/xpack
backend/src/main/java/io/metersphere/xpack
+1
-1
backend/src/main/resources/db/migration/V30__test_case_file.sql
...d/src/main/resources/db/migration/V30__test_case_file.sql
+7
-0
frontend/src/business/components/track/case/components/TestCaseEdit.vue
...usiness/components/track/case/components/TestCaseEdit.vue
+187
-2
frontend/src/business/components/track/case/components/TestCaseList.vue
...usiness/components/track/case/components/TestCaseList.vue
+1
-1
frontend/src/business/components/xpack
frontend/src/business/components/xpack
+1
-1
未找到文件。
backend/src/main/java/io/metersphere/base/domain/TestCaseFile.java
0 → 100644
浏览文件 @
8ce89cf4
package
io.metersphere.base.domain
;
import
java.io.Serializable
;
import
lombok.Data
;
@Data
public
class
TestCaseFile
implements
Serializable
{
private
String
caseId
;
private
String
fileId
;
private
static
final
long
serialVersionUID
=
1L
;
}
\ No newline at end of file
backend/src/main/java/io/metersphere/base/domain/TestCaseFileExample.java
0 → 100644
浏览文件 @
8ce89cf4
package
io.metersphere.base.domain
;
import
java.util.ArrayList
;
import
java.util.List
;
public
class
TestCaseFileExample
{
protected
String
orderByClause
;
protected
boolean
distinct
;
protected
List
<
Criteria
>
oredCriteria
;
public
TestCaseFileExample
()
{
oredCriteria
=
new
ArrayList
<
Criteria
>();
}
public
void
setOrderByClause
(
String
orderByClause
)
{
this
.
orderByClause
=
orderByClause
;
}
public
String
getOrderByClause
()
{
return
orderByClause
;
}
public
void
setDistinct
(
boolean
distinct
)
{
this
.
distinct
=
distinct
;
}
public
boolean
isDistinct
()
{
return
distinct
;
}
public
List
<
Criteria
>
getOredCriteria
()
{
return
oredCriteria
;
}
public
void
or
(
Criteria
criteria
)
{
oredCriteria
.
add
(
criteria
);
}
public
Criteria
or
()
{
Criteria
criteria
=
createCriteriaInternal
();
oredCriteria
.
add
(
criteria
);
return
criteria
;
}
public
Criteria
createCriteria
()
{
Criteria
criteria
=
createCriteriaInternal
();
if
(
oredCriteria
.
size
()
==
0
)
{
oredCriteria
.
add
(
criteria
);
}
return
criteria
;
}
protected
Criteria
createCriteriaInternal
()
{
Criteria
criteria
=
new
Criteria
();
return
criteria
;
}
public
void
clear
()
{
oredCriteria
.
clear
();
orderByClause
=
null
;
distinct
=
false
;
}
protected
abstract
static
class
GeneratedCriteria
{
protected
List
<
Criterion
>
criteria
;
protected
GeneratedCriteria
()
{
super
();
criteria
=
new
ArrayList
<
Criterion
>();
}
public
boolean
isValid
()
{
return
criteria
.
size
()
>
0
;
}
public
List
<
Criterion
>
getAllCriteria
()
{
return
criteria
;
}
public
List
<
Criterion
>
getCriteria
()
{
return
criteria
;
}
protected
void
addCriterion
(
String
condition
)
{
if
(
condition
==
null
)
{
throw
new
RuntimeException
(
"Value for condition cannot be null"
);
}
criteria
.
add
(
new
Criterion
(
condition
));
}
protected
void
addCriterion
(
String
condition
,
Object
value
,
String
property
)
{
if
(
value
==
null
)
{
throw
new
RuntimeException
(
"Value for "
+
property
+
" cannot be null"
);
}
criteria
.
add
(
new
Criterion
(
condition
,
value
));
}
protected
void
addCriterion
(
String
condition
,
Object
value1
,
Object
value2
,
String
property
)
{
if
(
value1
==
null
||
value2
==
null
)
{
throw
new
RuntimeException
(
"Between values for "
+
property
+
" cannot be null"
);
}
criteria
.
add
(
new
Criterion
(
condition
,
value1
,
value2
));
}
public
Criteria
andCaseIdIsNull
()
{
addCriterion
(
"case_id is null"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdIsNotNull
()
{
addCriterion
(
"case_id is not null"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdEqualTo
(
String
value
)
{
addCriterion
(
"case_id ="
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdNotEqualTo
(
String
value
)
{
addCriterion
(
"case_id <>"
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdGreaterThan
(
String
value
)
{
addCriterion
(
"case_id >"
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdGreaterThanOrEqualTo
(
String
value
)
{
addCriterion
(
"case_id >="
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdLessThan
(
String
value
)
{
addCriterion
(
"case_id <"
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdLessThanOrEqualTo
(
String
value
)
{
addCriterion
(
"case_id <="
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdLike
(
String
value
)
{
addCriterion
(
"case_id like"
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdNotLike
(
String
value
)
{
addCriterion
(
"case_id not like"
,
value
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdIn
(
List
<
String
>
values
)
{
addCriterion
(
"case_id in"
,
values
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdNotIn
(
List
<
String
>
values
)
{
addCriterion
(
"case_id not in"
,
values
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdBetween
(
String
value1
,
String
value2
)
{
addCriterion
(
"case_id between"
,
value1
,
value2
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andCaseIdNotBetween
(
String
value1
,
String
value2
)
{
addCriterion
(
"case_id not between"
,
value1
,
value2
,
"caseId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdIsNull
()
{
addCriterion
(
"file_id is null"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdIsNotNull
()
{
addCriterion
(
"file_id is not null"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdEqualTo
(
String
value
)
{
addCriterion
(
"file_id ="
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdNotEqualTo
(
String
value
)
{
addCriterion
(
"file_id <>"
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdGreaterThan
(
String
value
)
{
addCriterion
(
"file_id >"
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdGreaterThanOrEqualTo
(
String
value
)
{
addCriterion
(
"file_id >="
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdLessThan
(
String
value
)
{
addCriterion
(
"file_id <"
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdLessThanOrEqualTo
(
String
value
)
{
addCriterion
(
"file_id <="
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdLike
(
String
value
)
{
addCriterion
(
"file_id like"
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdNotLike
(
String
value
)
{
addCriterion
(
"file_id not like"
,
value
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdIn
(
List
<
String
>
values
)
{
addCriterion
(
"file_id in"
,
values
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdNotIn
(
List
<
String
>
values
)
{
addCriterion
(
"file_id not in"
,
values
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdBetween
(
String
value1
,
String
value2
)
{
addCriterion
(
"file_id between"
,
value1
,
value2
,
"fileId"
);
return
(
Criteria
)
this
;
}
public
Criteria
andFileIdNotBetween
(
String
value1
,
String
value2
)
{
addCriterion
(
"file_id not between"
,
value1
,
value2
,
"fileId"
);
return
(
Criteria
)
this
;
}
}
public
static
class
Criteria
extends
GeneratedCriteria
{
protected
Criteria
()
{
super
();
}
}
public
static
class
Criterion
{
private
String
condition
;
private
Object
value
;
private
Object
secondValue
;
private
boolean
noValue
;
private
boolean
singleValue
;
private
boolean
betweenValue
;
private
boolean
listValue
;
private
String
typeHandler
;
public
String
getCondition
()
{
return
condition
;
}
public
Object
getValue
()
{
return
value
;
}
public
Object
getSecondValue
()
{
return
secondValue
;
}
public
boolean
isNoValue
()
{
return
noValue
;
}
public
boolean
isSingleValue
()
{
return
singleValue
;
}
public
boolean
isBetweenValue
()
{
return
betweenValue
;
}
public
boolean
isListValue
()
{
return
listValue
;
}
public
String
getTypeHandler
()
{
return
typeHandler
;
}
protected
Criterion
(
String
condition
)
{
super
();
this
.
condition
=
condition
;
this
.
typeHandler
=
null
;
this
.
noValue
=
true
;
}
protected
Criterion
(
String
condition
,
Object
value
,
String
typeHandler
)
{
super
();
this
.
condition
=
condition
;
this
.
value
=
value
;
this
.
typeHandler
=
typeHandler
;
if
(
value
instanceof
List
<?>)
{
this
.
listValue
=
true
;
}
else
{
this
.
singleValue
=
true
;
}
}
protected
Criterion
(
String
condition
,
Object
value
)
{
this
(
condition
,
value
,
null
);
}
protected
Criterion
(
String
condition
,
Object
value
,
Object
secondValue
,
String
typeHandler
)
{
super
();
this
.
condition
=
condition
;
this
.
value
=
value
;
this
.
secondValue
=
secondValue
;
this
.
typeHandler
=
typeHandler
;
this
.
betweenValue
=
true
;
}
protected
Criterion
(
String
condition
,
Object
value
,
Object
secondValue
)
{
this
(
condition
,
value
,
secondValue
,
null
);
}
}
}
\ No newline at end of file
backend/src/main/java/io/metersphere/base/mapper/TestCaseFileMapper.java
0 → 100644
浏览文件 @
8ce89cf4
package
io.metersphere.base.mapper
;
import
io.metersphere.base.domain.TestCaseFile
;
import
io.metersphere.base.domain.TestCaseFileExample
;
import
java.util.List
;
import
org.apache.ibatis.annotations.Param
;
public
interface
TestCaseFileMapper
{
long
countByExample
(
TestCaseFileExample
example
);
int
deleteByExample
(
TestCaseFileExample
example
);
int
insert
(
TestCaseFile
record
);
int
insertSelective
(
TestCaseFile
record
);
List
<
TestCaseFile
>
selectByExample
(
TestCaseFileExample
example
);
int
updateByExampleSelective
(
@Param
(
"record"
)
TestCaseFile
record
,
@Param
(
"example"
)
TestCaseFileExample
example
);
int
updateByExample
(
@Param
(
"record"
)
TestCaseFile
record
,
@Param
(
"example"
)
TestCaseFileExample
example
);
}
\ No newline at end of file
backend/src/main/java/io/metersphere/base/mapper/TestCaseFileMapper.xml
0 → 100644
浏览文件 @
8ce89cf4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"io.metersphere.base.mapper.TestCaseFileMapper"
>
<resultMap
id=
"BaseResultMap"
type=
"io.metersphere.base.domain.TestCaseFile"
>
<result
column=
"case_id"
jdbcType=
"VARCHAR"
property=
"caseId"
/>
<result
column=
"file_id"
jdbcType=
"VARCHAR"
property=
"fileId"
/>
</resultMap>
<sql
id=
"Example_Where_Clause"
>
<where>
<foreach
collection=
"oredCriteria"
item=
"criteria"
separator=
"or"
>
<if
test=
"criteria.valid"
>
<trim
prefix=
"("
prefixOverrides=
"and"
suffix=
")"
>
<foreach
collection=
"criteria.criteria"
item=
"criterion"
>
<choose>
<when
test=
"criterion.noValue"
>
and ${criterion.condition}
</when>
<when
test=
"criterion.singleValue"
>
and ${criterion.condition} #{criterion.value}
</when>
<when
test=
"criterion.betweenValue"
>
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when
test=
"criterion.listValue"
>
and ${criterion.condition}
<foreach
close=
")"
collection=
"criterion.value"
item=
"listItem"
open=
"("
separator=
","
>
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql
id=
"Update_By_Example_Where_Clause"
>
<where>
<foreach
collection=
"example.oredCriteria"
item=
"criteria"
separator=
"or"
>
<if
test=
"criteria.valid"
>
<trim
prefix=
"("
prefixOverrides=
"and"
suffix=
")"
>
<foreach
collection=
"criteria.criteria"
item=
"criterion"
>
<choose>
<when
test=
"criterion.noValue"
>
and ${criterion.condition}
</when>
<when
test=
"criterion.singleValue"
>
and ${criterion.condition} #{criterion.value}
</when>
<when
test=
"criterion.betweenValue"
>
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when
test=
"criterion.listValue"
>
and ${criterion.condition}
<foreach
close=
")"
collection=
"criterion.value"
item=
"listItem"
open=
"("
separator=
","
>
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql
id=
"Base_Column_List"
>
case_id, file_id
</sql>
<select
id=
"selectByExample"
parameterType=
"io.metersphere.base.domain.TestCaseFileExample"
resultMap=
"BaseResultMap"
>
select
<if
test=
"distinct"
>
distinct
</if>
<include
refid=
"Base_Column_List"
/>
from test_case_file
<if
test=
"_parameter != null"
>
<include
refid=
"Example_Where_Clause"
/>
</if>
<if
test=
"orderByClause != null"
>
order by ${orderByClause}
</if>
</select>
<delete
id=
"deleteByExample"
parameterType=
"io.metersphere.base.domain.TestCaseFileExample"
>
delete from test_case_file
<if
test=
"_parameter != null"
>
<include
refid=
"Example_Where_Clause"
/>
</if>
</delete>
<insert
id=
"insert"
parameterType=
"io.metersphere.base.domain.TestCaseFile"
>
insert into test_case_file (case_id, file_id)
values (#{caseId,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR})
</insert>
<insert
id=
"insertSelective"
parameterType=
"io.metersphere.base.domain.TestCaseFile"
>
insert into test_case_file
<trim
prefix=
"("
suffix=
")"
suffixOverrides=
","
>
<if
test=
"caseId != null"
>
case_id,
</if>
<if
test=
"fileId != null"
>
file_id,
</if>
</trim>
<trim
prefix=
"values ("
suffix=
")"
suffixOverrides=
","
>
<if
test=
"caseId != null"
>
#{caseId,jdbcType=VARCHAR},
</if>
<if
test=
"fileId != null"
>
#{fileId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select
id=
"countByExample"
parameterType=
"io.metersphere.base.domain.TestCaseFileExample"
resultType=
"java.lang.Long"
>
select count(*) from test_case_file
<if
test=
"_parameter != null"
>
<include
refid=
"Example_Where_Clause"
/>
</if>
</select>
<update
id=
"updateByExampleSelective"
parameterType=
"map"
>
update test_case_file
<set>
<if
test=
"record.caseId != null"
>
case_id = #{record.caseId,jdbcType=VARCHAR},
</if>
<if
test=
"record.fileId != null"
>
file_id = #{record.fileId,jdbcType=VARCHAR},
</if>
</set>
<if
test=
"_parameter != null"
>
<include
refid=
"Update_By_Example_Where_Clause"
/>
</if>
</update>
<update
id=
"updateByExample"
parameterType=
"map"
>
update test_case_file
set case_id = #{record.caseId,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR}
<if
test=
"_parameter != null"
>
<include
refid=
"Update_By_Example_Where_Clause"
/>
</if>
</update>
</mapper>
\ No newline at end of file
backend/src/main/java/io/metersphere/commons/constants/FileType.java
浏览文件 @
8ce89cf4
package
io.metersphere.commons.constants
;
public
enum
FileType
{
JMX
(
".jmx"
),
CSV
(
".csv"
),
JSON
(
".json"
);
JMX
(
".jmx"
),
CSV
(
".csv"
),
JSON
(
".json"
)
,
PDF
(
".pdf"
),
JPG
(
".jpg"
),
PNG
(
".png"
)
;
// 保存后缀
private
String
suffix
;
...
...
backend/src/main/java/io/metersphere/service/FileService.java
浏览文件 @
8ce89cf4
...
...
@@ -4,6 +4,7 @@ import io.metersphere.base.domain.*;
import
io.metersphere.base.mapper.FileContentMapper
;
import
io.metersphere.base.mapper.FileMetadataMapper
;
import
io.metersphere.base.mapper.LoadTestFileMapper
;
import
io.metersphere.base.mapper.TestCaseFileMapper
;
import
io.metersphere.commons.constants.FileType
;
import
io.metersphere.commons.exception.MSException
;
import
org.springframework.stereotype.Service
;
...
...
@@ -24,6 +25,8 @@ public class FileService {
private
LoadTestFileMapper
loadTestFileMapper
;
@Resource
private
FileContentMapper
fileContentMapper
;
@Resource
private
TestCaseFileMapper
testCaseFileMapper
;
public
byte
[]
loadFileAsBytes
(
String
id
)
{
FileContent
fileContent
=
fileContentMapper
.
selectByPrimaryKey
(
id
);
...
...
@@ -66,6 +69,19 @@ public class FileService {
loadTestFileMapper
.
deleteByExample
(
example3
);
}
public
void
deleteFileRelatedByIds
(
List
<
String
>
ids
)
{
if
(
CollectionUtils
.
isEmpty
(
ids
))
{
return
;
}
FileMetadataExample
example
=
new
FileMetadataExample
();
example
.
createCriteria
().
andIdIn
(
ids
);
fileMetadataMapper
.
deleteByExample
(
example
);
FileContentExample
example2
=
new
FileContentExample
();
example2
.
createCriteria
().
andFileIdIn
(
ids
);
fileContentMapper
.
deleteByExample
(
example2
);
}
public
FileMetadata
saveFile
(
MultipartFile
file
)
{
final
FileMetadata
fileMetadata
=
new
FileMetadata
();
fileMetadata
.
setId
(
UUID
.
randomUUID
().
toString
());
...
...
@@ -109,4 +125,19 @@ public class FileService {
String
type
=
filename
.
substring
(
s
);
return
FileType
.
valueOf
(
type
.
toUpperCase
());
}
public
List
<
FileMetadata
>
getFileMetadataByCaseId
(
String
caseId
)
{
TestCaseFileExample
testCaseFileExample
=
new
TestCaseFileExample
();
testCaseFileExample
.
createCriteria
().
andCaseIdEqualTo
(
caseId
);
final
List
<
TestCaseFile
>
testCaseFiles
=
testCaseFileMapper
.
selectByExample
(
testCaseFileExample
);
if
(
CollectionUtils
.
isEmpty
(
testCaseFiles
))
{
return
null
;
}
List
<
String
>
fileIds
=
testCaseFiles
.
stream
().
map
(
TestCaseFile:
:
getFileId
).
collect
(
Collectors
.
toList
());
FileMetadataExample
example
=
new
FileMetadataExample
();
example
.
createCriteria
().
andIdIn
(
fileIds
);
return
fileMetadataMapper
.
selectByExample
(
example
);
}
}
\ No newline at end of file
backend/src/main/java/io/metersphere/track/controller/TestCaseController.java
浏览文件 @
8ce89cf4
...
...
@@ -2,6 +2,7 @@ package io.metersphere.track.controller;
import
com.github.pagehelper.Page
;
import
com.github.pagehelper.PageHelper
;
import
io.metersphere.base.domain.FileMetadata
;
import
io.metersphere.base.domain.Project
;
import
io.metersphere.base.domain.TestCase
;
import
io.metersphere.base.domain.TestCaseWithBLOBs
;
...
...
@@ -11,12 +12,18 @@ import io.metersphere.commons.utils.Pager;
import
io.metersphere.commons.utils.SessionUtils
;
import
io.metersphere.excel.domain.ExcelResponse
;
import
io.metersphere.service.CheckOwnerService
;
import
io.metersphere.service.FileService
;
import
io.metersphere.track.dto.TestCaseDTO
;
import
io.metersphere.track.request.testcase.EditTestCaseRequest
;
import
io.metersphere.track.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.track.request.testcase.TestCaseBatchRequest
;
import
io.metersphere.track.request.testplan.FileOperationRequest
;
import
io.metersphere.track.service.TestCaseService
;
import
org.apache.shiro.authz.annotation.Logical
;
import
org.apache.shiro.authz.annotation.RequiresRoles
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.MediaType
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
...
...
@@ -33,6 +40,8 @@ public class TestCaseController {
TestCaseService
testCaseService
;
@Resource
private
CheckOwnerService
checkOwnerService
;
@Resource
private
FileService
fileService
;
@PostMapping
(
"/list/{goPage}/{pageSize}"
)
public
Pager
<
List
<
TestCaseDTO
>>
list
(
@PathVariable
int
goPage
,
@PathVariable
int
pageSize
,
@RequestBody
QueryTestCaseRequest
request
)
{
...
...
@@ -94,16 +103,16 @@ public class TestCaseController {
return
testCaseService
.
getProjectByTestCaseId
(
testCaseId
);
}
@PostMapping
(
"/add"
)
@PostMapping
(
value
=
"/add"
,
consumes
=
{
"multipart/form-data"
}
)
@RequiresRoles
(
value
=
{
RoleConstants
.
TEST_USER
,
RoleConstants
.
TEST_MANAGER
},
logical
=
Logical
.
OR
)
public
void
addTestCase
(
@Request
Body
TestCaseWithBLOBs
testCase
)
{
testCaseService
.
addTestCase
(
testCase
);
public
void
addTestCase
(
@Request
Part
(
"request"
)
TestCaseWithBLOBs
testCase
,
@RequestPart
(
value
=
"file"
)
List
<
MultipartFile
>
files
)
{
testCaseService
.
save
(
testCase
,
files
);
}
@PostMapping
(
"/edit"
)
@PostMapping
(
value
=
"/edit"
,
consumes
=
{
"multipart/form-data"
}
)
@RequiresRoles
(
value
=
{
RoleConstants
.
TEST_USER
,
RoleConstants
.
TEST_MANAGER
},
logical
=
Logical
.
OR
)
public
void
editTestCase
(
@Request
Body
TestCaseWithBLOBs
testCase
)
{
testCaseService
.
edit
TestCase
(
testCase
);
public
void
editTestCase
(
@Request
Part
(
"request"
)
EditTestCaseRequest
request
,
@RequestPart
(
value
=
"file"
)
List
<
MultipartFile
>
files
)
{
testCaseService
.
edit
(
request
,
files
);
}
@PostMapping
(
"/delete/{testCaseId}"
)
...
...
@@ -150,4 +159,18 @@ public class TestCaseController {
testCaseService
.
deleteTestCaseBath
(
request
);
}
@GetMapping
(
"/file/metadata/{caseId}"
)
public
List
<
FileMetadata
>
getFileMetadata
(
@PathVariable
String
caseId
)
{
return
fileService
.
getFileMetadataByCaseId
(
caseId
);
}
@PostMapping
(
"/file/download"
)
public
ResponseEntity
<
byte
[]>
downloadJmx
(
@RequestBody
FileOperationRequest
fileOperationRequest
)
{
byte
[]
bytes
=
fileService
.
loadFileAsBytes
(
fileOperationRequest
.
getId
());
return
ResponseEntity
.
ok
()
.
contentType
(
MediaType
.
parseMediaType
(
"application/octet-stream"
))
.
header
(
HttpHeaders
.
CONTENT_DISPOSITION
,
"attachment; filename=\""
+
fileOperationRequest
.
getName
()
+
"\""
)
.
body
(
bytes
);
}
}
backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java
0 → 100644
浏览文件 @
8ce89cf4
package
io.metersphere.track.request.testcase
;
import
io.metersphere.base.domain.FileMetadata
;
import
io.metersphere.base.domain.TestCaseWithBLOBs
;
import
lombok.Getter
;
import
lombok.Setter
;
import
java.util.List
;
@Getter
@Setter
public
class
EditTestCaseRequest
extends
TestCaseWithBLOBs
{
private
List
<
FileMetadata
>
updatedFileList
;
}
backend/src/main/java/io/metersphere/track/service/TestCaseService.java
浏览文件 @
8ce89cf4
...
...
@@ -26,10 +26,13 @@ import io.metersphere.excel.listener.EasyExcelListener;
import
io.metersphere.excel.listener.TestCaseDataListener
;
import
io.metersphere.excel.utils.EasyExcelExporter
;
import
io.metersphere.i18n.Translator
;
import
io.metersphere.service.FileService
;
import
io.metersphere.track.dto.TestCaseDTO
;
import
io.metersphere.track.request.testcase.EditTestCaseRequest
;
import
io.metersphere.track.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.track.request.testcase.TestCaseBatchRequest
;
import
io.metersphere.xmind.XmindCaseParser
;
import
org.apache.commons.collections4.ListUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.ibatis.session.ExecutorType
;
import
org.apache.ibatis.session.SqlSession
;
...
...
@@ -83,8 +86,12 @@ public class TestCaseService {
TestCaseReviewTestCaseMapper
testCaseReviewTestCaseMapper
;
@Resource
TestCaseCommentService
testCaseCommentService
;
@Resource
FileService
fileService
;
@Resource
TestCaseFileMapper
testCaseFileMapper
;
p
ublic
void
addTestCase
(
TestCaseWithBLOBs
testCase
)
{
p
rivate
TestCaseWithBLOBs
addTestCase
(
TestCaseWithBLOBs
testCase
)
{
testCase
.
setName
(
testCase
.
getName
());
checkTestCaseExist
(
testCase
);
testCase
.
setId
(
UUID
.
randomUUID
().
toString
());
...
...
@@ -93,6 +100,7 @@ public class TestCaseService {
testCase
.
setNum
(
getNextNum
(
testCase
.
getProjectId
()));
testCase
.
setReviewStatus
(
TestCaseReviewStatus
.
Prepare
.
name
());
testCaseMapper
.
insert
(
testCase
);
return
testCase
;
}
public
List
<
TestCase
>
getTestCaseByNodeId
(
List
<
String
>
nodeIds
)
{
...
...
@@ -596,4 +604,55 @@ public class TestCaseService {
return
false
;
}
public
String
save
(
TestCaseWithBLOBs
testCase
,
List
<
MultipartFile
>
files
)
{
if
(
files
==
null
)
{
throw
new
IllegalArgumentException
(
Translator
.
get
(
"file_cannot_be_null"
));
}
final
TestCaseWithBLOBs
testCaseWithBLOBs
=
addTestCase
(
testCase
);
files
.
forEach
(
file
->
{
final
FileMetadata
fileMetadata
=
fileService
.
saveFile
(
file
);
TestCaseFile
testCaseFile
=
new
TestCaseFile
();
testCaseFile
.
setCaseId
(
testCaseWithBLOBs
.
getId
());
testCaseFile
.
setFileId
(
fileMetadata
.
getId
());
testCaseFileMapper
.
insert
(
testCaseFile
);
});
return
testCaseWithBLOBs
.
getId
();
}
public
String
edit
(
EditTestCaseRequest
request
,
List
<
MultipartFile
>
files
)
{
TestCaseWithBLOBs
testCaseWithBLOBs
=
testCaseMapper
.
selectByPrimaryKey
(
request
.
getId
());
if
(
testCaseWithBLOBs
==
null
)
{
MSException
.
throwException
(
Translator
.
get
(
"edit_load_test_not_found"
)
+
request
.
getId
());
}
// 新选择了一个文件,删除原来的文件
List
<
FileMetadata
>
updatedFiles
=
request
.
getUpdatedFileList
();
List
<
FileMetadata
>
originFiles
=
fileService
.
getFileMetadataByCaseId
(
request
.
getId
());
List
<
String
>
updatedFileIds
=
updatedFiles
.
stream
().
map
(
FileMetadata:
:
getId
).
collect
(
Collectors
.
toList
());
List
<
String
>
originFileIds
=
originFiles
.
stream
().
map
(
FileMetadata:
:
getId
).
collect
(
Collectors
.
toList
());
// 相减
List
<
String
>
deleteFileIds
=
ListUtils
.
subtract
(
originFileIds
,
updatedFileIds
);
fileService
.
deleteFileRelatedByIds
(
deleteFileIds
);
if
(!
CollectionUtils
.
isEmpty
(
deleteFileIds
))
{
TestCaseFileExample
testCaseFileExample
=
new
TestCaseFileExample
();
testCaseFileExample
.
createCriteria
().
andFileIdIn
(
deleteFileIds
);
testCaseFileMapper
.
deleteByExample
(
testCaseFileExample
);
}
if
(
files
!=
null
)
{
files
.
forEach
(
file
->
{
final
FileMetadata
fileMetadata
=
fileService
.
saveFile
(
file
);
TestCaseFile
testCaseFile
=
new
TestCaseFile
();
testCaseFile
.
setFileId
(
fileMetadata
.
getId
());
testCaseFile
.
setCaseId
(
request
.
getId
());
testCaseFileMapper
.
insert
(
testCaseFile
);
});
}
editTestCase
(
request
);
return
request
.
getId
();
}
}
xpack
@
ee74568b
比较
cf6b0652
...
ee74568b
Subproject commit
cf6b06526324326a563d933e07118fac014a63b4
Subproject commit
ee74568be0beba46da19616f5832e83f9164c688
backend/src/main/resources/db/migration/V30__test_case_file.sql
0 → 100644
浏览文件 @
8ce89cf4
create
table
if
not
exists
test_case_file
(
case_id
varchar
(
64
)
null
,
file_id
varchar
(
64
)
null
,
constraint
test_case_file_unique_key
unique
(
case_id
,
file_id
)
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8
;
frontend/src/business/components/track/case/components/TestCaseEdit.vue
浏览文件 @
8ce89cf4
...
...
@@ -209,6 +209,59 @@
</el-form-item>
</el-col>
</el-row>
<el-row
style=
"margin-top: 15px;margin-bottom: 10px"
>
<el-col
:offset=
"2"
:span=
"20"
>
附件:
<el-upload
action=
""
:show-file-list=
"false"
:before-upload=
"beforeUpload"
:http-request=
"handleUpload"
:on-exceed=
"handleExceed"
multiple
:limit=
"3"
:file-list=
"fileList"
>
<el-button
icon=
"el-icon-plus"
size=
"mini"
></el-button>
<!-- <span slot="tip" class="el-upload__tip"></span>-->
</el-upload>
</el-col>
</el-row>
<el-row
type=
"flex"
justify=
"center"
>
<el-col
:span=
"20"
>
<el-table
class=
"basic-config"
:data=
"tableData"
>
<el-table-column
prop=
"name"
:label=
"$t('load_test.file_name')"
>
</el-table-column>
<el-table-column
prop=
"size"
:label=
"$t('load_test.file_size')"
>
</el-table-column>
<el-table-column
prop=
"type"
:label=
"$t('load_test.file_type')"
>
</el-table-column>
<el-table-column
:label=
"$t('load_test.last_modify_time')"
>
<
template
v-slot:default=
"scope"
>
<i
class=
"el-icon-time"
/>
<span
class=
"last-modified"
>
{{
scope
.
row
.
updateTime
|
timestampFormatDate
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('commons.operating')"
>
<
template
v-slot:default=
"scope"
>
<el-button
@
click=
"handleDownload(scope.row)"
:disabled=
"!scope.row.id || readOnly"
type=
"primary"
icon=
"el-icon-download"
size=
"mini"
circle
/>
<el-button
:disabled=
"readOnly"
@
click=
"handleDelete(scope.row, scope.$index)"
type=
"danger"
icon=
"el-icon-delete"
size=
"mini"
circle
/>
</
template
>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-form>
<
template
v-slot:footer
>
...
...
@@ -234,6 +287,7 @@ import {TokenKey, WORKSPACE_ID} from '../../../../../common/js/constants';
import
MsDialogFooter
from
'
../../../common/components/MsDialogFooter
'
import
{
listenGoBack
,
removeGoBackListener
}
from
"
../../../../../common/js/utils
"
;
import
{
LIST_CHANGE
,
TrackEvent
}
from
"
@/business/components/common/head/ListEvent
"
;
import
{
Message
}
from
"
element-ui
"
;
export
default
{
name
:
"
TestCaseEdit
"
,
...
...
@@ -263,6 +317,9 @@ export default {
maintainerOptions
:
[],
testOptions
:
[],
workspaceId
:
''
,
fileList
:
[],
tableData
:
[],
uploadList
:
[],
rules
:
{
name
:
[
{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_name
'
),
trigger
:
'
blur
'
},
...
...
@@ -339,6 +396,7 @@ export default {
tmp
.
steps
=
JSON
.
parse
(
testCase
.
steps
);
Object
.
assign
(
this
.
form
,
tmp
);
this
.
form
.
module
=
testCase
.
nodeId
;
this
.
getFileMetaData
(
testCase
);
}
else
{
if
(
this
.
selectNode
.
data
)
{
this
.
form
.
module
=
this
.
selectNode
.
data
.
id
;
...
...
@@ -358,6 +416,24 @@ export default {
this
.
reload
();
this
.
dialogFormVisible
=
true
;
},
getFileMetaData
(
testCase
)
{
this
.
fileList
=
[];
this
.
tableData
=
[];
this
.
uploadList
=
[];
this
.
result
=
this
.
$get
(
"
test/case/file/metadata/
"
+
testCase
.
id
,
response
=>
{
let
files
=
response
.
data
;
if
(
!
files
)
{
return
;
}
// deep copy
this
.
fileList
=
JSON
.
parse
(
JSON
.
stringify
(
files
));
this
.
tableData
=
JSON
.
parse
(
JSON
.
stringify
(
files
));
this
.
tableData
.
map
(
f
=>
{
f
.
size
=
f
.
size
+
'
Bytes
'
;
});
})
},
handleAddStep
(
index
,
data
)
{
let
step
=
{};
step
.
num
=
data
.
num
+
1
;
...
...
@@ -400,7 +476,8 @@ export default {
if
(
valid
)
{
let
param
=
this
.
buildParam
();
if
(
this
.
validate
(
param
))
{
this
.
result
=
this
.
$post
(
'
/test/case/
'
+
this
.
operationType
,
param
,
()
=>
{
let
option
=
this
.
getOption
(
param
);
this
.
result
=
this
.
$request
(
option
,
()
=>
{
this
.
$success
(
this
.
$t
(
'
commons.save_success
'
));
if
(
this
.
operationType
==
'
add
'
&&
this
.
isCreateContinue
)
{
this
.
form
.
name
=
''
;
...
...
@@ -411,6 +488,7 @@ export default {
result
:
''
}];
this
.
form
.
remark
=
''
;
this
.
form
.
uploadList
=
[];
this
.
$emit
(
"
refresh
"
);
return
;
}
...
...
@@ -444,6 +522,32 @@ export default {
}
return
param
;
},
getOption
(
param
)
{
let
formData
=
new
FormData
();
let
url
=
'
/test/case/
'
+
this
.
operationType
;
this
.
uploadList
.
forEach
(
f
=>
{
formData
.
append
(
"
file
"
,
f
);
});
param
.
updatedFileList
=
this
.
fileList
;
let
requestJson
=
JSON
.
stringify
(
param
,
function
(
key
,
value
)
{
return
key
===
"
file
"
?
undefined
:
value
});
formData
.
append
(
'
request
'
,
new
Blob
([
requestJson
],
{
type
:
"
application/json
"
}));
return
{
method
:
'
POST
'
,
url
:
url
,
data
:
formData
,
headers
:
{
'
Content-Type
'
:
undefined
}
};
},
validate
(
param
)
{
for
(
let
i
=
0
;
i
<
param
.
steps
.
length
;
i
++
)
{
if
((
param
.
steps
[
i
].
desc
&&
param
.
steps
[
i
].
desc
.
length
>
300
)
||
...
...
@@ -527,7 +631,88 @@ export default {
return
true
;
});
}
}
},
handleExceed
()
{
this
.
$error
(
this
.
$t
(
'
load_test.file_size_limit
'
));
},
beforeUpload
(
file
)
{
if
(
!
this
.
fileValidator
(
file
))
{
/// todo: 显示错误信息
return
false
;
}
if
(
this
.
tableData
.
filter
(
f
=>
f
.
name
===
file
.
name
).
length
>
0
)
{
this
.
$error
(
this
.
$t
(
'
load_test.delete_file
'
));
return
false
;
}
let
type
=
file
.
name
.
substring
(
file
.
name
.
lastIndexOf
(
"
.
"
)
+
1
);
this
.
tableData
.
push
({
name
:
file
.
name
,
size
:
file
.
size
+
'
Bytes
'
,
/// todo: 按照大小显示Byte、KB、MB等
type
:
type
.
toUpperCase
(),
updateTime
:
file
.
lastModified
,
});
return
true
;
},
handleUpload
(
uploadResources
)
{
this
.
uploadList
.
push
(
uploadResources
.
file
);
},
handleDownload
(
file
)
{
let
data
=
{
name
:
file
.
name
,
id
:
file
.
id
,
};
let
config
=
{
url
:
'
/test/case/file/download
'
,
method
:
'
post
'
,
data
:
data
,
responseType
:
'
blob
'
};
this
.
result
=
this
.
$request
(
config
).
then
(
response
=>
{
const
content
=
response
.
data
;
const
blob
=
new
Blob
([
content
]);
if
(
"
download
"
in
document
.
createElement
(
"
a
"
))
{
// 非IE下载
// chrome/firefox
let
aTag
=
document
.
createElement
(
'
a
'
);
aTag
.
download
=
file
.
name
;
aTag
.
href
=
URL
.
createObjectURL
(
blob
);
aTag
.
click
();
URL
.
revokeObjectURL
(
aTag
.
href
)
}
else
{
// IE10+下载
navigator
.
msSaveBlob
(
blob
,
this
.
filename
)
}
}).
catch
(
e
=>
{
Message
.
error
({
message
:
e
.
message
,
showClose
:
true
});
});
},
handleDelete
(
file
,
index
)
{
this
.
$alert
(
this
.
$t
(
'
load_test.delete_file_confirm
'
)
+
file
.
name
+
"
?
"
,
''
,
{
confirmButtonText
:
this
.
$t
(
'
commons.confirm
'
),
callback
:
(
action
)
=>
{
if
(
action
===
'
confirm
'
)
{
this
.
_handleDelete
(
file
,
index
);
}
}
});
},
_handleDelete
(
file
,
index
)
{
this
.
fileList
.
splice
(
index
,
1
);
this
.
tableData
.
splice
(
index
,
1
);
let
i
=
this
.
uploadList
.
findIndex
(
upLoadFile
=>
upLoadFile
.
name
===
file
.
name
);
if
(
i
>
-
1
)
{
this
.
uploadList
.
splice
(
i
,
1
);
}
},
fileValidator
(
file
)
{
/// todo: 是否需要对文件内容和大小做限制
return
file
.
size
>
0
;
},
}
}
</
script
>
...
...
frontend/src/business/components/track/case/components/TestCaseList.vue
浏览文件 @
8ce89cf4
...
...
@@ -66,7 +66,7 @@
trigger=
"hover"
>
<test-case-detail
:test-case=
"scope.row"
/>
<
p
slot=
"reference"
>
{{
scope
.
row
.
name
}}
</p
>
<
span
slot=
"reference"
>
{{
scope
.
row
.
name
}}
</span
>
</el-popover>
</
template
>
</el-table-column>
...
...
xpack
@
cc38137a
比较
06d935cd
...
cc38137a
Subproject commit
06d935cd1d22ab36f09763745c2aff8ad3fb08c1
Subproject commit
cc38137a69a0f20fadece9c0f9f50a9468c4ace9
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录