feat(接口自动化): 历史操作日志

This commit is contained in:
fit2-zhao 2021-04-28 11:14:29 +08:00 committed by fit2-zhao
parent 49dae56d76
commit 4062f36cc2
32 changed files with 2456 additions and 7 deletions

View File

@ -423,6 +423,11 @@
<artifactId>json-path</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.flipkart.zjsonpatch</groupId>
<artifactId>zjsonpatch</artifactId>
<version>0.4.11</version>
</dependency>
</dependencies>

View File

@ -19,6 +19,7 @@ import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.domain.Schedule;
import io.metersphere.commons.constants.OperLogConstants;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.json.JSONSchemaGenerator;
import io.metersphere.commons.utils.CronUtils;
@ -26,6 +27,7 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.ScheduleRequest;
import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.service.CheckPermissionService;
import io.metersphere.service.ScheduleService;
import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest;
@ -94,6 +96,7 @@ public class ApiDefinitionController {
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.CREATE, title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = ApiDefinitionService.class)
public void create(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
checkPermissionService.checkProjectOwner(request.getProjectId());
apiDefinitionService.create(request, bodyFiles);
@ -102,18 +105,22 @@ public class ApiDefinitionController {
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
public ApiDefinitionWithBLOBs update(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
@MsAuditLog(module = "api_definition", type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#request.id)", title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = ApiDefinitionService.class)
public void update(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
checkPermissionService.checkProjectOwner(request.getProjectId());
return apiDefinitionService.update(request, bodyFiles);
}
@GetMapping("/delete/{id}")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = ApiDefinitionService.class)
public void delete(@PathVariable String id) {
apiDefinitionService.delete(id);
}
@PostMapping("/deleteBatch")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.BATCH_DEL, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = ApiDefinitionService.class)
public void deleteBatch(@RequestBody List<String> ids) {
apiDefinitionService.deleteBatch(ids);
}
@ -128,23 +135,27 @@ public class ApiDefinitionController {
}
@PostMapping("/deleteBatchByParams")
@MsAuditLog(module = "api_definition", type = OperLogConstants.BATCH_DEL, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = ApiDefinitionService.class)
public void deleteBatchByParams(@RequestBody ApiBatchRequest request) {
apiDefinitionService.deleteByParams(request);
}
@PostMapping("/removeToGc")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.GC, beforeEvent = "#msClass.getLogDetails(#ids)", msClass = ApiDefinitionService.class)
public void removeToGc(@RequestBody List<String> ids) {
apiDefinitionService.removeToGc(ids);
}
@PostMapping("/removeToGcByParams")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.BATCH_GC, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = ApiDefinitionService.class)
public void removeToGcByParams(@RequestBody ApiBatchRequest request) {
apiDefinitionService.removeToGcByParams(request);
}
@PostMapping("/reduction")
@MsAuditLog(module = "api_definition", type = OperLogConstants.RESTORE, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = ApiDefinitionService.class)
public void reduction(@RequestBody ApiBatchRequest request) {
apiDefinitionService.reduction(request);
}
@ -155,11 +166,13 @@ public class ApiDefinitionController {
}
@PostMapping(value = "/run/debug", consumes = {"multipart/form-data"})
@MsAuditLog(module = "api_definition", type = OperLogConstants.DEBUG, title = "#request.name", project = "#request.projectId")
public String runDebug(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiDefinitionService.run(request, bodyFiles);
}
@PostMapping(value = "/run", consumes = {"multipart/form-data"})
@MsAuditLog(module = "api_definition", type = OperLogConstants.EXECUTE, title = "#request.name", project = "#request.projectId")
public String run(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
request.setReportId(null);
return apiDefinitionService.run(request, bodyFiles);
@ -182,18 +195,21 @@ public class ApiDefinitionController {
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.IMPORT, title = "#request.name", project = "#request.projectId")
public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiDefinitionService.apiTestImport(file, request);
}
@PostMapping(value = "/export/{type}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.EXPORT, title = "#request.name", project = "#request.projectId")
public ApiExportResult export(@RequestBody ApiBatchRequest request, @PathVariable String type) {
return apiDefinitionService.export(request, type);
}
//定时任务创建
@PostMapping(value = "/schedule/create")
@MsAuditLog(module = "api_definition", type = OperLogConstants.CREATE, title = "#request.scheduleFrom", project = "#request.projectId")
public void createSchedule(@RequestBody ScheduleRequest request) throws MalformedURLException {
apiDefinitionService.createSchedule(request);
}
@ -252,6 +268,7 @@ public class ApiDefinitionController {
@PostMapping("/batch/editByParams")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
@MsAuditLog(module = "api_definition", type = OperLogConstants.BATCH_UPDATE, beforeEvent = "#msClass.getLogDetails(#request)", content = "#msClass.getLogDetails(#request)", msClass = ApiDefinitionService.class)
public void editByParams(@RequestBody ApiBatchRequest request) {
apiDefinitionService.editApiByParam(request);
}

View File

@ -11,6 +11,7 @@ import java.util.List;
@Setter
public class ApiBatchRequest extends ApiDefinitionWithBLOBs {
private List<String> ids;
private String name;
private List<OrderRequest> orders;
private String projectId;
private String moduleId;

View File

@ -33,6 +33,10 @@ import io.metersphere.controller.request.ScheduleRequest;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.SwaggerUrlImportJob;
import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.definition.DefinitionReference;
import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService;
import io.metersphere.service.SystemParameterService;
@ -671,6 +675,10 @@ public class ApiDefinitionService {
MSException.throwException(Translator.get("parse_data_error"));
}
importApi(request, apiImport);
if (CollectionUtils.isNotEmpty(apiImport.getData())) {
List<String> names = apiImport.getData().stream().map(ApiDefinitionWithBLOBs::getName).collect(Collectors.toList());
request.setName(String.join(",", names));
}
return apiImport;
}
@ -977,6 +985,10 @@ public class ApiDefinitionService {
System.out.println(apiDefinitionMapper.selectByExampleWithBLOBs(example));
apiExportResult = swagger3Parser.swagger3Export(apiDefinitionMapper.selectByExampleWithBLOBs(example));
}
if (CollectionUtils.isNotEmpty(((MsApiExportResult) apiExportResult).getData())) {
List<String> names = ((MsApiExportResult) apiExportResult).getData().stream().map(ApiDefinitionWithBLOBs::getName).collect(Collectors.toList());
request.setName(String.join(",", names));
}
return apiExportResult;
}
@ -1058,4 +1070,57 @@ public class ApiDefinitionService {
}
}
}
public String getLogDetails(String id) {
ApiDefinitionWithBLOBs bloBs = apiDefinitionMapper.selectByPrimaryKey(id);
List<DetailColumn> columns = ReflexObjectUtil.getColumns(bloBs, DefinitionReference.definitionColumns);
OperatingLogDetails details = new OperatingLogDetails(id, bloBs.getProjectId(), columns);
return JSON.toJSONString(details);
}
public String getLogDetails(List<String> ids) {
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
List<ApiDefinition> definitions = apiDefinitionMapper.selectByExample(example);
List<String> names = definitions.stream().map(ApiDefinition::getName).collect(Collectors.toList());
OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(ids), definitions.get(0).getProjectId(), String.join(",", names), new LinkedList<>());
return JSON.toJSONString(details);
}
return null;
}
public String getLogDetails(ApiBatchRequest request) {
request.getCondition();
if (CollectionUtils.isNotEmpty(request.getIds())) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(request.getIds());
List<ApiDefinition> definitions = apiDefinitionMapper.selectByExample(example);
List<DetailColumn> columns = new LinkedList<>();
if (StringUtils.isNotEmpty(request.getMethod())) {
columns.clear();
definitions.forEach(item -> {
DetailColumn column = new DetailColumn(DefinitionReference.definitionColumns.get("method"), "method", item.getMethod(), null);
columns.add(column);
});
} else if (StringUtils.isNotEmpty(request.getStatus())) {
columns.clear();
definitions.forEach(item -> {
DetailColumn column = new DetailColumn(DefinitionReference.definitionColumns.get("status"), "status", item.getStatus(), null);
columns.add(column);
});
} else if (StringUtils.isNotEmpty(request.getUserId())) {
columns.clear();
definitions.forEach(item -> {
DetailColumn column = new DetailColumn(DefinitionReference.definitionColumns.get("userId"), "userId", item.getUserId(), null);
columns.add(column);
});
}
List<String> names = definitions.stream().map(ApiDefinition::getName).collect(Collectors.toList());
OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(request.getIds()), request.getProjectId(),String.join(",",names), columns);
return JSON.toJSONString(details);
}
return null;
}
}

View File

@ -0,0 +1,29 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
@Data
public class OperatingLog implements Serializable {
private String id;
private String projectId;
private String operMethod;
private String operUser;
private String sourceId;
private String operType;
private String operModule;
private String operTitle;
private Long operTime;
private String operContent;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,820 @@
package io.metersphere.base.domain;
import java.util.ArrayList;
import java.util.List;
public class OperatingLogExample {
protected String orderByClause;
protected boolean distinct;
protected List<Criteria> oredCriteria;
public OperatingLogExample() {
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 andIdIsNull() {
addCriterion("id is null");
return (Criteria) this;
}
public Criteria andIdIsNotNull() {
addCriterion("id is not null");
return (Criteria) this;
}
public Criteria andIdEqualTo(String value) {
addCriterion("id =", value, "id");
return (Criteria) this;
}
public Criteria andIdNotEqualTo(String value) {
addCriterion("id <>", value, "id");
return (Criteria) this;
}
public Criteria andIdGreaterThan(String value) {
addCriterion("id >", value, "id");
return (Criteria) this;
}
public Criteria andIdGreaterThanOrEqualTo(String value) {
addCriterion("id >=", value, "id");
return (Criteria) this;
}
public Criteria andIdLessThan(String value) {
addCriterion("id <", value, "id");
return (Criteria) this;
}
public Criteria andIdLessThanOrEqualTo(String value) {
addCriterion("id <=", value, "id");
return (Criteria) this;
}
public Criteria andIdLike(String value) {
addCriterion("id like", value, "id");
return (Criteria) this;
}
public Criteria andIdNotLike(String value) {
addCriterion("id not like", value, "id");
return (Criteria) this;
}
public Criteria andIdIn(List<String> values) {
addCriterion("id in", values, "id");
return (Criteria) this;
}
public Criteria andIdNotIn(List<String> values) {
addCriterion("id not in", values, "id");
return (Criteria) this;
}
public Criteria andIdBetween(String value1, String value2) {
addCriterion("id between", value1, value2, "id");
return (Criteria) this;
}
public Criteria andIdNotBetween(String value1, String value2) {
addCriterion("id not between", value1, value2, "id");
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;
}
public Criteria andProjectIdIsNotNull() {
addCriterion("project_id is not null");
return (Criteria) this;
}
public Criteria andProjectIdEqualTo(String value) {
addCriterion("project_id =", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotEqualTo(String value) {
addCriterion("project_id <>", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThan(String value) {
addCriterion("project_id >", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("project_id >=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThan(String value) {
addCriterion("project_id <", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThanOrEqualTo(String value) {
addCriterion("project_id <=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLike(String value) {
addCriterion("project_id like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotLike(String value) {
addCriterion("project_id not like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdIn(List<String> values) {
addCriterion("project_id in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotIn(List<String> values) {
addCriterion("project_id not in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdBetween(String value1, String value2) {
addCriterion("project_id between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotBetween(String value1, String value2) {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andOperMethodIsNull() {
addCriterion("oper_method is null");
return (Criteria) this;
}
public Criteria andOperMethodIsNotNull() {
addCriterion("oper_method is not null");
return (Criteria) this;
}
public Criteria andOperMethodEqualTo(String value) {
addCriterion("oper_method =", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodNotEqualTo(String value) {
addCriterion("oper_method <>", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodGreaterThan(String value) {
addCriterion("oper_method >", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodGreaterThanOrEqualTo(String value) {
addCriterion("oper_method >=", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodLessThan(String value) {
addCriterion("oper_method <", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodLessThanOrEqualTo(String value) {
addCriterion("oper_method <=", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodLike(String value) {
addCriterion("oper_method like", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodNotLike(String value) {
addCriterion("oper_method not like", value, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodIn(List<String> values) {
addCriterion("oper_method in", values, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodNotIn(List<String> values) {
addCriterion("oper_method not in", values, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodBetween(String value1, String value2) {
addCriterion("oper_method between", value1, value2, "operMethod");
return (Criteria) this;
}
public Criteria andOperMethodNotBetween(String value1, String value2) {
addCriterion("oper_method not between", value1, value2, "operMethod");
return (Criteria) this;
}
public Criteria andOperUserIsNull() {
addCriterion("oper_user is null");
return (Criteria) this;
}
public Criteria andOperUserIsNotNull() {
addCriterion("oper_user is not null");
return (Criteria) this;
}
public Criteria andOperUserEqualTo(String value) {
addCriterion("oper_user =", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserNotEqualTo(String value) {
addCriterion("oper_user <>", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserGreaterThan(String value) {
addCriterion("oper_user >", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserGreaterThanOrEqualTo(String value) {
addCriterion("oper_user >=", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserLessThan(String value) {
addCriterion("oper_user <", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserLessThanOrEqualTo(String value) {
addCriterion("oper_user <=", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserLike(String value) {
addCriterion("oper_user like", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserNotLike(String value) {
addCriterion("oper_user not like", value, "operUser");
return (Criteria) this;
}
public Criteria andOperUserIn(List<String> values) {
addCriterion("oper_user in", values, "operUser");
return (Criteria) this;
}
public Criteria andOperUserNotIn(List<String> values) {
addCriterion("oper_user not in", values, "operUser");
return (Criteria) this;
}
public Criteria andOperUserBetween(String value1, String value2) {
addCriterion("oper_user between", value1, value2, "operUser");
return (Criteria) this;
}
public Criteria andOperUserNotBetween(String value1, String value2) {
addCriterion("oper_user not between", value1, value2, "operUser");
return (Criteria) this;
}
public Criteria andSourceIdIsNull() {
addCriterion("source_id is null");
return (Criteria) this;
}
public Criteria andSourceIdIsNotNull() {
addCriterion("source_id is not null");
return (Criteria) this;
}
public Criteria andSourceIdEqualTo(String value) {
addCriterion("source_id =", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdNotEqualTo(String value) {
addCriterion("source_id <>", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdGreaterThan(String value) {
addCriterion("source_id >", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdGreaterThanOrEqualTo(String value) {
addCriterion("source_id >=", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdLessThan(String value) {
addCriterion("source_id <", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdLessThanOrEqualTo(String value) {
addCriterion("source_id <=", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdLike(String value) {
addCriterion("source_id like", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdNotLike(String value) {
addCriterion("source_id not like", value, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdIn(List<String> values) {
addCriterion("source_id in", values, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdNotIn(List<String> values) {
addCriterion("source_id not in", values, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdBetween(String value1, String value2) {
addCriterion("source_id between", value1, value2, "sourceId");
return (Criteria) this;
}
public Criteria andSourceIdNotBetween(String value1, String value2) {
addCriterion("source_id not between", value1, value2, "sourceId");
return (Criteria) this;
}
public Criteria andOperTypeIsNull() {
addCriterion("oper_type is null");
return (Criteria) this;
}
public Criteria andOperTypeIsNotNull() {
addCriterion("oper_type is not null");
return (Criteria) this;
}
public Criteria andOperTypeEqualTo(String value) {
addCriterion("oper_type =", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeNotEqualTo(String value) {
addCriterion("oper_type <>", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeGreaterThan(String value) {
addCriterion("oper_type >", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeGreaterThanOrEqualTo(String value) {
addCriterion("oper_type >=", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeLessThan(String value) {
addCriterion("oper_type <", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeLessThanOrEqualTo(String value) {
addCriterion("oper_type <=", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeLike(String value) {
addCriterion("oper_type like", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeNotLike(String value) {
addCriterion("oper_type not like", value, "operType");
return (Criteria) this;
}
public Criteria andOperTypeIn(List<String> values) {
addCriterion("oper_type in", values, "operType");
return (Criteria) this;
}
public Criteria andOperTypeNotIn(List<String> values) {
addCriterion("oper_type not in", values, "operType");
return (Criteria) this;
}
public Criteria andOperTypeBetween(String value1, String value2) {
addCriterion("oper_type between", value1, value2, "operType");
return (Criteria) this;
}
public Criteria andOperTypeNotBetween(String value1, String value2) {
addCriterion("oper_type not between", value1, value2, "operType");
return (Criteria) this;
}
public Criteria andOperModuleIsNull() {
addCriterion("oper_module is null");
return (Criteria) this;
}
public Criteria andOperModuleIsNotNull() {
addCriterion("oper_module is not null");
return (Criteria) this;
}
public Criteria andOperModuleEqualTo(String value) {
addCriterion("oper_module =", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleNotEqualTo(String value) {
addCriterion("oper_module <>", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleGreaterThan(String value) {
addCriterion("oper_module >", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleGreaterThanOrEqualTo(String value) {
addCriterion("oper_module >=", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleLessThan(String value) {
addCriterion("oper_module <", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleLessThanOrEqualTo(String value) {
addCriterion("oper_module <=", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleLike(String value) {
addCriterion("oper_module like", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleNotLike(String value) {
addCriterion("oper_module not like", value, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleIn(List<String> values) {
addCriterion("oper_module in", values, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleNotIn(List<String> values) {
addCriterion("oper_module not in", values, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleBetween(String value1, String value2) {
addCriterion("oper_module between", value1, value2, "operModule");
return (Criteria) this;
}
public Criteria andOperModuleNotBetween(String value1, String value2) {
addCriterion("oper_module not between", value1, value2, "operModule");
return (Criteria) this;
}
public Criteria andOperTitleIsNull() {
addCriterion("oper_title is null");
return (Criteria) this;
}
public Criteria andOperTitleIsNotNull() {
addCriterion("oper_title is not null");
return (Criteria) this;
}
public Criteria andOperTitleEqualTo(String value) {
addCriterion("oper_title =", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleNotEqualTo(String value) {
addCriterion("oper_title <>", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleGreaterThan(String value) {
addCriterion("oper_title >", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleGreaterThanOrEqualTo(String value) {
addCriterion("oper_title >=", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleLessThan(String value) {
addCriterion("oper_title <", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleLessThanOrEqualTo(String value) {
addCriterion("oper_title <=", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleLike(String value) {
addCriterion("oper_title like", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleNotLike(String value) {
addCriterion("oper_title not like", value, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleIn(List<String> values) {
addCriterion("oper_title in", values, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleNotIn(List<String> values) {
addCriterion("oper_title not in", values, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleBetween(String value1, String value2) {
addCriterion("oper_title between", value1, value2, "operTitle");
return (Criteria) this;
}
public Criteria andOperTitleNotBetween(String value1, String value2) {
addCriterion("oper_title not between", value1, value2, "operTitle");
return (Criteria) this;
}
public Criteria andOperTimeIsNull() {
addCriterion("oper_time is null");
return (Criteria) this;
}
public Criteria andOperTimeIsNotNull() {
addCriterion("oper_time is not null");
return (Criteria) this;
}
public Criteria andOperTimeEqualTo(Long value) {
addCriterion("oper_time =", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeNotEqualTo(Long value) {
addCriterion("oper_time <>", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeGreaterThan(Long value) {
addCriterion("oper_time >", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeGreaterThanOrEqualTo(Long value) {
addCriterion("oper_time >=", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeLessThan(Long value) {
addCriterion("oper_time <", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeLessThanOrEqualTo(Long value) {
addCriterion("oper_time <=", value, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeIn(List<Long> values) {
addCriterion("oper_time in", values, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeNotIn(List<Long> values) {
addCriterion("oper_time not in", values, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeBetween(Long value1, Long value2) {
addCriterion("oper_time between", value1, value2, "operTime");
return (Criteria) this;
}
public Criteria andOperTimeNotBetween(Long value1, Long value2) {
addCriterion("oper_time not between", value1, value2, "operTime");
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);
}
}
}

View File

@ -0,0 +1,36 @@
package io.metersphere.base.mapper;
import io.metersphere.base.domain.OperatingLog;
import io.metersphere.base.domain.OperatingLogExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface OperatingLogMapper {
long countByExample(OperatingLogExample example);
int deleteByExample(OperatingLogExample example);
int deleteByPrimaryKey(String id);
int insert(OperatingLog record);
int insertSelective(OperatingLog record);
List<OperatingLog> selectByExampleWithBLOBs(OperatingLogExample example);
List<OperatingLog> selectByExample(OperatingLogExample example);
OperatingLog selectByPrimaryKey(String id);
int updateByExampleSelective(@Param("record") OperatingLog record, @Param("example") OperatingLogExample example);
int updateByExampleWithBLOBs(@Param("record") OperatingLog record, @Param("example") OperatingLogExample example);
int updateByExample(@Param("record") OperatingLog record, @Param("example") OperatingLogExample example);
int updateByPrimaryKeySelective(OperatingLog record);
int updateByPrimaryKeyWithBLOBs(OperatingLog record);
int updateByPrimaryKey(OperatingLog record);
}

View File

@ -0,0 +1,341 @@
<?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.OperatingLogMapper">
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.OperatingLog">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="oper_method" jdbcType="VARCHAR" property="operMethod" />
<result column="oper_user" jdbcType="VARCHAR" property="operUser" />
<result column="source_id" jdbcType="VARCHAR" property="sourceId" />
<result column="oper_type" jdbcType="VARCHAR" property="operType" />
<result column="oper_module" jdbcType="VARCHAR" property="operModule" />
<result column="oper_title" jdbcType="VARCHAR" property="operTitle" />
<result column="oper_time" jdbcType="BIGINT" property="operTime" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.OperatingLog">
<result column="oper_content" jdbcType="LONGVARCHAR" property="operContent" />
</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">
id, project_id, oper_method, oper_user, source_id, oper_type, oper_module, oper_title,
oper_time
</sql>
<sql id="Blob_Column_List">
oper_content
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.OperatingLogExample" resultMap="ResultMapWithBLOBs">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from operating_log
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByExample" parameterType="io.metersphere.base.domain.OperatingLogExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from operating_log
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from operating_log
where id = #{id,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String">
delete from operating_log
where id = #{id,jdbcType=VARCHAR}
</delete>
<delete id="deleteByExample" parameterType="io.metersphere.base.domain.OperatingLogExample">
delete from operating_log
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.OperatingLog">
insert into operating_log (id, project_id, oper_method,
oper_user, source_id, oper_type,
oper_module, oper_title, oper_time,
oper_content)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{operMethod,jdbcType=VARCHAR},
#{operUser,jdbcType=VARCHAR}, #{sourceId,jdbcType=VARCHAR}, #{operType,jdbcType=VARCHAR},
#{operModule,jdbcType=VARCHAR}, #{operTitle,jdbcType=VARCHAR}, #{operTime,jdbcType=BIGINT},
#{operContent,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.OperatingLog">
insert into operating_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="projectId != null">
project_id,
</if>
<if test="operMethod != null">
oper_method,
</if>
<if test="operUser != null">
oper_user,
</if>
<if test="sourceId != null">
source_id,
</if>
<if test="operType != null">
oper_type,
</if>
<if test="operModule != null">
oper_module,
</if>
<if test="operTitle != null">
oper_title,
</if>
<if test="operTime != null">
oper_time,
</if>
<if test="operContent != null">
oper_content,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="operMethod != null">
#{operMethod,jdbcType=VARCHAR},
</if>
<if test="operUser != null">
#{operUser,jdbcType=VARCHAR},
</if>
<if test="sourceId != null">
#{sourceId,jdbcType=VARCHAR},
</if>
<if test="operType != null">
#{operType,jdbcType=VARCHAR},
</if>
<if test="operModule != null">
#{operModule,jdbcType=VARCHAR},
</if>
<if test="operTitle != null">
#{operTitle,jdbcType=VARCHAR},
</if>
<if test="operTime != null">
#{operTime,jdbcType=BIGINT},
</if>
<if test="operContent != null">
#{operContent,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.OperatingLogExample" resultType="java.lang.Long">
select count(*) from operating_log
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update operating_log
<set>
<if test="record.id != null">
id = #{record.id,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.operMethod != null">
oper_method = #{record.operMethod,jdbcType=VARCHAR},
</if>
<if test="record.operUser != null">
oper_user = #{record.operUser,jdbcType=VARCHAR},
</if>
<if test="record.sourceId != null">
source_id = #{record.sourceId,jdbcType=VARCHAR},
</if>
<if test="record.operType != null">
oper_type = #{record.operType,jdbcType=VARCHAR},
</if>
<if test="record.operModule != null">
oper_module = #{record.operModule,jdbcType=VARCHAR},
</if>
<if test="record.operTitle != null">
oper_title = #{record.operTitle,jdbcType=VARCHAR},
</if>
<if test="record.operTime != null">
oper_time = #{record.operTime,jdbcType=BIGINT},
</if>
<if test="record.operContent != null">
oper_content = #{record.operContent,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExampleWithBLOBs" parameterType="map">
update operating_log
set id = #{record.id,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
oper_method = #{record.operMethod,jdbcType=VARCHAR},
oper_user = #{record.operUser,jdbcType=VARCHAR},
source_id = #{record.sourceId,jdbcType=VARCHAR},
oper_type = #{record.operType,jdbcType=VARCHAR},
oper_module = #{record.operModule,jdbcType=VARCHAR},
oper_title = #{record.operTitle,jdbcType=VARCHAR},
oper_time = #{record.operTime,jdbcType=BIGINT},
oper_content = #{record.operContent,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update operating_log
set id = #{record.id,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
oper_method = #{record.operMethod,jdbcType=VARCHAR},
oper_user = #{record.operUser,jdbcType=VARCHAR},
source_id = #{record.sourceId,jdbcType=VARCHAR},
oper_type = #{record.operType,jdbcType=VARCHAR},
oper_module = #{record.operModule,jdbcType=VARCHAR},
oper_title = #{record.operTitle,jdbcType=VARCHAR},
oper_time = #{record.operTime,jdbcType=BIGINT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.OperatingLog">
update operating_log
<set>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="operMethod != null">
oper_method = #{operMethod,jdbcType=VARCHAR},
</if>
<if test="operUser != null">
oper_user = #{operUser,jdbcType=VARCHAR},
</if>
<if test="sourceId != null">
source_id = #{sourceId,jdbcType=VARCHAR},
</if>
<if test="operType != null">
oper_type = #{operType,jdbcType=VARCHAR},
</if>
<if test="operModule != null">
oper_module = #{operModule,jdbcType=VARCHAR},
</if>
<if test="operTitle != null">
oper_title = #{operTitle,jdbcType=VARCHAR},
</if>
<if test="operTime != null">
oper_time = #{operTime,jdbcType=BIGINT},
</if>
<if test="operContent != null">
oper_content = #{operContent,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.OperatingLog">
update operating_log
set project_id = #{projectId,jdbcType=VARCHAR},
oper_method = #{operMethod,jdbcType=VARCHAR},
oper_user = #{operUser,jdbcType=VARCHAR},
source_id = #{sourceId,jdbcType=VARCHAR},
oper_type = #{operType,jdbcType=VARCHAR},
oper_module = #{operModule,jdbcType=VARCHAR},
oper_title = #{operTitle,jdbcType=VARCHAR},
oper_time = #{operTime,jdbcType=BIGINT},
oper_content = #{operContent,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.OperatingLog">
update operating_log
set project_id = #{projectId,jdbcType=VARCHAR},
oper_method = #{operMethod,jdbcType=VARCHAR},
oper_user = #{operUser,jdbcType=VARCHAR},
source_id = #{sourceId,jdbcType=VARCHAR},
oper_type = #{operType,jdbcType=VARCHAR},
oper_module = #{operModule,jdbcType=VARCHAR},
oper_title = #{operTitle,jdbcType=VARCHAR},
oper_time = #{operTime,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -0,0 +1,12 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.log.vo.OperatingLogDTO;
import io.metersphere.log.vo.OperatingLogRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtOperatingLogMapper {
List<OperatingLogDTO> list(@Param("request") OperatingLogRequest request);
}

View File

@ -0,0 +1,44 @@
<?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.ext.ExtOperatingLogMapper">
<select id="list" resultType="io.metersphere.log.vo.OperatingLogDTO">
SELECT
t.id,
t.project_id,
t.oper_user,
t.source_id,
t.oper_type,
t.oper_module,
t.oper_title,
t.oper_time,
t1.NAME userName,
t2.`name` projectName
FROM
operating_log t
LEFT JOIN USER t1 ON t.oper_user = t1.id
LEFT JOIN project t2 ON t.project_id = t2.id
join workspace w on t2.workspace_id = w.id
<where>
<if test="request.operUser != null and request.operUser != ''">
and t.oper_user like #{request.operUser, jdbcType=VARCHAR}
or t1.NAME like #{request.operUser, jdbcType=VARCHAR}
</if>
<if test="request.projectId != null and request.projectId !=''">
and t.project_id = #{request.projectId}
</if>
<if test="request.operType != null and request.operType != ''">
and t.oper_type like #{request.operType, jdbcType=VARCHAR}
</if>
<if test="request.operModule != null and request.operModule != ''">
and p.oper_module like #{request.opeModule, jdbcType=VARCHAR}
</if>
<if test="request.startTime != null and request.endTime != null">
AND t.oper_time BETWEEN #{request.startTime} AND #{request.endTime}
</if>
</where>
order by t.oper_time desc
</select>
</mapper>

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum OperLogConstants {
CREATE, DELETE, GC, RESTORE, DEBUG, UPDATE, BATCH_DEL, BATCH_UPDATE, BATCH_ADD, BATCH_RESTORE, BATCH_GC, IMPORT, EXPORT, ASSOCIATE_CASE, REVIEW, COPY, EXECUTE, SHARE, LOGIN, CREATE_PRE_TEST, OTHER
}

View File

@ -0,0 +1,76 @@
package io.metersphere.log.annotation;
import io.metersphere.commons.constants.OperLogConstants;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* MsAuditLog class
* @author mr.zhao
* @date 2021/05/06
*/
public @interface MsAuditLog {
/**
* 功能模块
*
* @return
*/
String module();
/**
* 项目
*
* @return
*/
String project() default "";
/**
* 操作人
*
* @return
*/
String operUser() default "";
/**
* 操作类型
*
* @return
*/
OperLogConstants type() default OperLogConstants.OTHER;
/**
* 标题
*/
String title() default "";
/**
* 操作内容
*
* @return
*/
String content() default "";
/**
* 操作前触发内容
*
* @return
*/
String beforeEvent() default "";
/**
*
* @return
*/
String beforeValue() default "";
/**
* 传入执行类
*
* @return
*/
Class[] msClass() default {};
}

View File

@ -0,0 +1,219 @@
package io.metersphere.log.aspect;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.OperatingLog;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.log.service.OperatingLogService;
import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 系统日志切面处理类
*/
@Aspect
@Component
public class MsLogAspect {
/**
* 解析spel表达式
*/
ExpressionParser parser = new SpelExpressionParser();
/**
* 将方法参数纳入Spring管理
*/
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Autowired
private ApplicationContext applicationContext;
@Resource
private OperatingLogService operatingLogService;
/**
* 定义切点 @Pointcut 在注解的位置切入代码
*/
@Pointcut("@annotation(io.metersphere.log.annotation.MsAuditLog)")
public void logPoinCut() {
}
@Before("logPoinCut()")
public void before(JoinPoint joinPoint) {
try {
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取参数对象数组
Object[] args = joinPoint.getArgs();
MsAuditLog msLog = method.getAnnotation(MsAuditLog.class);
if (msLog != null && StringUtils.isNotEmpty(msLog.beforeEvent())) {
// 操作内容
//获取方法参数名
String[] params = discoverer.getParameterNames(method);
//将参数纳入Spring管理
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
for (Class clazz : msLog.msClass()) {
context.setVariable("msClass", applicationContext.getBean(clazz));
}
Expression expression = parser.parseExpression(msLog.beforeEvent());
String beforeContent = expression.getValue(context, String.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(msLog);
Field value = invocationHandler.getClass().getDeclaredField("memberValues");
value.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
memberValues.put("beforeValue", beforeContent);
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
}
}
/**
* 切面 配置通知
*/
@AfterReturning("logPoinCut()")
public void saveLog(JoinPoint joinPoint) {
try {
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取参数对象数组
Object[] args = joinPoint.getArgs();
//获取操作
MsAuditLog msLog = method.getAnnotation(MsAuditLog.class);
if (msLog != null) {
//保存日志
OperatingLog msOperLog = new OperatingLog();
//保存获取的操作
msOperLog.setId(UUID.randomUUID().toString());
// 操作类型
msOperLog.setOperType(msLog.type().name());
// 项目ID
msOperLog.setProjectId(msLog.project());
String module = Translator.get(msLog.module());
msOperLog.setOperModule(StringUtils.isNotEmpty(module) ? module : msLog.module());
//获取方法参数名
String[] params = discoverer.getParameterNames(method);
//将参数纳入Spring管理
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
for (Class clazz : msLog.msClass()) {
context.setVariable("msClass", applicationContext.getBean(clazz));
}
// 项目ID 表达式
try {
Expression titleExp = parser.parseExpression(msLog.project());
String project = titleExp.getValue(context, String.class);
msOperLog.setProjectId(project);
} catch (Exception e) {
msOperLog.setProjectId(msLog.project());
}
// 标题
if (StringUtils.isNotEmpty(msLog.title())) {
String title = msLog.title();
try {
Expression titleExp = parser.parseExpression(title);
title = titleExp.getValue(context, String.class);
msOperLog.setOperTitle(title);
} catch (Exception e) {
msOperLog.setOperTitle(title);
}
}
// 操作内容
if (StringUtils.isNotEmpty(msLog.content())) {
Expression expression = parser.parseExpression(msLog.content());
String content = expression.getValue(context, String.class);
try {
if (StringUtils.isNotEmpty(content)) {
OperatingLogDetails details = JSON.parseObject(content, OperatingLogDetails.class);
msOperLog.setSourceId(details.getSourceId());
if (StringUtils.isNotEmpty(details.getProjectId())) {
msOperLog.setProjectId(details.getProjectId());
}
if (StringUtils.isEmpty(msLog.title())) {
msOperLog.setOperTitle(details.getTitle());
}
}
if (StringUtils.isNotEmpty(content) && StringUtils.isNotEmpty(msLog.beforeValue())) {
OperatingLogDetails details = JSON.parseObject(content, OperatingLogDetails.class);
List<DetailColumn> columns = ReflexObjectUtil.compared(JSON.parseObject(msLog.beforeValue(), OperatingLogDetails.class), details);
details.setColumns(columns);
msOperLog.setOperContent(JSON.toJSONString(details));
} else {
msOperLog.setOperContent(content);
}
} catch (Exception e) {
msOperLog.setOperContent(content);
}
}
// 只有前置操作的处理/ 删除操作
if (StringUtils.isNotEmpty(msLog.beforeEvent()) && StringUtils.isNotEmpty(msLog.beforeValue()) && StringUtils.isEmpty(msLog.content())) {
msOperLog.setOperContent(msLog.beforeValue());
OperatingLogDetails details = JSON.parseObject(msLog.beforeValue(), OperatingLogDetails.class);
if (StringUtils.isEmpty(msLog.title())) {
msOperLog.setOperTitle(details.getTitle());
}
msOperLog.setSourceId(details.getSourceId());
if (StringUtils.isNotEmpty(details.getProjectId())) {
msOperLog.setProjectId(details.getProjectId());
}
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
msOperLog.setOperMethod(className + "." + methodName);
msOperLog.setOperTime(System.currentTimeMillis());
//获取用户名
if (StringUtils.isNotEmpty(msLog.operUser())) {
msOperLog.setOperUser(msLog.operUser());
} else {
msOperLog.setOperUser(SessionUtils.getUserId());
}
operatingLogService.create(msOperLog);
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
}
}
}

View File

@ -0,0 +1,38 @@
package io.metersphere.log.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.log.service.OperatingLogService;
import io.metersphere.log.vo.OperatingLogDTO;
import io.metersphere.log.vo.OperatingLogRequest;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping(value = "/operating/log")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public class OperatingLogController {
@Resource
private OperatingLogService operatingLogService;
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<OperatingLogDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody OperatingLogRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return PageUtils.setPageInfo(page, operatingLogService.list(request));
}
@GetMapping("/get/{id}")
public OperatingLogDTO get(@PathVariable String id) {
return operatingLogService.get(id);
}
}

View File

@ -0,0 +1,48 @@
package io.metersphere.log.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.OperatingLog;
import io.metersphere.base.mapper.OperatingLogMapper;
import io.metersphere.base.mapper.ext.ExtOperatingLogMapper;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.log.vo.OperatingLogDTO;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.OperatingLogRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class OperatingLogService {
@Resource
private OperatingLogMapper operatingLogMapper;
@Resource
private ExtOperatingLogMapper extOperatingLogMapper;
public void create(OperatingLog log) {
operatingLogMapper.insert(log);
}
public List<OperatingLogDTO> list(OperatingLogRequest request) {
if (CollectionUtils.isNotEmpty(request.getTimes())) {
request.setStartTime(request.getTimes().get(0));
request.setEndTime(request.getTimes().get(1));
}
return extOperatingLogMapper.list(request);
}
public OperatingLogDTO get(String id) {
OperatingLog log = operatingLogMapper.selectByPrimaryKey(id);
OperatingLogDTO dto = new OperatingLogDTO();
BeanUtils.copyBean(dto, log);
if (StringUtils.isNotEmpty(log.getOperContent())) {
dto.setDetails(JSON.parseObject(log.getOperContent(), OperatingLogDetails.class));
}
return dto;
}
}

View File

@ -0,0 +1,71 @@
package io.metersphere.log.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.JsonDiff;
import java.util.List;
import java.util.Map;
public class MyJSONCompartor {
public static void compareJSON(String src, String target) {
JSONObject so = JSON.parseObject(src);
JSONObject to = JSON.parseObject(target);
for (String key : so.keySet()) {
Object value = so.get(key);
Object v2 = to.get(key);
if (value instanceof Map) {
compareJSON(JSON.toJSONString(value), JSON.toJSONString(v2));
} else if (value instanceof List) {
List list = (List<Map<String, Object>>) value;
List list2 = (List<Map<String, Object>>) v2;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof List || list.get(i) instanceof Map) {
compareJSON(JSON.toJSONString(list.get(i)), JSON.toJSONString(list2.get(i)));
} else {//如果是这种 LIst值不是Map也不是List,就可以直接比较值 "phoneNumList": ["10086""10084"]
if (!list.get(i).equals(list2.get(i))) {
System.err.println("key " + key + "值不一样,期望" + list.get(i) + ",实际" + list2.get(i));
}
}
}
} else {
if (null == value) {
System.err.println("key " + key + "值是null");
} else if (!value.equals(v2)) {
System.err.println("key " + key + "值不一样,期望" + value + ",实际" + v2);
}
}
}
}
public static void main(String[] args) {
final String json1 = "{\"type\":\"HTTPSamplerProxy\",\"id\":\"9cf1ef83-099b-4a57-a229-2250e9b12372\",\"name\":\"测试添加\",\"label\":\"HTTPSamplerProxy\",\"active\":false,\"enable\":true,\"hashTree\":[],\"customizeReq\":false,\"mockEnvironment\":false,\"protocol\":\"HTTP\",\"method\":\"GET\",\"path\":\"/test\",\"connectTimeout\":\"6000\",\"responseTimeout\":\"6000\",\"headers\":[{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept\",\"required\":true,\"valid\":true,\"value\":\"a\"},{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept-Charset\",\"required\":true,\"valid\":true,\"value\":\"b\"},{\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"valid\":false}],\"body\":{\"binary\":[],\"json\":false,\"kV\":true,\"kvs\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@character\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@datetime\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"oldKV\":false,\"type\":\"Form Data\",\"valid\":true,\"xml\":false},\"rest\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"cx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"c1\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"ca\",\"required\":false,\"type\":\"text\",\"valid\":true,\"value\":\"c2\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"followRedirects\":true,\"doMultipartPost\":false,\"arguments\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"test\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"1\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"pa\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"2\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}]}\n";
final String json2 = "{\"type\":\"HTTPSamplerProxy\",\"id\":\"9cf1ef83-099b-4a57-a229-2250e9b12372\",\"name\":\"测试添加\",\"label\":\"HTTPSamplerProxy\",\"active\":false,\"enable\":true,\"hashTree\":[],\"customizeReq\":false,\"mockEnvironment\":false,\"protocol\":\"HTTP\",\"method\":\"GET\",\"path\":\"/test\",\"connectTimeout\":\"6000\",\"responseTimeout\":\"6000\",\"headers\":[{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept\",\"required\":true,\"valid\":true,\"value\":\"a1\"},{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept-Charset\",\"required\":true,\"valid\":true,\"value\":\"b2\"},{\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"valid\":false}],\"body\":{\"binary\":[],\"json\":false,\"kV\":true,\"kvs\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@character\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@datetime\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"oldKV\":false,\"type\":\"Form Data\",\"valid\":true,\"xml\":false},\"rest\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"cx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"c13\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"ca\",\"required\":false,\"type\":\"text\",\"valid\":true,\"value\":\"c22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"followRedirects\":true,\"doMultipartPost\":false,\"arguments\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"test\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"11\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"pa\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}]}\n";
String json4 ="{\"type\":\"HTTPSamplerProxy\",\"id\":\"9cf1ef83-099b-4a57-a229-2250e9b12372\",\"name\":\"测试添加\",\"label\":\"HTTPSamplerProxy\",\"active\":false,\"enable\":true,\"hashTree\":[],\"customizeReq\":false,\"mockEnvironment\":false,\"protocol\":\"HTTP\",\"method\":\"GET\",\"path\":\"/test\",\"connectTimeout\":\"6000\",\"responseTimeout\":\"6000\",\"headers\":[{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept\",\"required\":true,\"valid\":true,\"value\":\"a1\"},{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept-Charset\",\"required\":true,\"valid\":true,\"value\":\"b2\"},{\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"valid\":false}],\"body\":{\"binary\":[],\"json\":false,\"kV\":true,\"kvs\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@character\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@datetime\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"oldKV\":false,\"type\":\"Form Data\",\"valid\":true,\"xml\":false},\"rest\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"cx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"c13\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"ca\",\"required\":false,\"type\":\"text\",\"valid\":true,\"value\":\"c22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"followRedirects\":true,\"doMultipartPost\":false,\"arguments\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"test\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"11\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"pa\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}]}\n";
String json5 ="{\"type\":\"HTTPSamplerProxy\",\"id\":\"9cf1ef83-099b-4a57-a229-2250e9b12372\",\"name\":\"测试添加\",\"label\":\"HTTPSamplerProxy\",\"active\":false,\"enable\":true,\"hashTree\":[],\"customizeReq\":false,\"mockEnvironment\":false,\"protocol\":\"HTTP\",\"method\":\"GET\",\"path\":\"/test\",\"connectTimeout\":\"6000\",\"responseTimeout\":\"6000\",\"headers\":[{\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"Accept\",\"required\":true,\"valid\":true,\"value\":\"a1\"},{\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"valid\":false}],\"body\":{\"binary\":[],\"json\":false,\"kV\":true,\"kvs\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@character\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"axx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"@datetime\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"oldKV\":false,\"type\":\"Form Data\",\"valid\":true,\"xml\":false},\"rest\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"cx\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"c13\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"ca\",\"required\":false,\"type\":\"text\",\"valid\":true,\"value\":\"c22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}],\"followRedirects\":true,\"doMultipartPost\":false,\"arguments\":[{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"test\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"11\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"pa\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"22\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"name\":\"cc\",\"required\":true,\"type\":\"text\",\"valid\":true,\"value\":\"cc\"},{\"contentType\":\"text/plain\",\"enable\":true,\"encode\":true,\"file\":false,\"required\":true,\"type\":\"text\",\"valid\":false}]}\n";
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode source = mapper.readTree(json1);
JsonNode target = mapper.readTree(json2);
JsonNode patch2 = JsonDiff.asJson( target, source);
JsonNode patch = JsonDiff.asJson( source, target);
System.out.println( patch.toString());
System.out.println( patch2.toString());
} catch (Exception e) {
}
}
}

View File

@ -0,0 +1,107 @@
package io.metersphere.log.utils;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.JsonDiff;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
public class ReflexObjectUtil {
public static List<DetailColumn> getColumns(Object obj, Map<String, String> columns) {
List<DetailColumn> columnList = new LinkedList<>();
if (obj == null) {
return columnList;
}
String dffValue = columns.get("ms-dff-col");
List<String> dffColumns = new LinkedList<>();
if (StringUtils.isNotEmpty(dffValue)) {
dffColumns = Arrays.asList(dffValue.split(","));
}
// 得到类对象
Class clazz = obj.getClass();
// 得到类中的所有属性集合
List<Field[]> fields = new LinkedList<>();
// 遍历所有父类字节码对象
while (clazz != null) {
// 获取字节码对象的属性对象数组
Field[] declaredFields = clazz.getDeclaredFields();
fields.add(declaredFields);
// 获得父类的字节码对象
clazz = clazz.getSuperclass();
}
for (Field[] fs : fields) {
for (int i = 0; i < fs.length; i++) {
Field f = fs[i];
f.setAccessible(true);
try {
if (columns.containsKey(f.getName())) {
Object val = f.get(obj);
DetailColumn column = new DetailColumn(columns.get(f.getName()), f.getName(), val, "");
if (dffColumns.contains(f.getName())) {
column.setDepthDff(true);
}
columnList.add(column);
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
}
}
}
List<String> keys = columns.keySet().stream().collect(Collectors.toList());
ReflexObjectUtil.order(keys, columnList);
return columnList;
}
public static void order(List<String> orderRegulation, List<DetailColumn> targetList) {
Collections.sort(targetList, ((o1, o2) -> {
int io1 = orderRegulation.indexOf(o1.getColumnName());
int io2 = orderRegulation.indexOf(o2.getColumnName());
return io1 - io2;
}));
}
public static List<DetailColumn> compared(OperatingLogDetails obj, OperatingLogDetails newObj) {
List<DetailColumn> comparedColumns = new LinkedList<>();
try {
if (obj != null && newObj != null) {
List<DetailColumn> originalColumns = obj.getColumns();
List<DetailColumn> newColumns = newObj.getColumns();
for (int i = 0; i < originalColumns.size(); i++) {
if (!StringUtils.equals(JSON.toJSONString(originalColumns.get(i).getOriginalValue()), JSON.toJSONString(newColumns.get(i).getOriginalValue()))) {
if (originalColumns.get(i).isDepthDff()) {
ObjectMapper mapper = new ObjectMapper();
JsonNode source = mapper.readTree(JSON.toJSONString(originalColumns.get(i).getOriginalValue()));
JsonNode target = mapper.readTree(JSON.toJSONString(newColumns.get(i).getOriginalValue()));
JsonNode before = JsonDiff.asJson(target, source);
JsonNode after = JsonDiff.asJson(source, target);
DetailColumn column = new DetailColumn();
BeanUtils.copyBean(column, originalColumns.get(i));
column.setOriginalValue(before.toString());
column.setNewValue(after.toString());
comparedColumns.add(column);
} else {
DetailColumn column = new DetailColumn();
BeanUtils.copyBean(column, originalColumns.get(i));
column.setNewValue(newColumns.get(i).getOriginalValue());
comparedColumns.add(column);
}
}
}
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
}
return comparedColumns;
}
}

View File

@ -0,0 +1,27 @@
package io.metersphere.log.vo;
import lombok.Data;
import java.util.UUID;
@Data
public class DetailColumn {
private String id;
private boolean depthDff;
private String columnTitle;
private String columnName;
private Object originalValue;
private Object newValue;
public DetailColumn() {
}
public DetailColumn(String columnTitle, String columnName, Object originalValue, Object newValue) {
this.id = UUID.randomUUID().toString();
this.columnTitle = columnTitle;
this.columnName = columnName;
this.originalValue = originalValue;
this.newValue = newValue;
}
}

View File

@ -0,0 +1,30 @@
package io.metersphere.log.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class OperatingLogDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String projectName;
private String operUser;
private String userName;
private String sourceId;
private String operType;
private String operModule;
private String operTitle;
private Long operTime;
private OperatingLogDetails details;
}

View File

@ -0,0 +1,30 @@
package io.metersphere.log.vo;
import lombok.Data;
import java.util.List;
@Data
public class OperatingLogDetails {
private String sourceId;
private String projectId;
private String title;
private List<DetailColumn> columns;
public OperatingLogDetails() {
}
public OperatingLogDetails(String sourceId, String projectId, List<DetailColumn> columns) {
this.sourceId = sourceId;
this.projectId = projectId;
this.columns = columns;
}
public OperatingLogDetails(String sourceId, String projectId, String title, List<DetailColumn> columns) {
this.sourceId = sourceId;
this.projectId = projectId;
this.title = title;
this.columns = columns;
}
}

View File

@ -0,0 +1,30 @@
package io.metersphere.log.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class OperatingLogRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String workspaceId;
private String projectId;
private String operUser;
private String sourceId;
private String operType;
private Long startTime;
private Long endTime;
private String operModule;
private String operTitle;
private List<Long> times;
}

View File

@ -0,0 +1,24 @@
package io.metersphere.log.vo.definition;
import java.util.LinkedHashMap;
import java.util.Map;
public class DefinitionReference {
public static Map<String, String> definitionColumns = new LinkedHashMap<>();
static {
definitionColumns.put("name", "接口名称");
definitionColumns.put("method", "请求类型");
definitionColumns.put("modulePath", "模块");
definitionColumns.put("status", "接口状态");
definitionColumns.put("protocol", "协议");
definitionColumns.put("userId", "负责人");
definitionColumns.put("path", "路径");
definitionColumns.put("tags", "标签");
definitionColumns.put("request", "请求参数");
definitionColumns.put("response", "返回参数");
definitionColumns.put("description", "描述");
// 需要深度对比的字段可以支持多个req1,req2
definitionColumns.put("ms-dff-col", "request");
}
}

View File

@ -191,7 +191,6 @@ quota_duration_excess_organization=压测时长超过组织限额
import_xmind_count_error=思维导图导入用例数量不能超过 500 条
license_valid_license_error=授权认证失败
import_xmind_not_found=未找到测试用例
test_review_task_notice=测试评审任务通知
test_track.length_less_than=标题过长,字数必须小于
# check owner
@ -221,4 +220,6 @@ custom_field_already=工作空间下已存在该字段:
template_already=工作空间下已存在该模板:
expect_name_exists=预期名称已存在
ssl_password_error=认证密码错误,请重新输入密码
ssl_file_error=认证文件加载失败,请检查认证文件
ssl_file_error=认证文件加载失败,请检查认证文件
#log
api_definition=接口定义

View File

@ -58,13 +58,13 @@
}
},
run() {
let projectId = this.$store.state.projectId;
let projectId = this.$store.state.projectId;
// envMap
if (!this.envMap || this.envMap.size === 0) {
projectId = this.$store.state.projectId;
} else {
//
if(this.runData.projectId){
if (this.runData.projectId) {
projectId = this.runData.projectId;
}
}
@ -77,9 +77,13 @@
item.projectId = projectId;
threadGroup.hashTree.push(item);
})
let reqObj = {id: this.reportId, testElement: testPlan, type: this.type,projectId: projectId, environmentMap: strMapToObj(this.envMap)};
let reqObj = {id: this.reportId, testElement: testPlan, type: this.type, projectId: projectId, environmentMap: strMapToObj(this.envMap)};
let bodyFiles = getBodyUploadFiles(reqObj, this.runData);
if (this.runData[0].url) {
reqObj.name = this.runData[0].url;
} else {
reqObj.name = this.runData[0].path;
}
let url = "";
if (this.debug) {
reqObj.reportId = this.reportId;
@ -92,7 +96,7 @@
this.getResult();
this.$emit('autoCheckStatus'); //
}, error => {
this.$emit('errorRefresh', {});
this.$emit('errorRefresh', {});
});
}
}

View File

@ -114,3 +114,4 @@ export const RESULT_MAP = new Map([
['error', '执行结果:未通过'],
['default', '执行结果:未执行']
]);

View File

@ -0,0 +1,123 @@
<template>
<el-dialog :close-on-click-modal="false" :title="getType(detail.operType)+title" :visible.sync="infoVisible" width="60%" :destroy-on-close="true"
@close="handleClose">
<div>
<p class="tip">{{ this.$t('operating_log.user') }} {{detail.operUser}}</p>
</div>
<div>
<p class="tip">{{ this.$t('operating_log.time') }} {{ detail.operTime | timestampFormatDate }}</p>
</div>
<div>
<p class="tip">{{ this.$t('report.test_log_details') }} </p>
<div v-if="detail && detail.operType !== 'CREATE' && detail && detail.details && detail.details.columns && detail.details.columns.length >0 ">
<div v-if="detail && detail.details && detail.details.columns" style="margin-left: 20px">
<el-table :data="detail.details.columns">
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')"/>
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')"/>
<el-table-column prop="newValue" :label="$t('operating_log.after_change')"/>
</el-table>
</div>
</div>
<div v-else-if="detail && (detail.details === null || (detail.details && detail.details.columns && detail.details.columns.length === 0))">
<span>{{detail.operTitle}} </span>
<span style="color: #409EFF">{{getType(detail.operType)}} </span>
<span style="color: #409EFF"> {{$t('api_test.home_page.detail_card.success')}}</span>
</div>
<div v-else>
<div v-if="detail && detail.details && detail.details.columns" style="margin-left: 20px">
<p v-for="n in detail.details.columns" :key="n.id">{{n.columnTitle}}{{n.originalValue}}</p>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: "MsLogDetail",
components: {},
props: {
title: String,
},
data() {
return {
infoVisible: false,
detail: {},
LOG_TYPE: [
{id: 'CREATE', label: this.$t('api_test.definition.request.create_info')},
{id: 'DELETE', label: this.$t('commons.delete')},
{id: 'UPDATE', label: this.$t('commons.update')},
{id: 'IMPORT', label: this.$t('api_test.api_import.label')},
{id: 'EXPORT', label: this.$t('commons.export')},
{id: 'ASSOCIATE_CASE', label: this.$t('test_track.review_view.relevance_case')},
{id: 'REVIEW', label: this.$t('test_track.review_view.start_review')},
{id: 'COPY', label: this.$t('commons.copy')},
{id: 'EXECUTE', label: this.$t('api_test.automation.execute')},
{id: 'CREATE_PRE_TEST', label: this.$t('api_test.create_performance_test')},
{id: 'SHARE', label: this.$t('operating_log.share')},
{id: 'LOGIN', label: this.$t('commons.login')},
{id: 'RESTORE', label: this.$t('commons.reduction')},
{id: 'DEBUG', label: this.$t('api_test.request.debug')},
{id: 'GC', label: this.$t('api_test.automation.trash')},
{id: 'BATCH_DEL', label: this.$t('api_test.definition.request.batch_delete')},
{id: 'BATCH_UPDATE', label: this.$t('api_test.definition.request.batch_edit')},
{id: 'BATCH_ADD', label: this.$t('commons.batch_add')},
{id: 'BATCH_RESTORE', label: "批量恢复"},
{id: 'BATCH_GC', label: "批量回收"}
],
LOG_TYPE_MAP: new Map([
['CREATE', this.$t('api_test.definition.request.create_info')],
['DELETE', this.$t('commons.delete')],
['UPDATE', this.$t('commons.update')],
['IMPORT', this.$t('api_test.api_import.label')],
['EXPORT', this.$t('commons.export')],
['ASSOCIATE_CASE', this.$t('test_track.review_view.relevance_case')],
['REVIEW', this.$t('test_track.review_view.start_review')],
['COPY', this.$t('commons.copy')],
['EXECUTE', this.$t('api_test.automation.execute')],
['CREATE_PRE_TEST', this.$t('api_test.create_performance_test')],
['SHARE', this.$t('operating_log.share')],
['LOGIN', this.$t('commons.login')],
['RESTORE', this.$t('commons.reduction')],
['DEBUG', this.$t('api_test.request.debug')],
['GC', this.$t('api_test.automation.trash')],
['BATCH_DEL', this.$t('api_test.definition.request.batch_delete')],
['BATCH_UPDATE', this.$t('api_test.definition.request.batch_edit')],
['BATCH_ADD', this.$t('commons.batch_add')],
['BATCH_RESTORE', "批量恢复"],
['BATCH_GC', "批量回收"],
])
}
},
methods: {
handleClose() {
this.infoVisible = false;
},
getDetails(id) {
this.result = this.$get("/operating/log/get/" + id, response => {
let data = response.data;
this.detail = data;
})
},
open(id) {
this.infoVisible = true;
this.getDetails(id);
},
getType(type) {
return this.LOG_TYPE_MAP.get(type);
},
}
}
</script>
<style scoped>
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<div v-loading="result.loading">
<el-card class="table-card">
<template v-slot:header>
<div style="font-size: 16px;margin-bottom: 20px;margin-left: 10px">
{{$t('operating_log.title')}}
</div>
<div>
<el-form :model="condition" label-position="right" label-width="80px" size="small" ref="basicForm" style="margin-right: 20px">
<el-row>
<el-col :span="6">
<el-form-item :label="$t('operating_log.time')" prop="times">
<el-date-picker
size="small"
v-model="condition.times"
type="datetimerange"
value-format="timestamp"
:range-separator="$t('commons.date.range_separator')"
:start-placeholder="$t('commons.date.start_date')"
:end-placeholder="$t('commons.date.end_date')" style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('operating_log.user')" prop="user">
<el-input size="small" v-model="condition.operUser"/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('commons.project')" prop="project">
<el-select size="small" v-model="condition.projectId" clearable>
<el-option v-for="o in items" :key="o.id" :label="$t(o.label)" :value="o.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('operating_log.type')" prop="type">
<el-select size="small" v-model="condition.operType" clearable>
<el-option v-for="o in LOG_TYPE" :key="o.id" :label="$t(o.label)" :value="o.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('operating_log.object')" prop="module">
<el-input size="small" v-model="condition.operModule"/>
</el-form-item>
</el-col>
<el-col :span="2">
<el-button type="primary" size="small" style="float: right" @click="search">{{$t('commons.adv_search.search')}}</el-button>
</el-col>
</el-row>
</el-form>
</div>
</template>
<el-table border class="adjust-table" :data="tableData" ref="operLog">
<el-table-column prop="operTime" :label="$t('operating_log.time')">
<template v-slot:default="scope">
<span>{{ scope.row.operTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="userName" :label="$t('operating_log.user')"/>
<el-table-column prop="projectName" :label="$t('commons.project')"/>
<el-table-column prop="operType" :label="$t('operating_log.type')">
<template v-slot:default="scope">
<span>{{getType(scope.row.operType)}}</span>
</template>
</el-table-column>
<el-table-column prop="operModule" :label="$t('operating_log.object')" show-overflow-tooltip width="120px"/>
<el-table-column prop="operTitle" :label="$t('operating_log.name')" show-overflow-tooltip width="150px"/>
<el-table-column :label="$t('report.test_log_details')">
<template v-slot:default="scope">
<el-link style="color: #409EFF" @click="openDetail(scope.row)">{{$t('operating_log.info')}}</el-link>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
<ms-log-detail ref="logDetail" :title="$t('report.test_log_details')"/>
</div>
</template>
<script>
import MsTablePagination from "../../common/pagination/TablePagination";
import MsTableOperator from "../../common/components/MsTableOperator";
import {getCurrentProjectID, getCurrentUser, hasRoles} from "@/common/js/utils";
import {PROJECT_ID, ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER, WORKSPACE_ID} from "@/common/js/constants";
import MsLogDetail from "./LogDetail";
export default {
name: "OperatingLog",
components: {
MsTablePagination, MsTableOperator, MsLogDetail
},
data() {
return {
result: {},
form: {},
currentPage: 0,
pageSize: 10,
total: 0,
items: [],
condition: {},
tableData: [],
LOG_TYPE: [
{id: 'CREATE', label: this.$t('api_test.definition.request.create_info')},
{id: 'DELETE', label: this.$t('commons.delete')},
{id: 'UPDATE', label: this.$t('commons.update')},
{id: 'IMPORT', label: this.$t('api_test.api_import.label')},
{id: 'EXPORT', label: this.$t('commons.export')},
{id: 'ASSOCIATE_CASE', label: this.$t('test_track.review_view.relevance_case')},
{id: 'REVIEW', label: this.$t('test_track.review_view.start_review')},
{id: 'COPY', label: this.$t('commons.copy')},
{id: 'EXECUTE', label: this.$t('api_test.automation.execute')},
{id: 'CREATE_PRE_TEST', label: this.$t('api_test.create_performance_test')},
{id: 'SHARE', label: this.$t('operating_log.share')},
{id: 'LOGIN', label: this.$t('commons.login')},
{id: 'RESTORE', label: this.$t('commons.reduction')},
{id: 'DEBUG', label: this.$t('api_test.request.debug')},
{id: 'GC', label: this.$t('api_test.automation.trash')},
{id: 'BATCH_DEL', label: this.$t('api_test.definition.request.batch_delete')},
{id: 'BATCH_UPDATE', label: this.$t('api_test.definition.request.batch_edit')},
{id: 'BATCH_ADD', label: this.$t('commons.batch_add')},
{id: 'BATCH_RESTORE', label: "批量恢复"},
{id: 'BATCH_GC', label: "批量回收"}
],
LOG_TYPE_MAP: new Map([
['CREATE', this.$t('api_test.definition.request.create_info')],
['DELETE', this.$t('commons.delete')],
['UPDATE', this.$t('commons.update')],
['IMPORT', this.$t('api_test.api_import.label')],
['EXPORT', this.$t('commons.export')],
['ASSOCIATE_CASE', this.$t('test_track.review_view.relevance_case')],
['REVIEW', this.$t('test_track.review_view.start_review')],
['COPY', this.$t('commons.copy')],
['EXECUTE', this.$t('api_test.automation.execute')],
['CREATE_PRE_TEST', this.$t('api_test.create_performance_test')],
['SHARE', this.$t('operating_log.share')],
['LOGIN', this.$t('commons.login')],
['RESTORE', this.$t('commons.reduction')],
['DEBUG', this.$t('api_test.request.debug')],
['GC', this.$t('api_test.automation.trash')],
['BATCH_DEL', this.$t('api_test.definition.request.batch_delete')],
['BATCH_UPDATE', this.$t('api_test.definition.request.batch_edit')],
['BATCH_ADD', this.$t('commons.batch_add')],
['BATCH_RESTORE', "批量恢复"],
['BATCH_GC', "批量回收"],
])
}
},
created() {
this.initTableData();
this.initProject();
},
methods: {
initTableData() {
let url = "/operating/log/list/" + this.currentPage + "/" + this.pageSize;
this.result.loading = true;
this.$post(url, this.condition, response => {
this.tableData = response.data.listObject;
this.total = response.data.itemCount;
this.result.loading = false;
})
},
initProject() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.result = this.$get("/project/listAll", response => {
let projects = response.data;
if (projects) {
this.items = [];
projects.forEach(item => {
let data = {id: item.id, label: item.name};
this.items.push(data);
})
}
})
}
},
getType(type) {
return this.LOG_TYPE_MAP.get(type);
},
search() {
this.initTableData();
},
openDetail(row) {
this.$refs.logDetail.open(row.id);
},
}
}
</script>
<style scoped>
</style>

View File

@ -103,6 +103,11 @@ export default {
path: 'envlist',
component: () => import('@/business/components/settings/project/EnvironmentList'),
meta: {project: true, title: 'api_test.environment.environment_config'}
},
{
path: 'operatingLog',
component: () => import('@/business/components/settings/operatinglog/OperatingLog'),
meta: {system: true, title: 'operating_log.title'}
}
]

View File

@ -1805,5 +1805,18 @@ export default {
set_report: "Set report",
report_name: "Report name",
run_with_resource_pool: "Run Within Resource pool",
},
operating_log: {
title: "Operating log",
time: "Operating time",
user: "Operating user",
type: "Operating type",
object: "Operating object",
name: "Operating title",
info: "Detail",
change_field: "Change field",
before_change: "Before change",
after_change: "After change",
share: "Share",
}
};

View File

@ -28,6 +28,7 @@ const messages = {
const i18n = new VueI18n({
locale: 'zh_CN',
messages,
silentTranslationWarn: true
});
const loadedLanguages = ['en_US', 'zh_CN', 'zh_TW'];

View File

@ -1813,5 +1813,18 @@ export default {
set_report: "集合报告",
report_name: "报告名称",
run_with_resource_pool: "资源池运行",
},
operating_log: {
title: "操作日志",
time: "操作时间",
user: "操作人",
type: "操作类型",
object: "操作对象",
name: "标题",
info: "查看详情",
change_field: "变更字段",
before_change: "变更前",
after_change: "变更后",
share: "分享",
}
};

View File

@ -1813,5 +1813,18 @@ export default {
set_report: "集合報告",
report_name: "報告名稱",
run_with_resource_pool: "資源池運行",
},
operating_log: {
title: "操作日誌",
time: "操作時間",
user: "操作人",
type: "操作類型",
object: "操作對象",
name: "標題",
info: "查看詳情",
change_field: "變更字段",
before_change: "變更前",
after_change: "變更後",
share: "分享",
}
};