master
1342486035@qq.com 2024-07-10 14:18:29 +08:00
parent 822e679f0f
commit 5bcb914139
116 changed files with 3049 additions and 732 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

38
cctp-atu/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# Compiled class file
*.class
# Log file
*.log
*.log.gz
logs/
*/logs/
*/*/logs/
# Temp file
*.temp
temp
*/temp
# IDEA profile dir
.idea/
*/.idea/
*/*/.idea/
# IDEA project file
*.iml
*/*.iml
*/*/*.iml
*/*/*/*.iml
target/
*/target/
*/*/target/
*.zip
Junit test
*/src/test/java/
*/src/test/resources/
dist/
*.jar

View File

@ -108,6 +108,7 @@
<dependency>
<groupId>net.northking.cctp</groupId>
<artifactId>cctp-test-element-core</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.northking.cctp</groupId>

View File

@ -21,6 +21,7 @@ import net.northking.cctp.se.exec.constant.AtuExecConstant;
import net.northking.cctp.se.file.Attachment;
import net.northking.cctp.se.lifecycle.EngineRuntime;
import net.northking.cctp.se.log.bean.StepLog;
import net.northking.cctp.se.plan.AtuTaskExecHeartbeatSchedule;
import net.northking.cctp.se.plan.bean.AutoTask;
import net.northking.cctp.se.plan.bean.QuoteData;
import net.northking.cctp.se.plan.bean.TaskExecResult;
@ -444,6 +445,11 @@ public class DefaultExecThread implements AtuExecThread{
try {
RabbitTemplate rabbitTemplate = SpringUtil.getBean(RabbitTemplate.class);
rabbitTemplate.convertAndSend(AtuExecConstant.TASK_EXEC_RESULT, JsonUtils.toJson(result));
if (!AtuExecConstant.TASK_START.equalsIgnoreCase(type)) {
log.debug("清理正在执行中任务数据");
AtuTaskExecHeartbeatSchedule.execTaskMap.remove(result.getTaskId());
}
} catch (Exception e) {
log.error("任务状态信息更新失败;", e);
}

View File

@ -107,20 +107,23 @@ public class EngineRegisterService {
private void setPcInfoLinux(CdEngineInfoRegisterDto dto, ScriptEngineInfo engineInfo) throws Exception{
CdPcDevice pcInfo = new CdPcDevice();
pcInfo.setDeviceName(InetAddress.getLocalHost().getHostName());
pcInfo.setDomainName("设备域名");
pcInfo.setIpAddr(getLinuxIp());
pcInfo.setOsArch(System.getProperty("os.arch"));
pcInfo.setOsName(System.getProperty("os.name"));
pcInfo.setRamSize(getMemory());
if (atuServerConfig.getIsPcExecutor()) {
pcInfo.setDeviceName(InetAddress.getLocalHost().getHostName());
pcInfo.setDomainName("设备域名");
pcInfo.setOsArch(System.getProperty("os.arch"));
pcInfo.setOsName(System.getProperty("os.name"));
pcInfo.setRamSize(getMemory());
if (!StringUtils.isBlank(atuServerConfig.getRemoteProtocol())) {
pcInfo.setRemoteProtocol(atuServerConfig.getRemoteProtocol());
}
if (!StringUtils.isBlank(atuServerConfig.getRemotePort())) {
pcInfo.setRemotePort(atuServerConfig.getRemotePort());
}
}
engineInfo.setOsName(System.getProperty("os.name"));
dto.setCdPcDevice(pcInfo);
if (!StringUtils.isBlank(atuServerConfig.getRemoteProtocol())) {
pcInfo.setRemoteProtocol(atuServerConfig.getRemoteProtocol());
}
if (!StringUtils.isBlank(atuServerConfig.getRemotePort())) {
pcInfo.setRemotePort(atuServerConfig.getRemotePort());
}
engineInfo.setOsName(pcInfo.getOsName());
}
private void setEngInfoLinux(CdEngineInfoRegisterDto dto) {

View File

@ -94,16 +94,6 @@ public class LifecycleSchedule
}
}
/**
*
* 10 60
*/
@Scheduled(initialDelay = 15 * 1000L, fixedDelay = 60 * 1000L)
public void manage()
{
}
private void deployNotice(Object object) {
byte[] json;
try{

View File

@ -9,5 +9,9 @@ public class MQConstant {
*/
public static final String ENGINE_HEARTBEAT = "engine.heartbeat";
/**
*
*/
public static final String TASK_EXEC_HEARTBEAT_QUEUE = "plan.task.exec.heartbeat";
}

View File

@ -79,12 +79,7 @@ public class AtuPlanTaskSchedule {
logger.error("未开启移动执行线程");
return;
}
logger.debug("移动线程池执行情况: {}", ExecutorPool.getMobilePool().getActiveCount() + "/" +
ExecutorPool.getMobilePool().getCorePoolSize());
if (!ExecutorPool.isMobileAvailable()){
logger.error("移动线程池中可执行线程已占满");
return;
}
List<String> list = new ArrayList<>();
try {
list = enginePlanInfoService.queryAllDeviceId(TYPE_MOBILE);
@ -92,43 +87,53 @@ public class AtuPlanTaskSchedule {
logger.error("引擎获取执行设备信息失败设备:",e);
}
if(list.size()>0){
//获取引擎注册后的设备id
EngineRuntime engineRuntime = EngineRuntime.getRuntime();
ScriptEngineInfo info = engineRuntime.getEngineInfo();
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("id",info.getEngineId());
paramMap.put("deviceList",list);
paramMap.put("type","1");
paramMap.put("userBy","engine");
String url = atuServerConfig.getServerUrl() + atuServerConfig.getTryAcqurieDeviceUrl();
try {
ResponseEntity<ResultWrapper> result = restTemplate.postForEntity(url, paramMap, ResultWrapper.class);
if(HttpStatus.OK.equals(result.getStatusCode())){
ResultWrapper deviceResult = result.getBody();
if (deviceResult != null && deviceResult.isSuccess()) {
List<Map<String,String>> body = (List<Map<String,String>>)deviceResult.getData();
if(!CollectionUtils.isEmpty(body)){
logger.debug("移动任务请求占用成功移动端设备信息:"+JSON.toJSONString(body));
for (int i = 0; i < body.size(); i++) {
Map<String, String> deviceMap = body.get(i);
String deviceId = deviceMap.get("id");
String deviceToken = deviceMap.get("token");
//获取设备对应的一条计划信息
boolean isUsed = getTaskFromQueue(deviceId, deviceToken,TYPE_MOBILE);
if(!isUsed){
//无任务 释放设备
logger.debug("无任务释放移动端设备:"+deviceId);
releaseDevice(deviceToken);
logger.debug("委托执行的设备数量:{}", list.size());
for (String taskDeviceId : list) {
logger.debug("移动线程池执行情况: {}", ExecutorPool.getMobilePool().getActiveCount() + "/" +
ExecutorPool.getMobilePool().getCorePoolSize());
if (!ExecutorPool.isMobileAvailable()){
logger.error("移动线程池中可执行线程已占满");
return;
}
//获取引擎注册后的设备id
EngineRuntime engineRuntime = EngineRuntime.getRuntime();
ScriptEngineInfo info = engineRuntime.getEngineInfo();
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("id",info.getEngineId());
List<String> tryAcqurieDeviceIdList = Collections.singletonList(taskDeviceId);
paramMap.put("deviceList",tryAcqurieDeviceIdList);
paramMap.put("type","1");
paramMap.put("userBy","engine");
String url = atuServerConfig.getServerUrl() + atuServerConfig.getTryAcqurieDeviceUrl();
try {
ResponseEntity<ResultWrapper> result = restTemplate.postForEntity(url, paramMap, ResultWrapper.class);
if(HttpStatus.OK.equals(result.getStatusCode())){
ResultWrapper deviceResult = result.getBody();
if (deviceResult != null && deviceResult.isSuccess()) {
List<Map<String,String>> body = (List<Map<String,String>>)deviceResult.getData();
if(!CollectionUtils.isEmpty(body)){
logger.debug("移动任务请求占用成功移动端设备信息:"+JSON.toJSONString(body));
for (int i = 0; i < body.size(); i++) {
Map<String, String> deviceMap = body.get(i);
String deviceId = deviceMap.get("id");
String deviceToken = deviceMap.get("token");
//获取设备对应的一条计划信息
boolean isUsed = getTaskFromQueue(deviceId, deviceToken,TYPE_MOBILE);
if(!isUsed){
//无任务 释放设备
logger.debug("无任务释放移动端设备:"+deviceId);
releaseDevice(deviceToken);
}
}
}
}
}
}
}catch (Exception e){
logger.error("请求占用设备失败,原因:",e);
//释放所有设备
releaseDevices(e, info.getEngineId(), tryAcqurieDeviceIdList);
}
}catch (Exception e){
logger.error("请求占用设备失败,原因:",e);
//释放所有设备
releaseDevices(e, info.getEngineId(), list);
}
}
}
@ -338,12 +343,16 @@ public class AtuPlanTaskSchedule {
task.setAppDownloadUrl(atuServerConfig.getServerUrl() + atuServerConfig.getAppDownloadUrl());
logger.debug("任务信息:" + JSON.toJSONString(task));
planDeviceService.sendTask2Exec(task, deviceToken);
AtuTaskExecHeartbeatSchedule.execTaskMap.put(task.getTaskId(), task.getCaseType());
return true;
} catch (Exception e) {
logger.debug("消息处理异常,回滚至队列中,消息为:{}", msg);
rabbitTemplate.convertAndSend(queueName, msg);
throw new ExecuteException("获取队列任务并推送执行异常, " + e.getMessage());
}
}else{
logger.debug("队列[{}]中无任务信息,修改该委托顺序", queueName);
enginePlanInfoService.updatePlanInfoSort(planInfo.getId());
}
return false;
});

View File

@ -0,0 +1,55 @@
package net.northking.cctp.se.plan;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.se.lifecycle.constant.MQConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class AtuTaskExecHeartbeatSchedule {
private static final Logger logger = LoggerFactory.getLogger(AtuTaskExecHeartbeatSchedule.class);
@Autowired
private RabbitAdmin rabbitAdmin;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* key:idvalue:
*/
public static ConcurrentHashMap<String, String> execTaskMap = new ConcurrentHashMap<>();
@PostConstruct
public void init(){
Queue queue = new Queue(MQConstant.TASK_EXEC_HEARTBEAT_QUEUE);
rabbitAdmin.declareQueue(queue);
}
/**
*
*/
@Scheduled(initialDelay = 10 * 1000,fixedDelay = 60 * 1000)
public void sendTaskExecHeartbeat(){
if (execTaskMap.size() <= 0){
logger.debug("无正在执行的任务...");
return;
}
try {
rabbitTemplate.convertAndSend(MQConstant.TASK_EXEC_HEARTBEAT_QUEUE, JSONUtil.toJsonStr(execTaskMap));
} catch (Exception e) {
logger.error("发送任务执行心跳异常");
}
}
}

View File

@ -54,6 +54,11 @@ public class EnginePlanInfo {
*/
private Date createdTime;
/**
*
*/
private Long sort;
public String getId() {
return id;
}
@ -126,4 +131,12 @@ public class EnginePlanInfo {
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Long getSort() {
return sort;
}
public void setSort(Long sort) {
this.sort = sort;
}
}

View File

@ -46,4 +46,9 @@ public interface EnginePlanInfoService {
*/
void uploadFinishedFile();
/**
*
* @param id id
*/
void updatePlanInfoSort(String id);
}

View File

@ -62,6 +62,7 @@ public class EnginePlanInfoServiceImpl implements EnginePlanInfoService {
info.setBrowserType(s.getBrowserType());
}
info.setCreatedTime(new Date());
info.setSort(0l);
repository.save(info);
count++;
}
@ -178,4 +179,9 @@ public class EnginePlanInfoServiceImpl implements EnginePlanInfoService {
}
logger.debug("检查是否存在未上传的任务结果文件结束。。。。");
}
@Override
public void updatePlanInfoSort(String id) {
repository.updatePlanInfoSort(id);
}
}

View File

@ -13,7 +13,7 @@ import java.util.List;
@Repository
public interface EnginePlanInfoRepository extends JpaRepository<EnginePlanInfo,String> {
@Query(value = "select * from engine_plan_info where device_id = :deviceId and type = :type order by priority desc ,created_time asc"
@Query(value = "select * from engine_plan_info where device_id = :deviceId and type = :type order by priority desc , sort asc , created_time asc"
,nativeQuery = true)
List<EnginePlanInfo> getOneInfoByDeviceId(@Param("deviceId") String deviceId,@Param("type") String type);
@ -34,5 +34,9 @@ public interface EnginePlanInfoRepository extends JpaRepository<EnginePlanInfo,S
@Query(value = "select * from engine_plan_info where 1 = 1"
,nativeQuery = true)
List<EnginePlanInfo> selectAllPlanInfo();
@Transactional
@Modifying
@Query(value = "update engine_plan_info set sort = sort + 1 where id = :id",nativeQuery = true)
void updatePlanInfoSort(@Param("id") String id);
}

View File

@ -1514,16 +1514,25 @@ public class ScriptRuntimeExecutor implements ScriptExecutor {
* @param stepRet
*/
private void snapshot(StepExecuteResult stepRet, boolean snapshotFlag, String msg) {
String contentSnapshotPath = this.getVariableValue(DebugerConst.ReplyConst.MOBILE_STEP_SNAPSHOT);
if (snapshotFlag) {
log.info(msg);
try {
DeviceConnection deviceConnection = (DeviceConnection) this.getContextVariable(IScriptRuntimeContext.KEY_CURRENT_CONN);
String tenantId = this.getContextVariable(IScriptRuntimeContext.KEY_TASK_TENANT).toString();
String imageUrl = deviceConnection.snapshotAllScreen(tenantId, currentTaskId);
stepRet.setActualImgUri(imageUrl);
if (StringUtils.isNotBlank(contentSnapshotPath)) {
stepRet.setActualImgUri(contentSnapshotPath);
} else {
stepRet.setActualImgUri(imageUrl);
}
} catch (Exception ex) {
log.error("截图失败:", ex);
}
} else {
if (StringUtils.isNotBlank(contentSnapshotPath)) {
stepRet.setActualImgUri(contentSnapshotPath);
}
}
}
@ -1567,7 +1576,7 @@ public class ScriptRuntimeExecutor implements ScriptExecutor {
}
private void doLog(LogLevel logLevel, String msg) {
if (listener != null) {
if (listener != null && this.stepLog != null) {
this.stepLog.setLevel(logLevel.name());
this.stepLog.setLog(msg);
this.stepLog.setStepId(this.currentStepId);

View File

@ -36,8 +36,8 @@ public class HttpUtils {
static {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(15000);
factory.setConnectTimeout(30000);
factory.setReadTimeout(30000);
restTemplate = new RestTemplate(factory);
SimpleClientHttpRequestFactory downloadFactory = new SimpleClientHttpRequestFactory();

View File

@ -677,7 +677,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
public Map<String, Object> checkDeviceOffline(String planId, String batchId) {
// allOffline 0-在线1-部分离线2-全部离线
Map<String, Object> result = new HashMap<>();
if (batchId == null) {
if (StrUtil.isEmpty(batchId)) {
batchId = IdUtil.simpleUUID();
}
result.put(PlanConstant.DEVICE_OFFLINE_KEY, PlanConstant.DEVICE_OFFLINE_PORTION);
@ -2543,11 +2543,13 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
// 不是手动执行时,校验设备是否离线
if (!PlanConstant.TRIGGER_TYPE_MANUAL.equals(atuPlanRunDto.getTriggerType())){
Map<String,Object> offlineMap = checkDeviceOffline(atuPlanRunDto.getPlanId(),atuPlanRunDto.getBatchId());
logger.debug("校验设备是否离线结果: {}", JSONUtil.toJsonStr(offlineMap));
Object allOfflineObj = offlineMap.get(PlanConstant.DEVICE_OFFLINE_KEY);
if (allOfflineObj != null && PlanConstant.DEVICE_OFFLINE_ALL == Integer.parseInt(allOfflineObj.toString())) {
logger.error("该计划绑定的设备已全部离线");
return;
}
atuPlanRunDto.setBatchId(offlineMap.get(PlanConstant.PLAN_BATCH_ID).toString());
}
// 1.查询计划信息
AtuPlanInfo planInfo = this.atuPlanInfoService.findByPrimaryKey(atuPlanRunDto.getPlanId());
@ -2875,6 +2877,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
redisTemplate.delete(RedisConstant.PLAN_BATCH_OFFLINE_DEVICE + batchId);
}
}
logger.debug("离线设备id集合: {}", JSONUtil.toJsonStr(offlineDeviceIdList));
List<AtuPlanBatchDeviceLink> batchDeviceList = new ArrayList<>();
// 1.1.获取PC与移动设备集合

View File

@ -0,0 +1,16 @@
package net.northking.cctp.executePlan.api.service;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskExecResultDto;
public interface AtuPlanSceneCaseTaskApiService {
void sceneCaseHandle(AtuTaskExecResultDto taskExecResult, String clusterKeyPrefix);
void queryNextNodeInfo(AtuTaskExecResultDto taskExecResult, AtuPlanSceneCaseTask planSceneCaseTask, AtuPlanTask planTask);
void associatedActualImgAndVideo(AtuTaskExecResultDto taskExecResult);
String parseMobilePerformanceFile(AtuTaskExecResultDto taskExecResult);
}

View File

@ -0,0 +1,426 @@
package net.northking.cctp.executePlan.api.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.NKFile;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RedisConstant;
import net.northking.cctp.executePlan.db.entity.AtuPlanInfo;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanInfoService;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.dto.planBatch.AppPerInfo;
import net.northking.cctp.executePlan.dto.planBatch.DevicePerInfo;
import net.northking.cctp.executePlan.dto.planBatch.MobileTaskPerformanceDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNextNodeDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeExecDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeInfoDto;
import net.northking.cctp.executePlan.dto.planTask.*;
import net.northking.cctp.executePlan.feign.AttachmentFeignClient;
import net.northking.cctp.executePlan.feign.PublicFeignClient;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Service
public class AtuPlanSceneCaseTaskApiServiceImpl implements AtuPlanSceneCaseTaskApiService{
private static final Logger logger = LoggerFactory.getLogger(AtuPlanSceneCaseTaskApiServiceImpl.class);
@Autowired
private AtuPlanInfoService planInfoService;
@Autowired
private AtuPlanBatchApiService planBatchApiService;
@Autowired
private AtuPlanSceneCaseTaskService planSceneCaseTaskService;
@Autowired
private AtuPlanTaskApiService planTaskApiService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private PublicFeignClient publicFeignClient;
@Autowired
private AttachmentFeignClient attachmentFeignClient;
@Autowired
private SimpleStorageService simpleStorageService;
/**
*
* @param taskExecResult
*/
@Override
public void sceneCaseHandle(AtuTaskExecResultDto taskExecResult, String clusterKeyPrefix){
AtuPlanSceneCaseTask planSceneCaseTask = planSceneCaseTaskService.findByPrimaryKey(taskExecResult.getTaskId());
if (ObjectUtil.isNull(planSceneCaseTask)){
logger.error("场景节点任务[" + taskExecResult.getTaskId() + "]信息不存在");
// 根据批次和用例id查询任务信息
AtuPlanTask query = new AtuPlanTask();
query.setBatchId(taskExecResult.getBatchId());
query.setCaseId(taskExecResult.getCaseId());
List<AtuPlanTask> taskList = planTaskApiService.query(query);
if (CollUtil.isEmpty(taskList)){
logger.error("根据批次id[{}]与用例id[{}]无法查询到该任务", query.getBatchId(), query.getCaseId());
return;
}
planTaskApiService.taskExecFailUpdate(taskList.get(0), PlanConstant.TASK_EXECUTE_FAIL_STATUS, "场景节点任务信息不存在");
return;
}
//开始执行同步更新计划任务表信息
AtuPlanTask planTask = planTaskApiService.findByPrimaryKey(planSceneCaseTask.getTaskId());
if (ObjectUtil.isNull(planTask)) {
logger.error("任务[" + planSceneCaseTask.getTaskId() + "]信息不存在");
return;
}
// 判断是否开始执行
if (ObjectUtil.equal(PlanConstant.TASK_START_EXECUTE_STATUS, taskExecResult.getStatus())){
planSceneCaseTask.setStartTime(taskExecResult.getCurrentTime());
planSceneCaseTask.setEngineId(taskExecResult.getEngineId());
planSceneCaseTask.setDeviceId(taskExecResult.getDeviceId());
planSceneCaseTask.setAppId(taskExecResult.getAppId());
planSceneCaseTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
Date now = new Date();
planSceneCaseTask.setLastHeartbeatTime(now);
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
// 判断是否首节点
AtuPlanSceneCaseTask countParams = new AtuPlanSceneCaseTask();
countParams.setTaskId(planSceneCaseTask.getTaskId());
long count = planSceneCaseTaskService.count(countParams);
if (count == 1) {
planTask.setStartTime(taskExecResult.getCurrentTime());
planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
planTask.setLastHeartbeatTime(now);
planTaskApiService.updateByPrimaryKey(planTask);
if (!redisTemplate.opsForHash().hasKey(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planTask.getBatchId(),
clusterKeyPrefix + PlanConstant.BATCH_START_TIME)) {
logger.debug("批次[" + planTask.getBatchId() + "]开始执行,更新计划的状态");
planInfoService.updatePlanByLastBatchId(planTask.getBatchId(), PlanConstant.PLAN_EXECUTING_STATUS);
}
// 更新批次统计数据
planBatchApiService.updateCacheBatchSumData(planTask);
}
}else {
// 获取下一节点信息
queryNextNodeInfo(taskExecResult, planSceneCaseTask, planTask);
}
}
/**
*
* @param taskExecResult
* @param planSceneCaseTask
* @param planTask
*/
@Override
public void queryNextNodeInfo(AtuTaskExecResultDto taskExecResult, AtuPlanSceneCaseTask planSceneCaseTask,
AtuPlanTask planTask){
planSceneCaseTask.setStatus(taskExecResult.getStatus());
planSceneCaseTask.setEndTime(taskExecResult.getCurrentTime());
planSceneCaseTask.setErrorMsg(taskExecResult.getMessage());
planSceneCaseTask.setVideoUrl(taskExecResult.getVideoPath());
planSceneCaseTask.setExecResultFile(taskExecResult.getFilePath());
logger.debug("更新当前节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
AtuSceneNextNodeDto sceneNextNodeDto = new AtuSceneNextNodeDto();
//传递计划任务表id
sceneNextNodeDto.setTaskId(planSceneCaseTask.getTaskId());
sceneNextNodeDto.setCaseId(taskExecResult.getCaseId());
sceneNextNodeDto.setNodeId(planSceneCaseTask.getNodeId());
sceneNextNodeDto.setSuccess(ObjectUtil.equal(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS, taskExecResult.getStatus()));
sceneNextNodeDto.setOutputArgs(taskExecResult.getOutputs());
sceneNextNodeDto.setScriptId(planTask.getScriptId());
sceneNextNodeDto.setSceneScriptUrl(planTask.getScriptJson());
ResultWrapper<AtuSceneNodeExecDto> nextNodeResult;
try {
logger.debug("查询场景下一节点信息,参数为 => " + JSONUtil.toJsonStr(sceneNextNodeDto));
//捕获异常信息
nextNodeResult = publicFeignClient.getNextNode(sceneNextNodeDto);
if (!nextNodeResult.isSuccess()){
logger.error("获取下一节点信息结果异常," + nextNodeResult.getMessage());
planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
nextNodeResult.getMessage());
return;
}
} catch (Exception e) {
logger.error("获取下一节点信息异常:",e);
planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
"获取下一节点信息异常" + e.getMessage());
return;
}
logger.debug("查询场景下一节点信息,返回结果为 => " + JSONUtil.toJsonStr(nextNodeResult));
AtuSceneNodeExecDto sceneNodeExecDto = nextNodeResult.getData();
//更新url到表里
if (sceneNodeExecDto != null && sceneNodeExecDto.getSceneScriptUrl() != null) {
planTask.setScriptJson(sceneNodeExecDto.getSceneScriptUrl());
planTaskApiService.updateByPrimaryKey(planTask);
}
AtuSceneNodeInfoDto nodeInfo = sceneNodeExecDto.getNodeInfo();
// 节点不管成功或者失败若没有连接到结束节点默认最后节点为结束节点信息不包含lineId
planSceneCaseTask.setNextNodeId(nodeInfo.getNodeId());
planSceneCaseTask.setLineId(nodeInfo.getLineId());
// 记录性能数据
if (PlanConstant.SCRIPT_TYPE_ANDROID.equals(planSceneCaseTask.getNodeType())
|| PlanConstant.SCRIPT_TYPE_IOS.equals(planSceneCaseTask.getNodeType())) {
logger.debug("处理场景移动端性能文件数据");
planSceneCaseTask.setPerDataPath(parseMobilePerformanceFile(taskExecResult));
}
logger.debug("更新当前节点中下一节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
// 关联文件,使用 CompletableFuture 复用线程
CompletableFuture.runAsync(() -> associatedActualImgAndVideo(taskExecResult));
// future.get();
// 判断是否结束节点
if (sceneNodeExecDto.getEnd()){
logger.info("场景任务执行结束....");
//判断任务状态是否未非引擎返回的状态 取消/超时
if(ObjectUtil.equal(PlanConstant.TASK_CANCEL_STATUS, planTask.getStatus())
||ObjectUtil.equal(PlanConstant.TASK_TIMEOUT_STATUS, planTask.getStatus())){
return;
}
planTask.setEndTime(planSceneCaseTask.getEndTime());
if (StrUtil.isEmpty(nodeInfo.getLineId())) {
planTask.setStatus(planSceneCaseTask.getStatus());
planTask.setErrorMsg(planSceneCaseTask.getErrorMsg());
}else{
// 节点执行结束且连接了结束节点,则该场景任务设置成功执行
planTask.setStatus(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
}
logger.debug("更新场景任务为结束");
planTaskApiService.updateByPrimaryKey(planTask);
// 更新批次统计数据
planBatchApiService.updateCacheBatchSumData(planTask);
// 删除节点更新时的场景任务文件
String noDeleteFileId = "/" + MinioPathUtils.idToPath(sceneNodeExecDto.getSceneScriptUrl())[1];
attachmentFeignClient.clearSceneTaskAttachments(planTask.getScriptId(), FileBusinessTypeEnum.SCENE_TASK_REFRESH_FILE.getCode(), noDeleteFileId);
}else {
// 新增下一节点任务信息
try {
createNextNodeTask(planSceneCaseTask, nodeInfo, taskExecResult, planTask.getEnvId());
}catch (Exception e){
logger.error("创建下一节点任务信息失败", e);
planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS,
"创建下一节点任务信息失败" + e.getMessage());
}
}
}
/**
*
* @param planSceneCaseTask
* @param nodeInfo
* @param taskExecResult
*/
private void createNextNodeTask(AtuPlanSceneCaseTask planSceneCaseTask, AtuSceneNodeInfoDto nodeInfo,
AtuTaskExecResultDto taskExecResult, String envId){
logger.debug("保存下一节点信息");
logger.debug("节点信息:" + JSONUtil.toJsonStr(nodeInfo));
AtuPlanSceneCaseTask nextNodeTask = new AtuPlanSceneCaseTask();
nextNodeTask.setId(IdUtil.simpleUUID());
nextNodeTask.setTaskId(planSceneCaseTask.getTaskId());
nextNodeTask.setScriptId(nodeInfo.getScriptId());
nextNodeTask.setVersionId(nodeInfo.getVersionId());
nextNodeTask.setVersionName(nodeInfo.getVersionName());
nextNodeTask.setScriptName(nodeInfo.getScriptName());
nextNodeTask.setScriptJson(nodeInfo.getScriptPath());
nextNodeTask.setNodeId(nodeInfo.getNodeId());
nextNodeTask.setNodeType(nodeInfo.getNodeType());
nextNodeTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
if (CollUtil.isNotEmpty(nodeInfo.getCaseParam())) {
nextNodeTask.setNodeParams(JSONUtil.toJsonStr(nodeInfo.getCaseParam()));
}
nextNodeTask.setPrevNodeId(planSceneCaseTask.getNodeId());
nextNodeTask.setCreatedTime(new Date());
planSceneCaseTaskService.insert(nextNodeTask);
// 查询计划信息
AtuPlanInfo planInfo = planInfoService.findByPrimaryKey(taskExecResult.getPlanId());
if (ObjectUtil.isNull(planInfo)){
logger.error("计划[" + taskExecResult.getPlanId() + "]信息为空");
return;
}
logger.debug("生成任务并推送至队列");
AtuTaskExecDto atuTaskExecDto = new AtuTaskExecDto();
atuTaskExecDto.setTenantId(planInfo.getTenantId());
atuTaskExecDto.setProjectId(planInfo.getProjectId());
atuTaskExecDto.setEnvId(envId);
atuTaskExecDto.setPlanId(planInfo.getId());
atuTaskExecDto.setBatchId(taskExecResult.getBatchId());
atuTaskExecDto.setTaskId(nextNodeTask.getId());
atuTaskExecDto.setCaseId(taskExecResult.getCaseId());
atuTaskExecDto.setCaseType(PlanConstant.SCRIPT_TYPE_SCENE);
atuTaskExecDto.setScriptPath(nodeInfo.getScriptPath());
atuTaskExecDto.setCaseParams(nodeInfo.getCaseParam());
atuTaskExecDto.setAppSet(planInfo.getAppSet());
atuTaskExecDto.setScreenRecordSet(planInfo.getScreenRecordSet());
atuTaskExecDto.setScreenshotSet(planInfo.getScreenshotSet());
atuTaskExecDto.setFailRetryNum(planInfo.getFailRetryCount());
atuTaskExecDto.setAppId(planTaskApiService.queryAppId(planInfo.getId(), nodeInfo.getPlatformType(),
nodeInfo.getAppPackage(), nodeInfo.getNodeType()));
// 发送任务执行信息至消息队列
planTaskApiService.sendToQueue(atuTaskExecDto.getBatchId(), atuTaskExecDto, nodeInfo.getNodeType());
}
@Override
public void associatedActualImgAndVideo(AtuTaskExecResultDto taskExecResult) {
try {
if (StringUtils.hasText(taskExecResult.getFilePath())) {
InputStream inputStream = simpleStorageService.downloadAsStream(NKSecurityContext.getTenantId(), taskExecResult.getFilePath());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
List<String> fileUriList = new ArrayList<>();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
String actualImgUri = stepExecuteResult.getActualImgUri();
if (StringUtils.hasText(actualImgUri)) {
fileUriList.add(actualImgUri);
}
}
fileUriList.add(taskExecResult.getFilePath());
if (StringUtils.hasText(taskExecResult.getVideoPath())) {
fileUriList.add(taskExecResult.getVideoPath());
}
attachmentFeignClient.associatedFiles(fileUriList, taskExecResult.getTaskId());
}
} catch (FileDownloadException e) {
logger.error("文件下载异常", e);
} catch (Exception e) {
logger.error("文件读取异常", e);
}
}
@Override
public String parseMobilePerformanceFile(AtuTaskExecResultDto taskExecResult){
String path = "";
DevicePerInfo devicePerInfo = taskExecResult.getDevicePerInfo();
logger.debug("设备性能数据结果 => {}", JSONUtil.toJsonStr(devicePerInfo) );
if (devicePerInfo == null){
return path;
}
AtuPlanInfo planInfo = planInfoService.findByBatchId(taskExecResult.getBatchId());
MobileTaskPerformanceDto taskPerInfo = new MobileTaskPerformanceDto();
taskPerInfo.setDeviceId(taskExecResult.getDeviceId());
taskPerInfo.setAppId(taskExecResult.getAppId());
String devicePerInfoAddress = devicePerInfo.getDevicePerInfoAddress();
logger.info("任务ID{},设备性能数据地址:{}", taskExecResult.getTaskId(), devicePerInfoAddress);
if (StrUtil.isNotBlank(devicePerInfoAddress)){
DevicePerInfoDataDto deviceData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
planInfo.getTenantId(), devicePerInfoAddress, DevicePerInfoDataDto.class);
logger.debug("设备性能数据 => {}", JSONUtil.toJsonStr(deviceData));
if (deviceData != null){
taskPerInfo.setDeviceCpuList(filterData(deviceData.getCpuList()));
taskPerInfo.setDeviceMemoList(filterData(deviceData.getMemoList()));
taskPerInfo.setDeviceFlowList(filterData(deviceData.getFlowList()));
taskPerInfo.setDeviceTemperatureList(filterData(deviceData.getTemperatureList()));
}
}
AppPerInfo appPerInfo = devicePerInfo.getAppPerInfo();
if (appPerInfo != null) {
// 安装时间
taskPerInfo.setAppInstallTime(appPerInfo.getAppInstallTime());
// 启动时间
taskPerInfo.setAppActiveTime(appPerInfo.getAppActiveTime());
String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
logger.info("任务ID{}app性能数据地址{}", taskExecResult.getTaskId(), appPerInfoAddress);
if (StrUtil.isNotBlank(appPerInfoAddress)) {
AppPerInfoDataDto appData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
planInfo.getTenantId(), appPerInfoAddress, AppPerInfoDataDto.class);
logger.debug("app性能数据 => {}", JSONUtil.toJsonStr(appData) );
if (appData != null) {
taskPerInfo.setAppCpuList(filterData(appData.getCpuList()));
taskPerInfo.setAppMemoList(filterData(appData.getMemoList()));
taskPerInfo.setAppFlowList(filterData(appData.getFlowList()));
}
}
}
logger.debug("上传新的性能数据");
logger.debug("MobileTaskPerformanceDto => {}", JSONUtil.toJsonStr(taskPerInfo) );
File file = MinioPathUtils.objectToJsonFile(taskPerInfo);
try {
NKFile nkFile = simpleStorageService.upload(planInfo.getTenantId(), file, true, taskExecResult.getTaskId(), FileBusinessTypeEnum.MOBILE_TASK_PERFORMANCE_SUMMARY);
path = nkFile.getId();
logger.debug("保存任务性能数据存放地址 => {}", path);
}catch (Exception e){
logger.error("文件上传异常", e);
}
// 删除临时性能文件
if (StrUtil.isNotBlank(devicePerInfoAddress)){
String[] pathUrl = MinioPathUtils.idToPath(devicePerInfoAddress);
try {
simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
} catch (Exception e) {
logger.error("文件删除删除失败", e);
}
}
String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
if (StrUtil.isNotBlank(appPerInfoAddress)){
String[] pathUrl = MinioPathUtils.idToPath(appPerInfoAddress);
try {
simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
} catch (Exception e) {
logger.error("文件删除删除失败", e);
}
}
return path;
}
/**
* 0
* @param data
* @return
*/
private List<Double> filterData(List<Double> data){
if (data == null || data.size() <= 0){
return new ArrayList<>();
}
return data.stream().filter(num -> num > 0).collect(Collectors.toList());
}
}

View File

@ -57,6 +57,13 @@ public interface AtuPlanTaskApiService extends ExcelService
*/
String insert(AtuPlanTask task);
/**
* id
* @param task
* @return
*/
int updateByPrimaryKey(AtuPlanTask task);
/**
*
* @param batchId ID
@ -186,4 +193,17 @@ public interface AtuPlanTaskApiService extends ExcelService
List<String> getTaskIdsByScriptIdsAndPlanId(List<String> oldScriptIds, String planId);
void getActualImagePath(List<String> deletedFilePath, String execResultFile);
/**
* N
* @param timeout
* @return
*/
List<AtuPlanTask> queryExecTimeoutTask(int timeout);
/**
* id
* @param taskId id
* @return
*/
AtuPlanTaskExtendDto queryTaskExtendById(String taskId);
}

View File

@ -348,6 +348,11 @@ public class AtuPlanTaskApiServiceImpl extends AbstractExcelService<AtuPlanTask>
return task.getId();
}
@Override
public int updateByPrimaryKey(AtuPlanTask task) {
return this.atuPlanTaskService.updateByPrimaryKey(task);
}
/**
*
@ -1669,6 +1674,16 @@ public class AtuPlanTaskApiServiceImpl extends AbstractExcelService<AtuPlanTask>
}
}
@Override
public List<AtuPlanTask> queryExecTimeoutTask(int timeout) {
return atuPlanTaskService.queryExecTimeoutTask(timeout);
}
@Override
public AtuPlanTaskExtendDto queryTaskExtendById(String taskId) {
return atuPlanTaskService.queryTaskExtendById(taskId);
}
private Map<String, String> getRetryTaskInputInfo(Map<String, List<AtuPlanTask>> envScriptTaskMap,
Map<String, List<String>> envScriptMap) {
Map<String, String> taskParamsMap = new HashMap<>();

View File

@ -76,4 +76,9 @@ public class RabbitConstant {
*
*/
public static final String BATCH_SUM_UPDATE_QUEUE = "execute.plan.batch.sum.update";
/**
*
*/
public static final String TASK_EXEC_HEARTBEAT_QUEUE = "plan.task.exec.heartbeat";
}

View File

@ -0,0 +1,76 @@
package net.northking.cctp.executePlan.consumer;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RabbitConstant;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class TaskExecHeartbeatConsumer {
private final static Logger logger = LoggerFactory.getLogger(TaskExecHeartbeatConsumer.class);
@Autowired
private AmqpAdmin amqpAdmin;
@Autowired
private AtuPlanTaskService planTaskService;
@Autowired
private AtuPlanSceneCaseTaskService planSceneCaseTaskService;
@PostConstruct
public void init(){
// 初始化队列并绑定到交换机
Queue queue = new Queue(RabbitConstant.TASK_EXEC_HEARTBEAT_QUEUE);
amqpAdmin.declareQueue(queue);
}
/**
*
* @param message
*/
@RabbitListener(queues = RabbitConstant.TASK_EXEC_HEARTBEAT_QUEUE)
public void receive(Message message){
try {
logger.info("计划任务心跳 ==> " + new String(message.getBody()));
Map<String, String> taskMap = JSONUtil.toBean(new String(message.getBody()), Map.class);
taskMap.forEach(this::handleTaskHeartbeat);
}catch (Exception e){
logger.error("任务生成失败", e);
}
}
private void handleTaskHeartbeat(String taskId, String taskType){
AtuPlanTask planTask = new AtuPlanTask();
planTask.setId(taskId);
planTask.setLastHeartbeatTime(new Date());
if (PlanConstant.SCRIPT_TYPE_SCENE.equals(taskType)){
// 场景任务
AtuPlanSceneCaseTask sceneCaseTask = planSceneCaseTaskService.findByPrimaryKey(taskId);
sceneCaseTask.setLastHeartbeatTime(new Date());
planSceneCaseTaskService.updateByPrimaryKey(sceneCaseTask);
// 更新场景节点对应的任务心跳
planTask.setId(sceneCaseTask.getTaskId());
}
// 普通任务
planTaskService.updateByPrimaryKey(planTask);
}
}

View File

@ -1,58 +1,29 @@
package net.northking.cctp.executePlan.consumer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.common.dto.AssociatedFilesDto;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.NKFile;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.executePlan.api.service.AtuPlanBatchApiService;
import net.northking.cctp.executePlan.api.service.AtuPlanTaskApiService;
import net.northking.cctp.executePlan.api.service.AtuPlanSceneCaseTaskApiService;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RabbitConstant;
import net.northking.cctp.executePlan.constants.RedisConstant;
import net.northking.cctp.executePlan.db.entity.AtuPlanInfo;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanAppLinkService;
import net.northking.cctp.executePlan.db.service.AtuPlanInfoService;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskService;
import net.northking.cctp.executePlan.dto.planBatch.AppPerInfo;
import net.northking.cctp.executePlan.dto.planBatch.DevicePerInfo;
import net.northking.cctp.executePlan.dto.planBatch.MobileTaskPerformanceDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNextNodeDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeExecDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeInfoDto;
import net.northking.cctp.executePlan.dto.planTask.*;
import net.northking.cctp.executePlan.feign.AttachmentFeignClient;
import net.northking.cctp.executePlan.feign.PublicFeignClient;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskExecResultDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.*;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
/**
* <p>Title: TaskExecResultConsumer</p>
@ -71,37 +42,20 @@ public class TaskExecResultConsumer {
@Autowired
private AtuPlanTaskService planTaskService;
@Autowired
private AtuPlanTaskApiService atuPlanTaskApiService;
@Autowired
private AtuPlanSceneCaseTaskService planSceneCaseTaskService;
@Autowired
private AtuPlanInfoService atuPlanInfoService;
@Autowired
private PublicFeignClient publicFeignClient;
@Autowired
private AmqpAdmin amqpAdmin;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private AtuPlanAppLinkService atuPlanAppLinkService;
@Autowired
private AtuPlanBatchApiService planBatchApiService;
@Autowired
private SimpleStorageService simpleStorageService;
@Autowired
private AtuPlanTaskService atuPlanTaskService;
@Autowired
private AttachmentFeignClient attachmentFeignClient;
private AtuPlanSceneCaseTaskApiService sceneCaseTaskApiService;
@PostConstruct
public void init(){
@ -113,12 +67,6 @@ public class TaskExecResultConsumer {
Binding binding = BindingBuilder.bind(queue).to(exchange)
.with(RabbitConstant.TASK_EXEC_RESULT).noargs();
amqpAdmin.declareBinding(binding);
Queue taskInputQueue = new Queue(RabbitConstant.TASK_INPUT_PARAM_UPDATE);
amqpAdmin.declareQueue(taskInputQueue);
Binding inputBinding = BindingBuilder.bind(taskInputQueue).to(exchange)
.with(RabbitConstant.TASK_INPUT_PARAM_UPDATE).noargs();
amqpAdmin.declareBinding(inputBinding);
}
@ -143,7 +91,7 @@ public class TaskExecResultConsumer {
}
// 判断是否场景用例
if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, taskExecResult.getCaseType())){
sceneCaseHandle(taskExecResult, clusterKeyPrefix);
sceneCaseTaskApiService.sceneCaseHandle(taskExecResult, clusterKeyPrefix);
return;
}
// 查询任务信息
@ -164,6 +112,7 @@ public class TaskExecResultConsumer {
planTask.setDeviceId(taskExecResult.getDeviceId());
planTask.setAppId(taskExecResult.getAppId());
planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
planTask.setLastHeartbeatTime(new Date());
if (!redisTemplate.opsForHash().hasKey(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planTask.getBatchId(),
clusterKeyPrefix + PlanConstant.BATCH_START_TIME)) {
@ -182,7 +131,7 @@ public class TaskExecResultConsumer {
if (PlanConstant.SCRIPT_TYPE_ANDROID.equals(planTask.getCaseType())
|| PlanConstant.SCRIPT_TYPE_IOS.equals(planTask.getCaseType())) {
logger.debug("处理移动端性能文件数据");
planTask.setPerDataPath(parseMobilePerformanceFile(taskExecResult));
planTask.setPerDataPath(sceneCaseTaskApiService.parseMobilePerformanceFile(taskExecResult));
}
// 终态休眠一会,避免集群同时更新执行中状态造成数据异常
@ -199,395 +148,10 @@ public class TaskExecResultConsumer {
planBatchApiService.updateCacheBatchSumData(planTask);
// 关联截图文件
CompletableFuture.runAsync(() -> associatedActualImgAndVideo(taskExecResult));
CompletableFuture.runAsync(() -> sceneCaseTaskApiService.associatedActualImgAndVideo(taskExecResult));
}catch (Exception e){
logger.error("任务结果更新失败", e);
}
}
private void associatedActualImgAndVideo(AtuTaskExecResultDto taskExecResult) {
try {
if (StringUtils.hasText(taskExecResult.getFilePath())) {
InputStream inputStream = simpleStorageService.downloadAsStream(NKSecurityContext.getTenantId(), taskExecResult.getFilePath());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
List<String> fileUriList = new ArrayList<>();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
String actualImgUri = stepExecuteResult.getActualImgUri();
if (StringUtils.hasText(actualImgUri)) {
fileUriList.add(actualImgUri);
}
}
fileUriList.add(taskExecResult.getFilePath());
if (StringUtils.hasText(taskExecResult.getVideoPath())) {
fileUriList.add(taskExecResult.getVideoPath());
}
attachmentFeignClient.associatedFiles(fileUriList, taskExecResult.getTaskId());
}
} catch (FileDownloadException e) {
logger.error("文件下载异常", e);
} catch (Exception e) {
logger.error("文件读取异常", e);
}
}
private String parseMobilePerformanceFile(AtuTaskExecResultDto taskExecResult){
String path = "";
DevicePerInfo devicePerInfo = taskExecResult.getDevicePerInfo();
logger.debug("设备性能数据结果 => {}", JSONUtil.toJsonStr(devicePerInfo) );
if (devicePerInfo == null){
return path;
}
AtuPlanInfo planInfo = atuPlanInfoService.findByBatchId(taskExecResult.getBatchId());
MobileTaskPerformanceDto taskPerInfo = new MobileTaskPerformanceDto();
taskPerInfo.setDeviceId(taskExecResult.getDeviceId());
taskPerInfo.setAppId(taskExecResult.getAppId());
String devicePerInfoAddress = devicePerInfo.getDevicePerInfoAddress();
logger.info("任务ID{},设备性能数据地址:{}", taskExecResult.getTaskId(), devicePerInfoAddress);
if (StrUtil.isNotBlank(devicePerInfoAddress)){
DevicePerInfoDataDto deviceData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
planInfo.getTenantId(), devicePerInfoAddress, DevicePerInfoDataDto.class);
logger.debug("设备性能数据 => {}", JSONUtil.toJsonStr(deviceData));
if (deviceData != null){
taskPerInfo.setDeviceCpuList(filterData(deviceData.getCpuList()));
taskPerInfo.setDeviceMemoList(filterData(deviceData.getMemoList()));
taskPerInfo.setDeviceFlowList(filterData(deviceData.getFlowList()));
taskPerInfo.setDeviceTemperatureList(filterData(deviceData.getTemperatureList()));
}
}
AppPerInfo appPerInfo = devicePerInfo.getAppPerInfo();
if (appPerInfo != null) {
// 安装时间
taskPerInfo.setAppInstallTime(appPerInfo.getAppInstallTime());
// 启动时间
taskPerInfo.setAppActiveTime(appPerInfo.getAppActiveTime());
String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
logger.info("任务ID{}app性能数据地址{}", taskExecResult.getTaskId(), appPerInfoAddress);
if (StrUtil.isNotBlank(appPerInfoAddress)) {
AppPerInfoDataDto appData = MinioPathUtils.downloadFileCoverObj(simpleStorageService,
planInfo.getTenantId(), appPerInfoAddress, AppPerInfoDataDto.class);
logger.debug("app性能数据 => {}", JSONUtil.toJsonStr(appData) );
if (appData != null) {
taskPerInfo.setAppCpuList(filterData(appData.getCpuList()));
taskPerInfo.setAppMemoList(filterData(appData.getMemoList()));
taskPerInfo.setAppFlowList(filterData(appData.getFlowList()));
}
}
}
logger.debug("上传新的性能数据");
logger.debug("MobileTaskPerformanceDto => {}", JSONUtil.toJsonStr(taskPerInfo) );
File file = MinioPathUtils.objectToJsonFile(taskPerInfo);
try {
NKFile nkFile = simpleStorageService.upload(planInfo.getTenantId(), file, true, taskExecResult.getTaskId(), FileBusinessTypeEnum.MOBILE_TASK_PERFORMANCE_SUMMARY);
path = nkFile.getId();
logger.debug("保存任务性能数据存放地址 => {}", path);
}catch (Exception e){
logger.error("文件上传异常", e);
}
// 删除临时性能文件
if (StrUtil.isNotBlank(devicePerInfoAddress)){
String[] pathUrl = MinioPathUtils.idToPath(devicePerInfoAddress);
try {
simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
} catch (Exception e) {
logger.error("文件删除删除失败", e);
}
}
String appPerInfoAddress = appPerInfo.getAppPerInfoAddress();
if (StrUtil.isNotBlank(appPerInfoAddress)){
String[] pathUrl = MinioPathUtils.idToPath(appPerInfoAddress);
try {
simpleStorageService.delete(pathUrl[0], "/" + pathUrl[1]);
} catch (Exception e) {
logger.error("文件删除删除失败", e);
}
}
return path;
}
/**
* 0
* @param data
* @return
*/
private List<Double> filterData(List<Double> data){
if (data == null || data.size() <= 0){
return new ArrayList<>();
}
return data.stream().filter(num -> num > 0).collect(Collectors.toList());
}
/**
*
* @param taskExecResult
*/
private void sceneCaseHandle(AtuTaskExecResultDto taskExecResult, String clusterKeyPrefix){
AtuPlanSceneCaseTask planSceneCaseTask = planSceneCaseTaskService.findByPrimaryKey(taskExecResult.getTaskId());
if (ObjectUtil.isNull(planSceneCaseTask)){
logger.error("场景节点任务[" + taskExecResult.getTaskId() + "]信息不存在");
return;
}
//开始执行同步更新计划任务表信息
AtuPlanTask planTask = planTaskService.findByPrimaryKey(planSceneCaseTask.getTaskId());
if (ObjectUtil.isNull(planTask)) {
logger.error("任务[" + planSceneCaseTask.getTaskId() + "]信息不存在");
return;
}
// 判断是否开始执行
if (ObjectUtil.equal(PlanConstant.TASK_START_EXECUTE_STATUS, taskExecResult.getStatus())){
planSceneCaseTask.setStartTime(taskExecResult.getCurrentTime());
planSceneCaseTask.setEngineId(taskExecResult.getEngineId());
planSceneCaseTask.setDeviceId(taskExecResult.getDeviceId());
planSceneCaseTask.setAppId(taskExecResult.getAppId());
planSceneCaseTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
// 判断是否首节点
AtuPlanSceneCaseTask countParams = new AtuPlanSceneCaseTask();
countParams.setTaskId(planSceneCaseTask.getTaskId());
long count = planSceneCaseTaskService.count(countParams);
if (count == 1) {
planTask.setStartTime(taskExecResult.getCurrentTime());
planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
planTaskService.updateByPrimaryKey(planTask);
if (!redisTemplate.opsForHash().hasKey(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planTask.getBatchId(),
clusterKeyPrefix + PlanConstant.BATCH_START_TIME)) {
logger.debug("批次[" + planTask.getBatchId() + "]开始执行,更新计划的状态");
atuPlanInfoService.updatePlanByLastBatchId(planTask.getBatchId(), PlanConstant.PLAN_EXECUTING_STATUS);
}
// 更新批次统计数据
planBatchApiService.updateCacheBatchSumData(planTask);
}
}else {
// 获取下一节点信息
queryNextNodeInfo(taskExecResult, planSceneCaseTask, planTask.getScriptId(), planTask.getEnvId(),planTask.getScriptJson());
}
}
/**
*
* @param taskExecResult
* @param planSceneCaseTask
* @param scriptUrl
*/
private void queryNextNodeInfo(AtuTaskExecResultDto taskExecResult,
AtuPlanSceneCaseTask planSceneCaseTask,
String sceneId,
String envId,
String scriptUrl){
planSceneCaseTask.setStatus(taskExecResult.getStatus());
planSceneCaseTask.setEndTime(taskExecResult.getCurrentTime());
planSceneCaseTask.setErrorMsg(taskExecResult.getMessage());
planSceneCaseTask.setVideoUrl(taskExecResult.getVideoPath());
planSceneCaseTask.setExecResultFile(taskExecResult.getFilePath());
logger.debug("更新当前节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
AtuSceneNextNodeDto sceneNextNodeDto = new AtuSceneNextNodeDto();
//传递计划任务表id
sceneNextNodeDto.setTaskId(planSceneCaseTask.getTaskId());
sceneNextNodeDto.setCaseId(taskExecResult.getCaseId());
sceneNextNodeDto.setNodeId(planSceneCaseTask.getNodeId());
sceneNextNodeDto.setSuccess(ObjectUtil.equal(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS, taskExecResult.getStatus()));
sceneNextNodeDto.setOutputArgs(taskExecResult.getOutputs());
sceneNextNodeDto.setScriptId(sceneId);
sceneNextNodeDto.setSceneScriptUrl(scriptUrl);
ResultWrapper<AtuSceneNodeExecDto> nextNodeResult;
try {
logger.debug("查询场景下一节点信息,参数为 => " + JSONUtil.toJsonStr(sceneNextNodeDto));
//捕获异常信息
nextNodeResult = publicFeignClient.getNextNode(sceneNextNodeDto);
if (!nextNodeResult.isSuccess()){
logger.error("获取下一节点信息结果异常," + nextNodeResult.getMessage());
sceneTaskExceptionEnd(planSceneCaseTask.getTaskId(), nextNodeResult.getMessage());
return;
}
} catch (Exception e) {
logger.error("获取下一节点信息异常:",e);
sceneTaskExceptionEnd(planSceneCaseTask.getTaskId(), "获取下一节点信息异常");
return;
}
logger.debug("查询场景下一节点信息,返回结果为 => " + JSONUtil.toJsonStr(nextNodeResult));
AtuSceneNodeExecDto sceneNodeExecDto = nextNodeResult.getData();
//更新url到表里
if (sceneNodeExecDto != null && sceneNodeExecDto.getSceneScriptUrl() != null) {
AtuPlanTask byPrimaryKey = atuPlanTaskService.findByPrimaryKey(planSceneCaseTask.getTaskId());
AtuPlanTask planTask = new AtuPlanTask();
planTask.setId(planSceneCaseTask.getTaskId());
planTask.setScriptJson(sceneNodeExecDto.getSceneScriptUrl());
int rows = atuPlanTaskService.updateByPrimaryKey(planTask);
}
AtuSceneNodeInfoDto nodeInfo = sceneNodeExecDto.getNodeInfo();
// 节点不管成功或者失败若没有连接到结束节点默认最后节点为结束节点信息不包含lineId
planSceneCaseTask.setNextNodeId(nodeInfo.getNodeId());
planSceneCaseTask.setLineId(nodeInfo.getLineId());
// 记录性能数据
if (PlanConstant.SCRIPT_TYPE_ANDROID.equals(planSceneCaseTask.getNodeType())
|| PlanConstant.SCRIPT_TYPE_IOS.equals(planSceneCaseTask.getNodeType())) {
logger.debug("处理场景移动端性能文件数据");
planSceneCaseTask.setPerDataPath(parseMobilePerformanceFile(taskExecResult));
}
logger.debug("更新当前节点中下一节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
planSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);
// 关联文件,使用 CompletableFuture 复用线程
CompletableFuture.runAsync(() -> associatedActualImgAndVideo(taskExecResult));
// future.get();
// 判断是否结束节点
if (sceneNodeExecDto.getEnd()){
logger.info("场景任务执行结束....");
// 更新任务表信息
AtuPlanTask planTask = planTaskService.findByPrimaryKey(planSceneCaseTask.getTaskId());
if (ObjectUtil.isNull(planTask)){
logger.error("任务[" + planSceneCaseTask.getTaskId() + "]信息不存在");
return;
}
//判断任务状态是否未非引擎返回的状态 取消/超时
if(ObjectUtil.equal(PlanConstant.TASK_CANCEL_STATUS, planTask.getStatus())
||ObjectUtil.equal(PlanConstant.TASK_TIMEOUT_STATUS, planTask.getStatus())){
return;
}
planTask.setEndTime(planSceneCaseTask.getEndTime());
if (StrUtil.isEmpty(nodeInfo.getLineId())) {
planTask.setStatus(planSceneCaseTask.getStatus());
planTask.setErrorMsg(planSceneCaseTask.getErrorMsg());
}else{
// 节点执行结束且连接了结束节点,则该场景任务设置成功执行
planTask.setStatus(PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
}
logger.debug("更新场景任务为结束");
planTaskService.updateByPrimaryKey(planTask);
// 更新批次统计数据
planBatchApiService.updateCacheBatchSumData(planTask);
// 删除节点更新时的场景任务文件
String noDeleteFileId = "/" + MinioPathUtils.idToPath(sceneNodeExecDto.getSceneScriptUrl())[1];
attachmentFeignClient.clearSceneTaskAttachments(sceneId, FileBusinessTypeEnum.SCENE_TASK_REFRESH_FILE.getCode(), noDeleteFileId);
}else {
// 新增下一节点任务信息
try {
createNextNodeTask(planSceneCaseTask, nodeInfo, taskExecResult, envId);
}catch (Exception e){
logger.error("创建下一节点任务信息失败", e);
sceneTaskExceptionEnd(planSceneCaseTask.getTaskId(), "创建下一节点任务信息失败" + e.getMessage());
}
}
}
/**
*
* @param planSceneCaseTask
* @param nodeInfo
* @param taskExecResult
*/
private void createNextNodeTask(AtuPlanSceneCaseTask planSceneCaseTask, AtuSceneNodeInfoDto nodeInfo,
AtuTaskExecResultDto taskExecResult, String envId){
logger.debug("保存下一节点信息");
logger.debug("节点信息:" + JSONUtil.toJsonStr(nodeInfo));
AtuPlanSceneCaseTask nextNodeTask = new AtuPlanSceneCaseTask();
nextNodeTask.setId(IdUtil.simpleUUID());
nextNodeTask.setTaskId(planSceneCaseTask.getTaskId());
nextNodeTask.setScriptId(nodeInfo.getScriptId());
nextNodeTask.setVersionId(nodeInfo.getVersionId());
nextNodeTask.setVersionName(nodeInfo.getVersionName());
nextNodeTask.setScriptName(nodeInfo.getScriptName());
nextNodeTask.setScriptJson(nodeInfo.getScriptPath());
nextNodeTask.setNodeId(nodeInfo.getNodeId());
nextNodeTask.setNodeType(nodeInfo.getNodeType());
nextNodeTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
if (CollUtil.isNotEmpty(nodeInfo.getCaseParam())) {
nextNodeTask.setNodeParams(JSONUtil.toJsonStr(nodeInfo.getCaseParam()));
}
nextNodeTask.setPrevNodeId(planSceneCaseTask.getNodeId());
nextNodeTask.setCreatedTime(new Date());
planSceneCaseTaskService.insert(nextNodeTask);
// 查询计划信息
AtuPlanInfo planInfo = atuPlanInfoService.findByPrimaryKey(taskExecResult.getPlanId());
if (ObjectUtil.isNull(planInfo)){
logger.error("计划[" + taskExecResult.getPlanId() + "]信息为空");
return;
}
logger.debug("生成任务并推送至队列");
AtuTaskExecDto atuTaskExecDto = new AtuTaskExecDto();
atuTaskExecDto.setTenantId(planInfo.getTenantId());
atuTaskExecDto.setProjectId(planInfo.getProjectId());
atuTaskExecDto.setEnvId(envId);
atuTaskExecDto.setPlanId(planInfo.getId());
atuTaskExecDto.setBatchId(taskExecResult.getBatchId());
atuTaskExecDto.setTaskId(nextNodeTask.getId());
atuTaskExecDto.setCaseId(taskExecResult.getCaseId());
atuTaskExecDto.setCaseType(PlanConstant.SCRIPT_TYPE_SCENE);
atuTaskExecDto.setScriptPath(nodeInfo.getScriptPath());
atuTaskExecDto.setCaseParams(nodeInfo.getCaseParam());
atuTaskExecDto.setAppSet(planInfo.getAppSet());
atuTaskExecDto.setScreenRecordSet(planInfo.getScreenRecordSet());
atuTaskExecDto.setScreenshotSet(planInfo.getScreenshotSet());
atuTaskExecDto.setFailRetryNum(planInfo.getFailRetryCount());
atuTaskExecDto.setAppId(atuPlanTaskApiService.queryAppId(planInfo.getId(), nodeInfo.getPlatformType(),
nodeInfo.getAppPackage(), nodeInfo.getNodeType()));
// 发送任务执行信息至消息队列
atuPlanTaskApiService.sendToQueue(atuTaskExecDto.getBatchId(), atuTaskExecDto, nodeInfo.getNodeType());
}
/**
*
* @param taskId ID
* @param errorMsg
*/
private void sceneTaskExceptionEnd(String taskId, String errorMsg){
AtuPlanTask planTask = planTaskService.findByPrimaryKey(taskId);
planTask.setStatus(PlanConstant.TASK_EXECUTE_FAIL_STATUS);
planTask.setEndTime(System.currentTimeMillis());
planTask.setErrorMsg(errorMsg);
planTaskService.updateByPrimaryKey(planTask);
// 更新批次统计数据
planBatchApiService.updateCacheBatchSumData(planTask);
}
/**
*
*
* @param message
*/
@Transactional(rollbackFor = Exception.class)
@RabbitListener(queues = RabbitConstant.TASK_INPUT_PARAM_UPDATE)
public void receiveTaskInputParam(Message message) {
logger.info("接收信息 ==> " + new String(message.getBody()));
try {
JSONObject event = JSONUtil.parseObj(new String(message.getBody()));
AtuTaskExecDto receiveTask = JSONUtil.toBean(event, AtuTaskExecDto.class);
if (PlanConstant.SCRIPT_TYPE_SCENE.equals(receiveTask.getCaseType())) {
// 更新到场景任务表
AtuPlanSceneCaseTask sceneTask = new AtuPlanSceneCaseTask();
sceneTask.setId(receiveTask.getTaskId());
sceneTask.setNodeParams(JSONUtil.toJsonStr(receiveTask.getCaseParams()));
planSceneCaseTaskService.updateByPrimaryKey(sceneTask);
} else {
// 更新到任务表
AtuPlanTask updateTask = new AtuPlanTask();
updateTask.setId(receiveTask.getTaskId());
updateTask.setCaseParam(JSONUtil.toJsonStr(receiveTask.getCaseParams()));
planTaskService.updateByPrimaryKey(updateTask);
}
} catch (Exception e) {
logger.error("更新任务的输入参数失败", e);
}
}
}

View File

@ -0,0 +1,75 @@
package net.northking.cctp.executePlan.consumer;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RabbitConstant;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskService;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskExecDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
public class TaskInputParamConsumer {
private final static Logger logger = LoggerFactory.getLogger(TaskInputParamConsumer.class);
@Autowired
private AtuPlanSceneCaseTaskService planSceneCaseTaskService;
@Autowired
private AtuPlanTaskService planTaskService;
@Autowired
private AmqpAdmin amqpAdmin;
@PostConstruct
public void init(){
// 初始化队列并绑定到交换机
Exchange exchange = new DirectExchange(RabbitConstant.PROJECT_EXCHANGE_KEY);
amqpAdmin.declareExchange(exchange);
Queue queue = new Queue(RabbitConstant.TASK_INPUT_PARAM_UPDATE);
amqpAdmin.declareQueue(queue);
Binding binding = BindingBuilder.bind(queue).to(exchange)
.with(RabbitConstant.TASK_INPUT_PARAM_UPDATE).noargs();
amqpAdmin.declareBinding(binding);
}
/**
*
*
* @param message
*/
@Transactional(rollbackFor = Exception.class)
@RabbitListener(queues = RabbitConstant.TASK_INPUT_PARAM_UPDATE)
public void receiveTaskInputParam(Message message) {
logger.info("接收信息 ==> " + new String(message.getBody()));
try {
JSONObject event = JSONUtil.parseObj(new String(message.getBody()));
AtuTaskExecDto receiveTask = JSONUtil.toBean(event, AtuTaskExecDto.class);
if (PlanConstant.SCRIPT_TYPE_SCENE.equals(receiveTask.getCaseType())) {
// 更新到场景任务表
AtuPlanSceneCaseTask sceneTask = new AtuPlanSceneCaseTask();
sceneTask.setId(receiveTask.getTaskId());
sceneTask.setNodeParams(JSONUtil.toJsonStr(receiveTask.getCaseParams()));
planSceneCaseTaskService.updateByPrimaryKey(sceneTask);
} else {
// 更新到任务表
AtuPlanTask updateTask = new AtuPlanTask();
updateTask.setId(receiveTask.getTaskId());
updateTask.setCaseParam(JSONUtil.toJsonStr(receiveTask.getCaseParams()));
planTaskService.updateByPrimaryKey(updateTask);
}
} catch (Exception e) {
logger.error("更新任务的输入参数失败", e);
}
}
}

View File

@ -79,4 +79,10 @@ public interface AtuPlanSceneCaseTaskDao extends AtuPlanSceneCaseTaskMapper
List<AtuPlanSceneCaseTask> querySceneCaseTasksByTaskIds(@Param("taskIds") List<String> taskIds);
/**
* N
* @param timeout
* @return
*/
List<AtuPlanSceneCaseTask> queryExecTimeoutTask(int timeout);
}

View File

@ -7,6 +7,7 @@ package net.northking.cctp.executePlan.db.dao;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.mapper.AtuPlanTaskMapper;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskExtendDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskPageDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskQueryDto;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskSendBugDto;
@ -109,4 +110,17 @@ public interface AtuPlanTaskDao extends AtuPlanTaskMapper
List<AtuPlanTask> queryTasksByBatchIds(@Param("batchIds") List<String> batchIds);
/**
* N
* @param timeout
* @return
*/
List<AtuPlanTask> queryExecTimeoutTask(int timeout);
/**
* id
* @param id id
* @return
*/
AtuPlanTaskExtendDto queryTaskExtendById(String id);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) Corporation 2023 . All rights reserved.
* Copyright (c) Corporation 2024 . All rights reserved.
*
*/
package net.northking.cctp.executePlan.db.entity;
@ -17,7 +17,7 @@ import java.util.Date;
*
*
* <p> <br>
* createdate: 2023-11-13 14:35:51 <br>
* createdate: 2024-07-03 18:59:03 <br>
* @author: maven-cctp-plugin <br>
* @since: 1.0 <br>
*/
@ -48,6 +48,7 @@ public class AtuPlanSceneCaseTask extends Partition implements Serializable
public static final String KEY_execResultFile = "execResultFile";
public static final String KEY_perDataPath = "perDataPath";
public static final String KEY_createdTime = "createdTime";
public static final String KEY_lastHeartbeatTime = "lastHeartbeatTime";
/**
* <br>
@ -189,6 +190,11 @@ public class AtuPlanSceneCaseTask extends Partition implements Serializable
*/
@ApiModelProperty("创建时间")
private Date createdTime;
/**
* <br>
*/
@ApiModelProperty("执行心跳时间")
private Date lastHeartbeatTime;
/**
* <br>
@ -605,6 +611,30 @@ public class AtuPlanSceneCaseTask extends Partition implements Serializable
this.createdTime = (Date)createdTime.clone();
}
}
/**
* <br>
*/
public Date getLastHeartbeatTime()
{
if(lastHeartbeatTime != null)
{
return (Date)lastHeartbeatTime.clone();
}
return null;
}
/**
* <br>
*
* @param lastHeartbeatTime
*/
public void setLastHeartbeatTime(Date lastHeartbeatTime)
{
if(lastHeartbeatTime != null)
{
this.lastHeartbeatTime = (Date)lastHeartbeatTime.clone();
}
}
public AtuPlanSceneCaseTask()
{

View File

@ -49,6 +49,7 @@ public class AtuPlanTask extends Partition implements Serializable
public static final String KEY_bugId = "bugId";
public static final String KEY_perDataPath = "perDataPath";
public static final String KEY_createdTime = "createdTime";
public static final String KEY_lastHeartbeatTime = "lastHeartbeatTime";
/**
* <br>
@ -196,6 +197,11 @@ public class AtuPlanTask extends Partition implements Serializable
*/
@ApiModelProperty("创建时间")
private Date createdTime;
/**
* <br>
*/
@ApiModelProperty("执行心跳时间")
private Date lastHeartbeatTime;
/**
* <br>
@ -629,6 +635,30 @@ public class AtuPlanTask extends Partition implements Serializable
this.createdTime = (Date)createdTime.clone();
}
}
/**
* <br>
*/
public Date getLastHeartbeatTime()
{
if(lastHeartbeatTime != null)
{
return (Date)lastHeartbeatTime.clone();
}
return null;
}
/**
* <br>
*
* @param lastHeartbeatTime
*/
public void setLastHeartbeatTime(Date lastHeartbeatTime)
{
if(lastHeartbeatTime != null)
{
this.lastHeartbeatTime = (Date)lastHeartbeatTime.clone();
}
}
public AtuPlanTask()
{

View File

@ -93,6 +93,11 @@ private static final Logger logger = LoggerFactory.getLogger(AtuPlanSceneCaseTas
return this.atuPlanSceneCaseTaskDao.querySceneCaseTasksByTaskIds(taskIds);
}
@Override
public List<AtuPlanSceneCaseTask> queryExecTimeoutTask(int timeout) {
return this.atuPlanSceneCaseTaskDao.queryExecTimeoutTask(timeout);
}
// ---- The End by Generator ----//

View File

@ -11,6 +11,7 @@ import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.executePlan.db.dao.AtuPlanTaskDao;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskService;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskExtendDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskPageDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskQueryDto;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskSendBugDto;
@ -156,6 +157,16 @@ private static final Logger logger = LoggerFactory.getLogger(AtuPlanTaskServiceI
return this.atuPlanTaskDao.queryTasksByBatchIds(batchIds);
}
@Override
public List<AtuPlanTask> queryExecTimeoutTask(int timeout) {
return this.atuPlanTaskDao.queryExecTimeoutTask(timeout);
}
@Override
public AtuPlanTaskExtendDto queryTaskExtendById(String taskId) {
return this.atuPlanTaskDao.queryTaskExtendById(taskId);
}
// ---- The End by Generator ----//

View File

@ -42,5 +42,6 @@ public interface AtuPlanSceneCaseTaskMapper extends BasicDao<AtuPlanSceneCaseTas
String COLUMN_execResultFile = "exec_result_file";
String COLUMN_perDataPath = "per_data_path";
String COLUMN_createdTime = "created_time";
String COLUMN_lastHeartbeatTime = "last_heartbeat_time";
}

View File

@ -43,5 +43,6 @@ public interface AtuPlanTaskMapper extends BasicDao<AtuPlanTask>
String COLUMN_bugId = "bug_id";
String COLUMN_perDataPath = "per_data_path";
String COLUMN_createdTime = "created_time";
String COLUMN_lastHeartbeatTime = "last_heartbeat_time";
}

View File

@ -79,4 +79,10 @@ public interface AtuPlanSceneCaseTaskService extends BasicService<AtuPlanSceneCa
*/
List<AtuPlanSceneCaseTask> querySceneCaseTasksByTaskIds(List<String> taskIds);
/**
* N
* @param timeout
* @return
*/
List<AtuPlanSceneCaseTask> queryExecTimeoutTask(int timeout);
}

View File

@ -7,6 +7,7 @@ package net.northking.cctp.executePlan.db.service;
import net.northking.cctp.common.db.BasicService;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskExtendDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskPageDto;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskQueryDto;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskSendBugDto;
@ -113,4 +114,17 @@ public interface AtuPlanTaskService extends BasicService<AtuPlanTask>
*/
List<AtuPlanTask> queryTasksByBatchIds(List<String> batchIds);
/**
* N
* @param timeout
* @return
*/
List<AtuPlanTask> queryExecTimeoutTask(int timeout);
/**
* id
* @param taskId id
* @return
*/
AtuPlanTaskExtendDto queryTaskExtendById(String taskId);
}

View File

@ -0,0 +1,26 @@
package net.northking.cctp.executePlan.dto.planTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
public class AtuPlanTaskExtendDto extends AtuPlanTask {
private String planId;
private String planName;
public String getPlanId() {
return planId;
}
public void setPlanId(String planId) {
this.planId = planId;
}
public String getPlanName() {
return planName;
}
public void setPlanName(String planName) {
this.planName = planName;
}
}

View File

@ -0,0 +1,85 @@
package net.northking.cctp.executePlan.job;
import cn.hutool.core.collection.CollUtil;
import net.northking.cctp.executePlan.api.service.AtuPlanSceneCaseTaskApiService;
import net.northking.cctp.executePlan.api.service.AtuPlanTaskApiService;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask;
import net.northking.cctp.executePlan.db.entity.AtuPlanTask;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskExtendDto;
import net.northking.cctp.executePlan.dto.planTask.AtuTaskExecResultDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.List;
@EnableScheduling
@Configuration
public class TaskExecTimeoutJob {
private final static Logger logger = LoggerFactory.getLogger(TaskExecTimeoutJob.class);
@Autowired
private AtuPlanTaskApiService planTaskApiService;
@Autowired
private AtuPlanSceneCaseTaskService sceneCaseTaskService;
@Autowired
private AtuPlanSceneCaseTaskApiService sceneCaseTaskApiService;
/**
* 3
*/
public static final int TIMEOUT = 3 * 60;
public static final String ERROR_MSG = "引擎执行任务超时";
@Scheduled(initialDelay = 10 * 1000,fixedDelay = 60 * 1000)
private void handleTaskExecTimeout(){
// 查询正在执行中且心跳时间超过n分钟的任务
List<AtuPlanTask> taskList = planTaskApiService.queryExecTimeoutTask(TIMEOUT);
if (CollUtil.isNotEmpty(taskList)){
logger.debug("超时任务数量:{}", taskList.size());
for (AtuPlanTask planTask : taskList) {
planTaskApiService.taskExecFailUpdate(planTask, PlanConstant.TASK_TIMEOUT_STATUS, ERROR_MSG);
}
}
List<AtuPlanSceneCaseTask> sceneCaseTaskList = sceneCaseTaskService.queryExecTimeoutTask(TIMEOUT);
if (CollUtil.isNotEmpty(sceneCaseTaskList)){
logger.debug("超时场景节点任务数量:{}", sceneCaseTaskList.size());
AtuTaskExecResultDto taskExecResult = new AtuTaskExecResultDto();
taskExecResult.setCaseType(PlanConstant.SCRIPT_TYPE_SCENE);
taskExecResult.setStatus(PlanConstant.TASK_TIMEOUT_STATUS);
taskExecResult.setMessage(ERROR_MSG);
taskExecResult.setCurrentTime(System.currentTimeMillis());
for (AtuPlanSceneCaseTask sceneCaseTask : sceneCaseTaskList) {
AtuPlanTaskExtendDto taskExtendDto = planTaskApiService.queryTaskExtendById(sceneCaseTask.getTaskId());
if (taskExtendDto == null){
logger.error("任务[{}]信息不存在", sceneCaseTask.getTaskId());
logger.error("删除任务[{}]的节点任务信息", sceneCaseTask.getTaskId());
AtuPlanSceneCaseTask delParams = new AtuPlanSceneCaseTask();
delParams.setTaskId(sceneCaseTask.getTaskId());
sceneCaseTaskService.deleteByExample(delParams);
continue;
}
taskExecResult.setPlanId(taskExtendDto.getPlanId());
taskExecResult.setBatchId(taskExtendDto.getBatchId());
taskExecResult.setCaseId(taskExtendDto.getCaseId());
taskExecResult.setTaskId(sceneCaseTask.getId());
AtuPlanTask planTask = new AtuPlanTask();
BeanUtils.copyProperties(taskExtendDto, planTask);
// 场景节点执行失败,执行下一节点
sceneCaseTaskApiService.queryNextNodeInfo(taskExecResult, sceneCaseTask, planTask);
}
}
}
}

View File

@ -9,7 +9,7 @@
<resultMap id="BaseResultMap" type="net.northking.cctp.executePlan.db.entity.AtuPlanSceneCaseTask">
<!-- 主键 -->
<id column="id" jdbcType="VARCHAR" property="id"/>
<!-- 任务主键 -->
<!-- 任务主键 -->
<result column="task_id" jdbcType="VARCHAR" property="taskId"/>
<!-- 脚本主键 -->
<result column="script_id" jdbcType="VARCHAR" property="scriptId"/>
@ -55,6 +55,8 @@
<result column="per_data_path" jdbcType="VARCHAR" property="perDataPath"/>
<!-- 创建时间 -->
<result column="created_time" jdbcType="TIMESTAMP" property="createdTime"/>
<!-- 执行心跳时间 -->
<result column="last_heartbeat_time" jdbcType="TIMESTAMP" property="lastHeartbeatTime"/>
</resultMap>
<sql id="Base_Column_List">
@ -82,6 +84,7 @@
,exec_result_file
,per_data_path
,created_time
,last_heartbeat_time
</sql>
<sql id="Table_Name">
@ -202,6 +205,9 @@
</if>
<if test="createdTime != null">
AND created_time=#{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time=#{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</delete>
@ -282,6 +288,9 @@
<if test="createdTime != null">
created_time,
</if>
<if test="lastHeartbeatTime != null">
last_heartbeat_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -356,6 +365,9 @@
<if test="createdTime != null">
#{createdTime, jdbcType=TIMESTAMP},
</if>
<if test="lastHeartbeatTime != null">
#{lastHeartbeatTime, jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
@ -387,6 +399,7 @@
exec_result_file,
per_data_path,
created_time,
last_heartbeat_time,
</trim>
values
<foreach collection="list" item="item" index="index" separator=",">
@ -415,6 +428,7 @@
#{item.execResultFile, jdbcType=VARCHAR},
#{item.perDataPath, jdbcType=VARCHAR},
#{item.createdTime, jdbcType=TIMESTAMP},
#{item.lastHeartbeatTime, jdbcType=TIMESTAMP},
</trim>
</foreach>
</insert>
@ -492,6 +506,9 @@
<if test="createdTime != null">
created_time = #{createdTime, jdbcType=TIMESTAMP},
</if>
<if test="lastHeartbeatTime != null">
last_heartbeat_time = #{lastHeartbeatTime, jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -574,6 +591,9 @@
<if test="createdTime != null">
AND created_time = #{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time = #{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</select>
@ -653,6 +673,9 @@
<if test="createdTime != null">
AND created_time=#{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time=#{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</select>

View File

@ -57,6 +57,8 @@
<result column="per_data_path" jdbcType="VARCHAR" property="perDataPath"/>
<!-- 创建时间 -->
<result column="created_time" jdbcType="TIMESTAMP" property="createdTime"/>
<!-- 执行心跳时间 -->
<result column="last_heartbeat_time" jdbcType="TIMESTAMP" property="lastHeartbeatTime"/>
</resultMap>
<sql id="Base_Column_List">
@ -85,6 +87,7 @@
,bug_id
,per_data_path
,created_time
,last_heartbeat_time
</sql>
<sql id="Table_Name">
@ -208,6 +211,9 @@
</if>
<if test="createdTime != null">
AND created_time=#{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time=#{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</delete>
@ -291,6 +297,9 @@
<if test="createdTime != null">
created_time,
</if>
<if test="lastHeartbeatTime != null">
last_heartbeat_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -368,6 +377,9 @@
<if test="createdTime != null">
#{createdTime, jdbcType=TIMESTAMP},
</if>
<if test="lastHeartbeatTime != null">
#{lastHeartbeatTime, jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
@ -400,6 +412,7 @@
bug_id,
per_data_path,
created_time,
last_heartbeat_time,
</trim>
values
<foreach collection="list" item="item" index="index" separator=",">
@ -429,6 +442,7 @@
#{item.bugId, jdbcType=VARCHAR},
#{item.perDataPath, jdbcType=VARCHAR},
#{item.createdTime, jdbcType=TIMESTAMP},
#{item.lastHeartbeatTime, jdbcType=TIMESTAMP},
</trim>
</foreach>
</insert>
@ -509,6 +523,9 @@
<if test="createdTime != null">
created_time = #{createdTime, jdbcType=TIMESTAMP},
</if>
<if test="lastHeartbeatTime != null">
last_heartbeat_time = #{lastHeartbeatTime, jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -594,6 +611,9 @@
<if test="createdTime != null">
AND created_time = #{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time = #{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</select>
@ -676,6 +696,9 @@
<if test="createdTime != null">
AND created_time=#{createdTime,jdbcType=TIMESTAMP}
</if>
<if test="lastHeartbeatTime != null">
AND last_heartbeat_time=#{lastHeartbeatTime,jdbcType=TIMESTAMP}
</if>
</trim>
</select>

View File

@ -220,8 +220,15 @@
#{taskId}
</foreach>
</select>
<select id="queryExecTimeoutTask" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from
<include refid="Table_Name"/>
where status = 1 and timestampdiff(second, last_heartbeat_time, NOW()) > #{timeout}
</select>
<delete id="deleteByPlanIdLimit">
<delete id="deleteByPlanIdLimit">
delete from atu_plan_scene_case_task
where task_id in (select id from atu_plan_task
where batch_id in (select id from atu_plan_batch

View File

@ -53,6 +53,13 @@
<result column="created_time" jdbcType="TIMESTAMP" property="createdTime"/>
</resultMap>
<resultMap id="TaskExtendMap" extends="BaseResultMap" type="net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskExtendDto">
<!-- 计划主键 -->
<result column="plan_id" jdbcType="VARCHAR" property="planId"/>
<!-- 脚本主键 -->
<result column="plan_name" jdbcType="VARCHAR" property="planName"/>
</resultMap>
<select id="queryList" resultMap="DetailMap" parameterType="net.northking.cctp.executePlan.dto.planTask.AtuPlanTaskQueryDto">
select
id
@ -307,9 +314,17 @@
#{batchId}
</foreach>
</select>
<select id="queryExecTimeoutTask" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/> from <include refid="Table_Name"/>
where status = 1 and case_type != 5 and timestampdiff(second, last_heartbeat_time, NOW()) > #{timeout}
</select>
<select id="queryTaskExtendById" resultMap="TaskExtendMap">
SELECT t.*, b.plan_id from <include refid="Table_Name"/> t
left join atu_plan_batch b on b.id = t.batch_id
where t.id = #{id}
</select>
<delete id="deleteByPlanIdLimit">
<delete id="deleteByPlanIdLimit">
DELETE FROM <include refid="Table_Name"/>
where batch_id in (select id from atu_plan_batch where plan_id = #{planId})
LIMIT #{num}

View File

@ -51,4 +51,8 @@ public interface AutomationRequestCmd {
String INPUT_PASSWORD_BY_OCR = "input_password_by_ocr"; //输入密码ocr
String GET_ELEMENT_MONEY_TEXT = "get_element_money_text"; //识别金额
String SCREEN_SHOT = "screen_shot"; //屏幕截图
String GET_ELEMENT_VALUE_BY_PATH_OCR = "get_element_value_by_path_ocr"; //获取控件的值OCR方式
}

View File

@ -57,10 +57,11 @@ public class AtuElementInfoRestfulCtrl
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultWrapper<String> add(
@RequestBody @Validated AtuElementInfoAddDto inputDto)
@RequestHeader(AuthDependencyConstants.REQUEST_HEADER_PROJECT_ID) String projectId,
@RequestBody @Validated AtuElementInfoAddDto inputDto)
{
ResultWrapper<String> wrapper = new ResultWrapper<>();
String uuid = apiService.add(inputDto);
String uuid = apiService.add(inputDto, projectId);
return wrapper.success(uuid);
}
@ -76,10 +77,11 @@ public class AtuElementInfoRestfulCtrl
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultWrapper<Integer> update(
@RequestBody @Validated AtuElementInfoUpdateDto inputDto) throws IllegalAccessException
@RequestHeader(AuthDependencyConstants.REQUEST_HEADER_PROJECT_ID) String projectId,
@RequestBody @Validated AtuElementInfoUpdateDto inputDto) throws IllegalAccessException
{
ResultWrapper<Integer> wrapper = new ResultWrapper<>();
Integer count = apiService.updateByPK(inputDto);
Integer count = apiService.updateByPK(inputDto, projectId);
return wrapper.success(count);
}

View File

@ -34,7 +34,7 @@ public interface AtuElementInfoApiService extends ExcelService
* @param dto
* @return
*/
String add(AtuElementInfoAddDto dto);
String add(AtuElementInfoAddDto dto, String projectId);
/**
*
@ -42,7 +42,7 @@ public interface AtuElementInfoApiService extends ExcelService
* @param dto
* @return
*/
Integer updateByPK(AtuElementInfoUpdateDto dto) throws IllegalAccessException;
Integer updateByPK(AtuElementInfoUpdateDto dto, String projectId) throws IllegalAccessException;
/**
*

View File

@ -298,13 +298,18 @@ public class AtuElementInfoApiServiceImpl extends AbstractExcelService<AtuElemen
@Override
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public String add(AtuElementInfoAddDto dto)
public String add(AtuElementInfoAddDto dto, String projectId)
{
//元素目录校验
AtuElementGroup elementGroup = atuElementGroupService.findByPrimaryKey(dto.getGroupId());
if (null == elementGroup || ScriptConstant.STR_ZERO.equals(elementGroup.getIsLeaf())) {
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_DIR_IS_VALID);
}
//元素名称校验
List<AtuElementInfo> atuElementInfos = atuElementInfoService.findByName(dto.getName(), projectId);
if (!CollectionUtils.isEmpty(atuElementInfos)) {
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_IS_EXISTS);
}
// 校验获取方式是否为空
if (CollUtil.isEmpty(dto.getFetchTypeList())){
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_FETCH_TYPE_NO_EXISTS);
@ -374,13 +379,18 @@ public class AtuElementInfoApiServiceImpl extends AbstractExcelService<AtuElemen
@Override
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Integer updateByPK(AtuElementInfoUpdateDto dto) throws IllegalAccessException
public Integer updateByPK(AtuElementInfoUpdateDto dto, String projectId) throws IllegalAccessException
{
//参数校验
AtuElementInfo oldElement = atuElementInfoService.findByPrimaryKey(dto.getId());
if (oldElement == null || ScriptConstant.STR_ONE.equals(oldElement.getIsDeleted())) {
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_NO_EXISTS);
}
//元素名称校验
List<AtuElementInfo> atuElementInfos = atuElementInfoService.findByNameButNotSelf(dto.getName(), dto.getId(), projectId);
if (!CollectionUtils.isEmpty(atuElementInfos)) {
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_IS_EXISTS);
}
// 校验获取方式是否为空
if (CollUtil.isEmpty(dto.getFetchTypeList())){
throw new PlatformRuntimeException(ElementLibraryError.ELEMENT_FETCH_TYPE_NO_EXISTS);

View File

@ -35,6 +35,8 @@ public interface AtuElementInfoDao extends AtuElementInfoMapper
List<String> selectUpdaterByProject(@Param("projectId") String projectId);
List<AtuElementInfo> findByName(@Param("name") String name, @Param("projectId") String projectId);
List<AtuElementInfo> findByNameButNotSelf(@Param("name") String name, @Param("id") String id, @Param("projectId") String projectId);
}

View File

@ -70,5 +70,25 @@ private static final Logger logger = LoggerFactory.getLogger(AtuElementInfoServi
public List<String> selectUpdaterByProject(String projectId) {
return atuElementInfoDao.selectUpdaterByProject(projectId);
}
/**
*
* @param name
* @return
*/
@Override
public List<AtuElementInfo> findByName(String name, String projectId) {
return atuElementInfoDao.findByName(name, projectId);
}
/**
*
* @param name
* @return
*/
@Override
public List<AtuElementInfo> findByNameButNotSelf(String name, String id, String projectId) {
return atuElementInfoDao.findByNameButNotSelf(name, id, projectId);
}
}

View File

@ -49,7 +49,18 @@ public interface AtuElementInfoService extends BasicService<AtuElementInfo>
List<String> selectUpdaterByProject(String projectId);
/**
*
* @param name
* @return
*/
List<AtuElementInfo> findByName(String name, String projectId);
/**
*
* @param name
* @return
*/
List<AtuElementInfo> findByNameButNotSelf(String name, String id, String projectId);
}

View File

@ -41,6 +41,9 @@ public class AtuScriptInfoDetailDto extends AtuScriptInfo implements Serializabl
private List<Object> inputList;
@ApiModelProperty("脚本输出项参数")
private List<Object> outputList;
@ApiModelProperty("分组信息")
private String namePath;
public List<Object> getInputList() {
return inputList;
@ -105,4 +108,12 @@ public class AtuScriptInfoDetailDto extends AtuScriptInfo implements Serializabl
public void setPrincipal(String principal) {
this.principal = principal;
}
public String getNamePath() {
return namePath;
}
public void setNamePath(String namePath) {
this.namePath = namePath;
}
}

View File

@ -20,6 +20,7 @@ public enum ElementLibraryError implements PlatformError {
ELEMENT_MOVE_NO_LEAF("请选择叶子节点目录移动!"),
ELEMENT_VALUE_IS_TOO_LONG("元素属性值超出长度限制!"),
ELEMENT_NO_EXISTS("当前元素不存在,请刷新列表"),
ELEMENT_IS_EXISTS("当前元素已存在,元素名称不可重复"),
ELEMENT_FETCH_TYPE_NO_EXISTS("元素获取方式为空,请检查");
private static final int START_CODE = 220000;

View File

@ -203,6 +203,25 @@
WHERE is_deleted = 0 AND project_id = #{projectId}
GROUP BY updated_by
</select>
<select id="findByName" resultType="net.northking.cctp.scriptcase.db.entity.AtuElementInfo">
select
<include refid="Base_Column_List"/>
from
<include refid="Table_Name"/>
where is_deleted = '0'
and name = #{name}
and project_id = #{projectId}
</select>
<select id="findByNameButNotSelf" resultType="net.northking.cctp.scriptcase.db.entity.AtuElementInfo">
select
<include refid="Base_Column_List"/>
from
<include refid="Table_Name"/>
where is_deleted = '0'
and id != #{id}
and name = #{name}
and project_id = #{projectId}
</select>
<update id="deleteElementBatch" parameterType="net.northking.cctp.scriptcase.db.entity.AtuElementInfo">
update
<include refid="Table_Name"/>

View File

@ -46,6 +46,8 @@
<result column="app_package" jdbcType="VARCHAR" property="appPackage"/>
<!-- 版本名称 -->
<result column="version_name" jdbcType="VARCHAR" property="versionName"/>
<!-- 分组 -->
<result column="name_path" jdbcType="VARCHAR" property="namePath"/>
<!-- 应用平台 -->
<result column="platform" jdbcType="VARCHAR" property="platform"/>
@ -154,9 +156,12 @@
,b.version_name
,a.platform
,a.principal_id
,c.name_path
from ${defaultSchema}.atu_script_info a
left join ${defaultSchema}.atu_script_version b
on (a.id = b.script_id and a.latest_version_id = b.version_id)
left join atu_script_group c
on a.group_id = c.id
<trim prefix="WHERE" prefixOverrides="AND" >
<if test="query.projectIds != null and query.projectIds.size > 0">
AND a.project_id IN

View File

@ -51,4 +51,8 @@ public interface AutomationRequestCmd {
String INPUT_PASSWORD_BY_OCR = "input_password_by_ocr"; //输入密码ocr
String GET_ELEMENT_MONEY_TEXT = "get_element_money_text"; //识别金额
String SCREEN_SHOT = "screen_shot"; //屏幕截图
String GET_ELEMENT_VALUE_BY_PATH_OCR = "get_element_value_by_path_ocr"; //获取控件的值OCR方式
}

View File

@ -83,4 +83,9 @@ public interface UpperParamKey {
*
*/
String USING_TYPE = "using_type";
/**
* id
*/
String TENANT_ID = "tenant_id";
}

View File

@ -75,6 +75,10 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand
inputPasswordByOcr(request);
} else if (AutomationRequestCmd.GET_ELEMENT_MONEY_TEXT.equals(cmd)) {
getElementMoneyText(request);
} else if (AutomationRequestCmd.SCREEN_SHOT.equals(cmd)) {
screenShot(request);
} else if (AutomationRequestCmd.GET_ELEMENT_VALUE_BY_PATH_OCR.equals(cmd)) {
getElementValueByPathOcr(request);
}
}

View File

@ -184,4 +184,14 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{
public void getElementMoneyText(CmdAutomationRequest request) {
}
@Override
public void screenShot(CmdAutomationRequest request) {
}
@Override
public void getElementValueByPathOcr(CmdAutomationRequest request) {
}
}

View File

@ -76,4 +76,8 @@ public interface AutomationMessageHandler {
void getElementMoneyText(CmdAutomationRequest request); //识别金额
void screenShot(CmdAutomationRequest request); //识别金额
void getElementValueByPathOcr(CmdAutomationRequest request); ////获取控件的值OCR方式
}

View File

@ -9,12 +9,15 @@ import net.northking.cctp.upperComputer.automation.constants.Command;
import net.northking.cctp.upperComputer.automation.constants.UpperParamKey;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
import net.northking.cctp.upperComputer.config.MobileProperty;
import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager;
import net.northking.cctp.upperComputer.deviceManager.thread.IosDeviceInitThread;
import net.northking.cctp.upperComputer.driver.ios.NKAgent;
import net.northking.cctp.upperComputer.driver.ios.command.data.*;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.entity.PhoneEntity;
import net.northking.cctp.upperComputer.enums.FileBusinessTypeEnum;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.service.IosDebuggerServiceImpl;
import net.northking.cctp.upperComputer.utils.HttpUtils;
@ -142,7 +145,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
if (!deviceHandleHelper.isAppInstalled(phoneEntity.getUdid(), appPackage)) {
response = CmdAutomationResponse.builderSuccess(request, "app安装失败");
response = CmdAutomationResponse.builderFailure(request, "app安装失败");
return;
}
logger.info("activate app[{}]", appPackage);
@ -195,10 +198,10 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
elapsedTime = currentTime - startTime;
}
if (null == uiNodeData) {
response = CmdAutomationResponse.builderSuccess(request, "元素不存在");
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
} else {
if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) {
response = CmdAutomationResponse.builderSuccess(request, "元素不存在");
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
} else {
TapXYData tapXYData = new TapXYData();
tapXYData.setX(uiNodeData.getX());
@ -233,7 +236,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
if (null == tapXYData) {
response = CmdAutomationResponse.builderSuccess(request, "查找控件失败").withData(false);
response = CmdAutomationResponse.builderFailure(request, "查找控件失败").withData(false);
} else {
response = CmdAutomationResponse.builderSuccess(request, "查找控件完成").withData(tapXYData);
}
@ -337,14 +340,14 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
if (null != uiNodeData) {
if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) {
response = CmdAutomationResponse.builderSuccess(request, "找不到控件");
response = CmdAutomationResponse.builderFailure(request, "找不到控件");
return;
}
if (StringUtils.isNotBlank(value)) {
response = CmdAutomationResponse.builderSuccess(request, "已获取控件的值").withData(value);
}
} else {
response = CmdAutomationResponse.builderSuccess(request, "找不到控件");
response = CmdAutomationResponse.builderFailure(request, "找不到控件");
}
} catch (Exception e) {
logger.error("根据xpath获取控件的值失败原因", e);
@ -1052,22 +1055,26 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
//转换比例
int width = phoneEntity.getScreenWidth() * phoneEntity.getScale();
int height = phoneEntity.getScreenHeight() * phoneEntity.getScale();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
Double realityX = 0.0;
Double realityY = 0.0;
if(data.get(UpperParamKey.X) != null) {
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
//转换比例
int width = phoneEntity.getScreenWidth() * phoneEntity.getScale();
int height = phoneEntity.getScreenHeight() * phoneEntity.getScale();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
}
ocrParamMap.put("img_base64", base64Str);
ocrParamMap.put("targets", ocrText);
HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
@ -1314,7 +1321,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
if (clickSuccess) {
response = CmdAutomationResponse.builderSuccess(request, "点击控件成功").withData(clickSuccess);
} else {
response = CmdAutomationResponse.builderSuccess(request, "点击控件失败").withData(false);
response = CmdAutomationResponse.builderFailure(request, "点击控件失败").withData(false);
}
} catch (Exception e) {
logger.error("根据ocr点击控件失败原因", e);
@ -1379,7 +1386,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
} else if ("1".equalsIgnoreCase(type)) {
logger.info("启动ios的app:{}", appPackage);
if (!deviceHandleHelper.isAppInstalled(phoneEntity.getUdid(), appPackage)) {
response = CmdAutomationResponse.builderSuccess(request, "app未安装");
response = CmdAutomationResponse.builderFailure(request, "app未安装");
return;
}
boolean success = deviceHandleHelper.activateApp(phoneEntity.getUdid(), appPackage);
@ -1391,7 +1398,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
} else {
logger.info("重启ios的app:{}", appPackage);
if (!deviceHandleHelper.isAppInstalled(phoneEntity.getUdid(), appPackage)) {
response = CmdAutomationResponse.builderSuccess(request, "app未安装");
response = CmdAutomationResponse.builderFailure(request, "app未安装");
return;
}
boolean success = iosService.terminateApp(serial, appPackage);
@ -1511,11 +1518,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
logger.error("请求ocr接口失败接口地址{}",ocrAddress,e);
}
logger.info("识别金额为:{}", value);
if (StringUtils.isNotBlank(value)) {
response = CmdAutomationResponse.builderSuccess(request, "识别金额成功").withData(value);
} else {
response = CmdAutomationResponse.builderFailure(request, "识别金额失败");
}
response = CmdAutomationResponse.builderSuccess(request, "识别金额成功").withData(value);
}
} catch (Exception e) {
logger.error("识别失败,原因:", e);
@ -1525,6 +1528,137 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
@Override
public void screenShot(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "屏幕截图失败");
logger.debug("开始上传截图,信息:{}", JSON.toJSONString(request));
try {
String serial = phoneEntity.getUdid();
File file = ScreenShotUtils.getIOSMobileScreenShot(serial);
Map<String, Object> data = request.getData();
String path = null;
try {
//租户id
String tenantId = (String) data.get(UpperParamKey.TENANT_ID);
String serverAddr = SpringUtils.getProperties("nk.mobile-computer.serverAddr");
String publicUploadAddr = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
Attachment upload = HttpUtils.upload(serverAddr + publicUploadAddr, file.getAbsolutePath(), tenantId, "", FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
if (null != upload && org.apache.commons.lang3.StringUtils.isNotBlank(upload.getId())) {
logger.debug("文件上传成功返回id{}", upload.getId());
path = upload.getUrlPath();
}
} catch (Exception e) {
logger.error("截图失败", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
if (file.exists()) {
boolean delete = file.delete();
if (!delete) {
logger.warn("临时文件【{}】删除失败", file.getAbsolutePath());
response = CmdAutomationResponse.builderFailure(request, "临时文件【{}】删除失败");
}
}
}
if (StringUtils.isNotBlank(path)) {
response = CmdAutomationResponse.builderSuccess(request, "屏幕截图成功").withData(path);
}
} catch (Exception e) {
logger.error("屏幕截图失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void getElementValueByPathOcr(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
try {
Map<String, Object> data = request.getData();
Integer waitTimeout = (Integer) data.get(UpperParamKey.WAIT_TIMEOUT);
NKAgent nkAgent = getNkAgent();
if (null == nkAgent) {
response = CmdAutomationResponse.builderFailure(request, "设备与上位机连接断开");
return;
}
String nodeTree = (String) data.get(UpperParamKey.NODE_TREE);
logger.debug("拿到的nodeTree:{}", nodeTree);
SearchUiNodeData searchUiNodeData = new SearchUiNodeData();
searchUiNodeData.setChain(nodeTree);
UiNodeData uiNodeData = null;
String value = null;
long startTime = System.currentTimeMillis(); //当前时间
long elapsedTime = 0; // 初始化经过的时间
while(elapsedTime < waitTimeout * 1000 -300) {
uiNodeData = nkAgent.uiNodeInfo(searchUiNodeData);
// logger.info("拿到的uiNodeData{}", uiNodeData);
if (uiNodeData != null && !(uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0)) {
value = getValueByOcr(uiNodeData);
break;
}
long currentTime = System.currentTimeMillis();
elapsedTime = currentTime - startTime - 1000;
}
response = CmdAutomationResponse.builderSuccess(request, "获取控件的值OCR方式成功").withData(value);
} catch (Exception e) {
logger.error("根据xpath判断元素是否存在失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally{
logger.info("根据nodetree查找元素失败的response{}", response);
sendResultToEngine(response);
}
}
private String getValueByOcr(UiNodeData uiNodeData) {
String value = null;
String imgBase64 = getOcrAreaByBodeToBase64(uiNodeData);
String ocrAddress = SpringUtils.getProperties("nk.http-request-path.ocrGetAllTextNum");
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("img_base64", imgBase64);
ocrParamMap.put("targets", "");
HttpEntity<Map> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
List ocrResultList = null;
try {
logger.info("传参:{}", JSON.toJSONString(ocrParamMap));
ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
logger.info("得到的ocr结果{}", ocrResultList);
} catch (Exception e) {
logger.error("ocr失败", e);
}
List<String> dataList = new ArrayList<>();
if (!CollectionUtils.isEmpty(ocrResultList)) {
if (!CollectionUtils.isEmpty(ocrResultList)) {
for (Object o : ocrResultList) {
dataList.add(((Map) o).get("text") + "");
}
}
}
value = JSON.toJSONString(dataList);
} catch (Exception e) {
logger.error("请求ocr接口失败接口地址{}",ocrAddress,e);
}
logger.info("识别文字为:{}", value);
return value;
}
private String getOcrAreaByBodeToBase64(UiNodeData uiNodeData) {
Integer x = uiNodeData.getX();
Integer y = uiNodeData.getY();
Integer screenWidth = phoneEntity.getScreenWidth();
Integer screenHeight = phoneEntity.getScreenHeight();
Integer cutWidth = uiNodeData.getWidth();
Integer cutHeight = uiNodeData.getHeight();
logger.debug("拿到的截图参数----->>>>>x:{},y:{},screenWidth:{},screenHeight:{},cutWidth:{},cutHeight:{}", x, y, screenWidth, screenHeight, cutWidth, cutHeight);
File file = deviceHandleHelper.getScreenShotFile(serial, x, y, cutWidth, cutHeight, screenWidth, screenHeight);
logger.debug("ocr截图{}", file.getAbsolutePath());
String base64Str = getFileBase64(file);
return base64Str;
}
private void getKeyBoardInfo(String ocrArea) {
Map<String, JSONObject> itemMap = new HashMap<>();
HttpHeaders httpHeaders = new HttpHeaders();

View File

@ -921,7 +921,7 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService {
if (!tmpPicDir.exists()) {
tmpPicDir.mkdir();
}
String tmpPicPath = tmpPicDir.getAbsolutePath() + "/" + System.currentTimeMillis() + ".png";
String tmpPicPath = tmpPicDir.getAbsolutePath() + "/" + deviceId + "_" + System.currentTimeMillis() + ".png";
tmpPicFile = new File(tmpPicPath);
try (FileOutputStream fos = new FileOutputStream(tmpPicFile)){
fos.write(screenShotData);
@ -932,7 +932,7 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService {
} catch (Exception e) {
logger.error("设备【"+deviceId+"】截图异常", e);
}finally {
logger.debug("设备【{}】写到本地的图片大小:{}");
logger.debug("设备【{}】写到本地的图片大小:{}", deviceId, tmpPicFile != null ? tmpPicFile.length() : 0);
}
return tmpPicFile;
}

View File

@ -577,7 +577,7 @@ public abstract class AbstractIosMessageHandlerThread extends AbstractMessageHan
return;
}
}
screenResponseThread.startRecordScreen((String) params.get("tenantId"), catchParam.getRealWidth(), catchParam.getRealHeight(), "1");
screenResponseThread.startRecordScreen((String) params.get("tenantId"), catchParam.getRealWidth(), catchParam.getRealHeight(), "1", null);
SessionUtils.sendSuccessData(session, request, null, "开启录屏成功");
}
@ -660,7 +660,7 @@ public abstract class AbstractIosMessageHandlerThread extends AbstractMessageHan
screenResponseThread = IOSDeviceManager.getInstance().getScreenThread(phoneEntity.getUdid());
if (null == screenResponseThread || screenResponseThread.isInterrupted() || !screenResponseThread.isAlive()) {
//读取手机端响应线程
screenResponseThread = new IosScreenResponseThread(phoneEntity);
screenResponseThread = new IosScreenResponseThread(phoneEntity, null);
screenResponseThread.start();
screenResponseThread.startSendScreenToWeb(session, catchParam);
screenResponseThread.setScreenOnRequest(request);

View File

@ -16,6 +16,7 @@ import net.northking.cctp.upperComputer.driver.agent.command.*;
import net.northking.cctp.upperComputer.driver.agent.command.data.PackageInfo;
import net.northking.cctp.upperComputer.driver.agent.command.protocol.ProtocolCommand;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.enums.FileBusinessTypeEnum;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.exception.ParamMistakeException;
import net.northking.cctp.upperComputer.utils.HttpUtils;
@ -153,7 +154,7 @@ public class AndroidMessageHandlerThread extends AbstractMessageHandler {
//上传到服务器
String serverIp = SpringUtils.getProperties("nk.mobile-computer.serverAddr");
String fileUploadPath = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
String fileId = HttpUtils.doUpload(serverIp + fileUploadPath, tmpFile, tenantId);
String fileId = HttpUtils.doUpload(serverIp + fileUploadPath, tmpFile, tenantId,null, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
logger.info("上传文件到服务器完成:{}", fileId);
Map<String, Object> result = new HashMap<>();
result.put("fileName", fileName);
@ -1151,7 +1152,7 @@ public class AndroidMessageHandlerThread extends AbstractMessageHandler {
String pubUploadPath = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
Attachment upload = null;
try {
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId);
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId,null,FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
} catch (Exception e) {
logger.debug("[{}]上传截图失败", adbDevice.getSerial(),e);
}

View File

@ -16,6 +16,7 @@ import net.northking.cctp.upperComputer.driver.ios.command.data.TypeKeysUiNodeDa
import net.northking.cctp.upperComputer.driver.ios.packet.EmptyCommandData;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.entity.PhoneEntity;
import net.northking.cctp.upperComputer.enums.FileBusinessTypeEnum;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.exception.ParamMistakeException;
import net.northking.cctp.upperComputer.utils.HttpUtils;
@ -688,7 +689,7 @@ public class IosMacMessageHandlerThread extends AbstractIosMessageHandlerThread
String pubUploadPath = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
Attachment upload = null;
try {
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId);
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId, null, FileBusinessTypeEnum.MOBILE_RECORD_SCREENSHOT.getCode());
} catch (Exception e) {
logger.debug("[{}]上传截图失败", phoneEntity.getUdid(), e);
}

View File

@ -67,6 +67,8 @@ public class IosScreenResponseThread extends Thread {
private boolean sendDeviceStatus = true;
private String currentTaskId;
// private IosScreenCompressHandleThread screenCompressHandle;
@Override
@ -225,11 +227,10 @@ public class IosScreenResponseThread extends Thread {
/**
* @param phone
*/
public IosScreenResponseThread(PhoneEntity phone) {
// screenCompressHandle = new IosScreenCompressHandleThread(phone.getUdid());
// screenCompressHandle.start();
public IosScreenResponseThread(PhoneEntity phone, String currentTaskId) {
this.phone = phone;
setName(phone.getUdid() + "拉取屏幕");
this.currentTaskId = currentTaskId;
}
private UsbMuxd.ConnectOperator getDeviceConnectOperator(UsbMuxd usbMuxd) {
@ -307,15 +308,15 @@ public class IosScreenResponseThread extends Thread {
interrupt();
}
public void startRecordScreen(String tenantId, int width, int height, String recordType) {
public void startRecordScreen(String tenantId, int width, int height,String recordType, String currentTaskId){
this.recordingType = recordType;
if (null != screenRecordThread && screenRecordThread.isAlive()) {
screenRecordThread.killRecord();
}
SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
String dateFormat = format.format(new Date());
String videoName = this.phone.getUdid() + "_" + dateFormat + ".mp4";
screenRecordThread = new IosScreenRecordThread(this.phone.getUdid(), videoName, tenantId, width, height);
String videoName = this.phone.getUdid() + "_" + dateFormat + ".mp4";
screenRecordThread = new IosScreenRecordThread(this.phone.getUdid(), videoName, tenantId,width, height, currentTaskId);
screenRecordThread.start();
}
@ -331,12 +332,12 @@ public class IosScreenResponseThread extends Thread {
}
//自动化结束录屏
public String stopRecord(String tenantId) {
public String stopRecord(String tenantId,boolean isSave) {
this.recordingType = "0";
if (null == screenRecordThread || screenRecordThread.isInterrupted() || !screenRecordThread.isAlive()) {
return null;
}
String result = screenRecordThread.endRecord(tenantId);
String result = screenRecordThread.endRecord(tenantId,isSave);
JSONObject object = JSONObject.parseObject(result, JSONObject.class);
screenRecordThread = null;
String videoUrl = object.getString("videoUrl");

View File

@ -10,6 +10,7 @@ import net.northking.cctp.upperComputer.deviceManager.thread.IosDeviceInitThread
import net.northking.cctp.upperComputer.driver.ios.NKAgent;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.entity.PhoneEntity;
import net.northking.cctp.upperComputer.enums.FileBusinessTypeEnum;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.exception.ParamMistakeException;
import net.northking.cctp.upperComputer.utils.HttpUtils;
@ -709,7 +710,7 @@ public class IosWindowsAndLinuxMessageHandlerThread extends AbstractIosMessageHa
String pubUploadPath = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
Attachment upload = null;
try {
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId);
upload = HttpUtils.upload(serverAddress + pubUploadPath, screenShotData, tenantId, null, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
} catch (Exception e) {
logger.debug("[{}]上传截图失败", phoneEntity.getUdid(), e);
}

View File

@ -17,7 +17,7 @@ spring:
nk:
mobile-computer:
password: 123456
keepScreenOn: true #<EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>true<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>agentʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD>false<EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>Ļ
keepScreenOn: true #是否点亮屏幕如果true启动agent时不会关闭手机屏幕false则会关闭手机屏幕
idPath: /home/attect/cctp-mobile
ctrlIp: 172.16.77.25
stfPath: /home/yc/soft/cctp-mobile
@ -36,26 +36,26 @@ nk:
http-request-path:
useNewOcr: true
ocrServer: http://192.168.0.33:5000
ocrFindArea: ${nk.http-request-path.ocrServer}/WebAPI/FindTextCoordinateByOcr #ocr<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD>λ<EFBFBD><EFBFBD>
imgFindArea: ${nk.http-request-path.ocrServer}/WebAPI/findImageInImage #ͼƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>
ocrGetText: ${nk.http-request-path.ocrServer}/WebAPI/GetTextByOcr #ͼƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>
ocrGetVerificationCode: http://158.58.160.183:8000/yzm_v2 #<EFBFBD>°<EFBFBD>ocr<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD>
ocrGetAllTextNum: http://158.58.160.183:8000/rpa_v2 #<EFBFBD>°<EFBFBD>ocr<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>
getSafeKeyBoardNum: http://158.58.160.183:8000/keyboard_v2 #<EFBFBD>°<EFBFBD>ocr<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
hzBankOcrAddress: http://197.68.24.38:9283/gateway/spring-ocrcloud-platform/OCRA0001 #<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>OCR<EFBFBD><EFBFBD><EFBFBD><EFBFBD>nginx<EFBFBD><EFBFBD>ַ
ocrFindArea: ${nk.http-request-path.ocrServer}/WebAPI/FindTextCoordinateByOcr #ocr查找文本位置
imgFindArea: ${nk.http-request-path.ocrServer}/WebAPI/findImageInImage #图片查找位置
ocrGetText: ${nk.http-request-path.ocrServer}/WebAPI/GetTextByOcr #图片查找位置
ocrGetVerificationCode: http://158.58.160.183:8000/yzm_v2 #新版ocr获取验证码
ocrGetAllTextNum: http://158.58.160.183:8000/rpa_v2 #新版ocr获取金额
getSafeKeyBoardNum: http://158.58.160.183:8000/keyboard_v2 #新版ocr获取安全键盘
hzBankOcrAddress: http://197.68.24.38:9283/gateway/spring-ocrcloud-platform/OCRA0001 #杭州银行OCR请求nginx地址
macos:
build-wda:
build-wda: true #是否重新构建wda
xcode-username: mac01 #mac电脑的用户名不能是root
key-chain-password: testtest #mac系统的密码
wda-project-path: /Users/mac01/Downloads/WebDriverAgent #wda工程代码位置
xcode14-path: /Applications/Xcode14/Xcode.app #xcode14位置
xcode15-path: /Applications/Xcode.app #xcode15位置
wda-for-ios17-below-package-path: /Users/mac01/Downloads/wda14/ #打包后wda包的位置,为ios17以下使用
wda-for-ios17-above-package-path: /Users/mac01/Downloads/wda15/ #打包后wda包的位置,为ios17及以上使用
wda-for-ios17-below-package-default-path: /Users/mac01/Downloads/wda14/Build/Products/WebDriverAgentRunner_iphoneos16.4-arm64.xctestrun #如果不打包wda指定wda的路径,为ios17及以下使用
wda-for-ios17-above-package-default-path: /Users/mac01/Downloads/wda15/Build/Products/WebDriverAgentRunner_iphoneos17.4-arm64.xctestrun #如果不打包wda指定wda的路径,为ios17及以上使用
print-wda-output: true #是否在日志中打印wda的输出
build-wda: true #是否重新构建wda
xcode-username: mac01 #mac电脑的用户名不能是root
key-chain-password: testtest #mac系统的密码
wda-project-path: /Users/mac01/Downloads/WebDriverAgent #wda工程代码位置
xcode14-path: /Applications/Xcode14/Xcode.app #xcode14位置
xcode15-path: /Applications/Xcode.app #xcode15位置
wda-for-ios17-below-package-path: /Users/mac01/Downloads/wda14/ #打包后wda包的位置,为ios17以下使用
wda-for-ios17-above-package-path: /Users/mac01/Downloads/wda15/ #打包后wda包的位置,为ios17及以上使用
wda-for-ios17-below-package-default-path: /Users/mac01/Downloads/wda14/Build/Products/WebDriverAgentRunner_iphoneos16.4-arm64.xctestrun #如果不打包wda指定wda的路径,为ios17及以下使用
wda-for-ios17-above-package-default-path: /Users/mac01/Downloads/wda15/Build/Products/WebDriverAgentRunner_iphoneos17.4-arm64.xctestrun #如果不打包wda指定wda的路径,为ios17及以上使用
print-wda-output: true #是否在日志中打印wda的输出
logging:
level:
net.northking: debug

30
cctp-production/.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Compiled class file
*.class
# Log file
*.log
*.log.gz
logs/
*/logs/
*/*/logs/
# Temp file
*.temp
temp
*/temp
# IDEA profile dir
.idea/
*/.idea/
*/*/.idea/
# IDEA project file
*.iml
*/*.iml
*/*/*.iml
*/*/*/*.iml
target/
*/target/
*/*/target/

View File

@ -2,6 +2,7 @@ package net.northking.cctp.device.api.activity.service;
import net.northking.cctp.common.db.Pagination;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.device.db.entity.CdDeviceToken;
import net.northking.cctp.device.db.entity.CdDeviceUsageLog;
import net.northking.cctp.device.dto.device.CdMobileDeviceExitDto;
import net.northking.cctp.device.dto.device.DeviceLockDto;
@ -48,4 +49,17 @@ public interface DeviceActivityApiService {
String releaseMobDevice(CdMobileDeviceExitDto dto);
Map<String,List<String>> queryDeviceType(List<String> deviceIds);
/**
*
* @param token token
* @return true or false
*/
Boolean deviceRelease(String token);
/**
*
* @param deviceTokens token
*/
void release(List<CdDeviceToken> deviceTokens);
}

View File

@ -633,6 +633,39 @@ public class DeviceActivityApiServiceImpl implements DeviceActivityApiService {
return resultMap;
}
@Override
public Boolean deviceRelease(String token) {
CdDeviceToken tokenQuery = new CdDeviceToken();
tokenQuery.setToken(token);
List<CdDeviceToken> resultList = cdDeviceTokenService.query(tokenQuery);
release(resultList);
return true;
}
@Override
public void release(List<CdDeviceToken> deviceTokens){
if (deviceTokens != null && deviceTokens.size() > 0) {
for (CdDeviceToken deviceToken : deviceTokens) {
switch (deviceToken.getDeviceType()) {
case DeviceConstants.DEVICE_MOB_TYPE:
CdMobileDeviceExitDto mobileDto = new CdMobileDeviceExitDto();
mobileDto.setDeviceId(deviceToken.getId());
mobileDto.setToken(deviceToken.getToken());
this.releaseMobDevice(mobileDto);
break;
case DeviceConstants.DEVICE_PC_TYPE:
CdPcDeviceExitDto pcDto = new CdPcDeviceExitDto();
pcDto.setDeviceId(deviceToken.getId());
pcDto.setToken(deviceToken.getToken());
this.releasePcDevice(pcDto);
break;
default:
break;
}
}
}
}
@Override
public String releasePcDevice(CdPcDeviceExitDto dto) {
Lock lock = redisLockRegistry.obtain(DeviceConstants.LOCK_PC + dto.getDeviceId());
@ -792,9 +825,22 @@ public class DeviceActivityApiServiceImpl implements DeviceActivityApiService {
log.setDeviceId(deviceId);
log.setDeviceType(cdDeviceToken.getDeviceType());
List<CdDeviceUsageLog> query = this.cdDeviceUsageLogService.query(log);
if (query.size() > 0) {
query.get(0).setEndTime(new Date());
this.cdDeviceUsageLogService.updateByPrimaryKey(query.get(0));
if (query != null && query.size() > 0) {
CdDeviceUsageLog cdDeviceUsageLog = query.get(0);
Date now = new Date();
if (cdDeviceUsageLog.getStartTime() != null) {
Date oneSecondAfter = new Date(cdDeviceUsageLog.getStartTime().getTime() + 1000);
if (now.getTime() > oneSecondAfter.getTime()) {
cdDeviceUsageLog.setEndTime(now);
this.cdDeviceUsageLogService.updateByPrimaryKey(cdDeviceUsageLog);
} else {
// 不记录占用时间小于1秒的记录
this.cdDeviceUsageLogService.deleteByPrimaryKey(cdDeviceUsageLog.getId());
}
}else{
cdDeviceUsageLog.setEndTime(now);
this.cdDeviceUsageLogService.updateByPrimaryKey(cdDeviceUsageLog);
}
}
}
}

View File

@ -14,6 +14,7 @@ import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.device.api.activity.service.DeviceActivityApiService;
import net.northking.cctp.device.config.EngineConfig;
import net.northking.cctp.device.constants.DeviceConstants;
import net.northking.cctp.device.constants.DeviceError;
@ -27,6 +28,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
@ -77,6 +79,12 @@ public class CdEngineInfoApiServiceImpl extends AbstractExcelService<CdEngineInf
@Autowired
private EngineConfig engineConfig;
@Autowired
private DeviceActivityApiService activityApiService;
@Autowired
private RedisTemplate redisTemplate;
//= Excel 导入导出相关代码 start ==================//
@Override
public BasicService<CdEngineInfo> getService()
@ -154,7 +162,14 @@ public class CdEngineInfoApiServiceImpl extends AbstractExcelService<CdEngineInf
{
CdEngineInfo entity = new CdEngineInfo();
BeanUtils.copyProperties(dto, entity);
return this.cdEngineInfoService.updateByPrimaryKey(entity);
int rows = this.cdEngineInfoService.updateByPrimaryKey(entity);
if (rows > 0){
if (dto.getEnabled() != null && !dto.getEnabled()){
logger.debug("引擎禁用,删除引擎的线程缓存数据");
redisTemplate.opsForZSet().remove(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY, dto.getId());
}
}
return rows;
}
@Override
@ -290,6 +305,15 @@ public class CdEngineInfoApiServiceImpl extends AbstractExcelService<CdEngineInf
this.cdEngineInfoService.insert(dto.getCdEngineInfo());
resultDto.setEngineId(engineId);
}
try {
logger.debug("释放引擎占用的设备");
List<CdDeviceToken> deviceTokenList = this.cdDeviceTokenService.queryByEngineIds(Collections.singletonList(resultDto.getEngineId()));
activityApiService.release(deviceTokenList);
} catch (Exception e) {
logger.error("释放引擎占用的设备异常", e);
}
return resultDto;
}

View File

@ -124,25 +124,26 @@ public class HeartMQReceiver {
engineInfo.setRunningRelease(dto.getEngineVersion());
this.cdEngineInfoService.updateByPrimaryKey(engineInfo);
// 更新缓存中引擎可执行线程数量
Map<String, Object> executePoolInfo = dto.getExecutePoolInfo();
if (executePoolInfo != null) {
boolean enableMobile = executePoolInfo.get("enableMobile") != null
&& (boolean) executePoolInfo.get("enableMobile");
if (enableMobile) {
Object mobileThread = executePoolInfo.get("mobileThread");
Object mobileThreadActive = executePoolInfo.get("mobile_thread_active");
if (mobileThread != null && mobileThreadActive != null){
BigDecimal mobileThreadBd = new BigDecimal(String.valueOf(mobileThread));
BigDecimal mobileThreadActiveBd = new BigDecimal(String.valueOf(mobileThreadActive));
if (mobileThreadBd.compareTo(new BigDecimal("0")) > 0) {
BigDecimal divide = mobileThreadActiveBd.divide(mobileThreadBd, 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"));
redisTemplate.opsForZSet().add(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY, engineInfo.getId(),
divide.doubleValue());
if (engineInfo.getEnabled()) {
// 更新缓存中引擎可执行线程数量
Map<String, Object> executePoolInfo = dto.getExecutePoolInfo();
if (executePoolInfo != null) {
boolean enableMobile = executePoolInfo.get("enableMobile") != null
&& (boolean) executePoolInfo.get("enableMobile");
if (enableMobile) {
Object mobileThread = executePoolInfo.get("mobileThread");
Object mobileThreadActive = executePoolInfo.get("mobile_thread_active");
if (mobileThread != null && mobileThreadActive != null) {
BigDecimal mobileThreadBd = new BigDecimal(String.valueOf(mobileThread));
BigDecimal mobileThreadActiveBd = new BigDecimal(String.valueOf(mobileThreadActive));
if (mobileThreadBd.compareTo(new BigDecimal("0")) > 0) {
BigDecimal divide = mobileThreadActiveBd.divide(mobileThreadBd, 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"));
redisTemplate.opsForZSet().add(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY, engineInfo.getId(),
divide.doubleValue());
}
}
}
}
}

View File

@ -24,6 +24,8 @@ public interface CdDeviceTokenDao extends CdDeviceTokenMapper
{
List<CdDeviceToken> queryDeviceList(@Param("userId") String userId, @Param("ids") List<String> ids);
// ---- The End by Generator ----//
List<CdDeviceToken> queryByEngineIds(@Param("ids") List<String> engineIds);
// ---- The End by Generator ----//
}

View File

@ -56,6 +56,11 @@ private static final Logger logger = LoggerFactory.getLogger(CdDeviceTokenServic
return cdDeviceTokenDao.queryDeviceList(userId,ids);
}
@Override
public List<CdDeviceToken> queryByEngineIds(List<String> engineIds) {
return cdDeviceTokenDao.queryByEngineIds(engineIds);
}
// ---- The End by Generator ----//

View File

@ -13,6 +13,8 @@ public interface CdDeviceTokenService extends BasicService<CdDeviceToken>
{
List<CdDeviceToken> queryDeviceList(String userId,List<String> ids);
List<CdDeviceToken> queryByEngineIds(List<String> engineIds);
// ---- The End by Generator ----//
}

View File

@ -110,27 +110,8 @@ public class DevicePubCtrl {
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultWrapper<Boolean> deviceRelease(@PathVariable("token") String token) {
ResultWrapper<Boolean> wrapper = new ResultWrapper<>();
CdDeviceToken tokenQuery = new CdDeviceToken();
tokenQuery.setToken(token);
List<CdDeviceToken> resultList = deviceTokenService.query(tokenQuery);
if (resultList != null && resultList.size() > 0) {
CdDeviceToken device = resultList.get(0);
switch (device.getDeviceType()) {
case "1":
CdMobileDeviceExitDto mobileDto = new CdMobileDeviceExitDto();
mobileDto.setDeviceId(device.getId());
mobileDto.setToken(token);
apiService.releaseMobDevice(mobileDto);
break;
case "2":
CdPcDeviceExitDto pcDto = new CdPcDeviceExitDto();
pcDto.setDeviceId(device.getId());
pcDto.setToken(token);
apiService.releasePcDevice(pcDto);
break;
}
}
return wrapper.success(true);
Boolean result = this.apiService.deviceRelease(token);
return wrapper.success(result);
}

View File

@ -1,10 +1,12 @@
package net.northking.cctp.device.schedule;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.device.api.activity.service.DeviceActivityApiService;
import net.northking.cctp.device.bus.feign.PlatformFeign;
import net.northking.cctp.device.bus.feign.dto.UserInfoDto;
import net.northking.cctp.device.bus.publisher.DeviceStatusPublisher;
import net.northking.cctp.device.config.EmailConfig;
import net.northking.cctp.device.constants.DeviceConstants;
import net.northking.cctp.device.db.entity.*;
import net.northking.cctp.device.db.service.*;
import net.northking.cctp.device.util.DeviceStatusUtil;
@ -27,10 +29,7 @@ import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.concurrent.locks.Lock;
@Component
@ -90,6 +89,9 @@ public class DeviceSchedule {
@Autowired
private EmailConfig emailConfig;
@Autowired
private DeviceActivityApiService activityApiService;
@Scheduled(cron = "0 5 0 * * *")
public void recycleDevice() {
Lock lock = lockRegistry.obtain("scheduler:DeviceSchedule.recycleDevice");
@ -183,6 +185,15 @@ public class DeviceSchedule {
statusUtil.updatePcStatus(status);
}
this.cdEngineInfoService.updateEngineStatus(ids);
try {
logger.debug("释放引擎占用的设备");
List<CdDeviceToken> deviceTokenList = this.cdDeviceTokenService.queryByEngineIds(ids);
activityApiService.release(deviceTokenList);
logger.debug("删除缓存中引擎线程的使用率");
redisTemplate.opsForZSet().remove(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY, ids.toArray());
} catch (Exception e) {
logger.error("释放引擎占用的设备失败", e);
}
}
logger.info("---------------------------校验设备与引擎过期定时任务:结束-----------------------------------");

View File

@ -8,10 +8,23 @@
FROM cd_device_token
WHERE
user_id = #{userId, jdbcType=VARCHAR}
AND id in
<foreach collection="ids" item="idItem" open="(" separator="," close=")">
#{idItem, jdbcType=VARCHAR}
</foreach>
<if test="ids != null and ids.size > 0">
AND id in
<foreach collection="ids" item="idItem" open="(" separator="," close=")">
#{idItem, jdbcType=VARCHAR}
</foreach>
</if>
</select>
<select id="queryByEngineIds" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM cd_device_token
WHERE
user_id in
<foreach collection="ids" item="idItem" open="(" separator="," close=")">
#{idItem, jdbcType=VARCHAR}
</foreach>
</select>
<!-- 请在本文件内添加自定义SQL语句 -->

View File

@ -27,7 +27,9 @@ import javax.websocket.CloseReason;
import javax.websocket.Session;
import java.io.OutputStream;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
@ -39,6 +41,10 @@ public class MobileConnectServiceImpl implements MobileConnectService {
@Autowired
private ThreadPoolExecutor executorService;
/**
* web deviceId-Session
*/
private ConcurrentMap<String,Session> sessionMap = new ConcurrentHashMap<>();
/**
*
@ -50,6 +56,11 @@ public class MobileConnectServiceImpl implements MobileConnectService {
*/
private ConcurrentHashMap<String, ProcessMsgThread> mobileConnectionThreadMap = new ConcurrentHashMap<>();
/**
* tokenmap
*/
private static Map<String, String> userTokenMap = new ConcurrentHashMap<>();
@Autowired
private RedisTemplate redisTemplate;
@ -172,7 +183,7 @@ public class MobileConnectServiceImpl implements MobileConnectService {
logger.info("线程池还能支持手机连接.........................");
Future future = mobileConnectionTaskMap.get(deviceId);
if (future == null || future.isCancelled() || future.isDone()) {
ProcessMsgThread processMsgThread = new ProcessMsgThread(userToken, sessionId, deviceId, deviceToken, session,mode);
ProcessMsgThread processMsgThread = new ProcessMsgThread(userToken, session,sessionId, deviceId, deviceToken, mode);
future = executorService.submit(processMsgThread);
mobileConnectionThreadMap.put(deviceId, processMsgThread);
mobileConnectionTaskMap.put(deviceId, future);
@ -202,4 +213,19 @@ public class MobileConnectServiceImpl implements MobileConnectService {
logger.warn("出现没有sessionId的请求。。。。。。。。。。");
}
}
@Override
public Session getSession(String deviceId) {
return sessionMap.get(deviceId);
}
@Override
public void addUserToken(String sessionId, String token) {
userTokenMap.put(sessionId, token);
}
@Override
public void removeUserToken(String id) {
userTokenMap.remove(id);
}
}

View File

@ -24,4 +24,13 @@ public interface MobileConnectService {
void releaseUsingDevice(String deviceId, String deviceToken);
//拿到session
Session getSession(String deviceId);
//添加用户token
void addUserToken(String sessionId, String token);
//移除用户token
void removeUserToken(String id);
}

View File

@ -1,14 +1,18 @@
package net.northking.cctp.mobile.socket;
import com.alibaba.fastjson.JSON;
import com.hzbank.testteam.autotest.dependencies.authDependency.constants.AuthDependencyConstants;
import com.hzbank.testteam.autotest.dependencies.authDependency.dto.TokenValueDTO;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.utils.SpringUtil;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.security.authentication.SecurityUserCache;
import net.northking.cctp.mobile.constants.DeviceConstants;
import net.northking.cctp.mobile.constants.MobileConnectionConstants;
import net.northking.cctp.mobile.db.service.MobileConnectService;
import net.northking.cctp.mobile.entity.UpperWSResponse;
import net.northking.cctp.mobile.util.SpringUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -42,13 +46,6 @@ public class MobileSessionManager {
logger.info("Init web session manager ...");
}
private static SecurityUserCache securityUserCache;
@Autowired
public void setSecurityUserCache(SecurityUserCache securityUserCache) {
MobileSessionManager.securityUserCache = securityUserCache;
}
/**
* Websocket
*
@ -62,6 +59,7 @@ public class MobileSessionManager {
String userToken = queryParams.get(DeviceConstants.AUTHORIZATION);
String sessionId = queryParams.get(DeviceConstants.KEY_SESSION_ID);
String deviceToken = queryParams.get(DeviceConstants.KEY_DEVICE_TOKEN);
String reportId = queryParams.get(DeviceConstants.KEY_REPORT_ID);
String mode = queryParams.get(DeviceConstants.KEY_MODE);
if (StringUtils.isNotBlank(mode)) {
logger.debug("deviceToken:[{}]直播连接视频",deviceToken);
@ -120,8 +118,11 @@ public class MobileSessionManager {
* @return
*/
private boolean authorize(String sessionId, String token) {
if (securityUserCache.hasToken(token)) {
// SpringUtils.getBean(MobileConnectService.class).addUserToken(sessionId, token);
RedissonClient redisson = SpringUtil.getBean(RedissonClient.class);
RBucket<TokenValueDTO> tokenValueDTORBucket = redisson.getBucket(AuthDependencyConstants.TOKEN_PREFIX + token);
TokenValueDTO tokenValueDTO = tokenValueDTORBucket.get();
if (tokenValueDTO != null ) {
SpringUtils.getBean(MobileConnectService.class).addUserToken(sessionId, token);
return true;
}
return false;

View File

@ -2,12 +2,8 @@ package net.northking.cctp.mobile.socket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.northking.cctp.mobile.api.report.service.DeviceUseReportDetailApiService;
import net.northking.cctp.mobile.constants.MobileConnectionConstants;
import net.northking.cctp.mobile.db.service.DeviceUseReportService;
import net.northking.cctp.mobile.dto.report.DeviceLogDto;
import net.northking.cctp.mobile.dto.report.DeviceUseReportDetailAddDto;
import net.northking.cctp.mobile.dto.report.DeviceUseReportQueryDto;
import net.northking.cctp.mobile.entity.UpperWSResponse;
import net.northking.cctp.mobile.entity.WSResponse;
import net.northking.cctp.mobile.util.JsonUtils;
@ -44,6 +40,7 @@ public class UpperWebSocketClient extends WebSocketClient {
private RemoteEndpoint.Basic basicRemote;
public UpperWebSocketClient(URI uri, Session webSession, String deviceId, String sessionId) {
super(uri);
this.webSession = webSession;
@ -72,7 +69,7 @@ public class UpperWebSocketClient extends WebSocketClient {
@Override
public void onMessage(String msg) {
// logger.debug("服务端接收到上位机信息msg:{}", msg);
//logger.debug("服务端接收到上位机信息msg:{}", msg);
try {
UpperWSResponse msgResponse = JsonUtils.fromJsonString(msg, UpperWSResponse.class);
String cmd = msgResponse.getCmd();
@ -198,12 +195,9 @@ public class UpperWebSocketClient extends WebSocketClient {
*/
private void handleUpperMsgForward(UpperWSResponse msgResponse) {
WSResponse wsResponse = WSResponse.buildResponse(sessionId, msgResponse);
WSUtils.sendText(basicRemote, JsonUtils.toJson(wsResponse), deviceId);
WSUtils.sendText(webSession, JsonUtils.toJson(wsResponse), deviceId);
}
/**
*
*/
private void handleUpperMsgActive(UpperWSResponse msgResponse) {
//根据返回结果决定返回内容
WSResponse wsResponse = WSResponse.buildResponse(msgResponse.getCmd(), sessionId, msgResponse.getData());

View File

@ -1,5 +1,6 @@
package net.northking.cctp.mobile.thread;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.mobile.constants.MobileConnectionConstants;
import net.northking.cctp.mobile.db.service.MobileConnectService;
@ -16,8 +17,11 @@ import net.northking.cctp.mobile.util.JsonUtils;
import net.northking.cctp.mobile.util.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.websocket.Session;
import java.net.URI;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@ -134,7 +138,7 @@ public class ProcessMsgThread extends Thread{
private void exitMobile(){
if (StringUtils.hasText(deviceToken)) {
if (StringUtils.isNotBlank(mode)) {
if (StringUtils.hasText(mode)) {
logger.info("设备【{}】是直播退出,不释放手机.......",deviceId);
} else {
SpringUtils.getBean(MobileConnectService.class).releaseUsingDevice(deviceId, deviceToken);
@ -152,11 +156,10 @@ public class ProcessMsgThread extends Thread{
logger.info("前端设备连接信息添加进队列");
}
public void exit() {
this.alive = false;
}
private void sendMsg2Web(String deviceId, String msg){
if(null == webSession || !webSession.isOpen()){
logger.warn("前端Session[deviceId:{}]已关闭",deviceId);
return;

View File

@ -32,4 +32,27 @@ public class WSUtils {
logger.warn("web端session已经断开",e);
}
}
public static void sendText(Session session, String msg, String deviceId) {
synchronized (session) {
if (null != session && session.isOpen()) {
try {
session.getBasicRemote().sendText(msg);
} catch (Exception e) {
logger.error(String.format("设备【%s】发送屏幕数据到前端失败", deviceId), e);
//增加重发机制
}
} else {
logger.warn("Session[deviceId:{}]不存在或已关闭...", deviceId);
//关闭上位机会话
try {
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "session失效关闭"));
} catch (IOException e) {
logger.warn("session关闭失败", e);
}
return;
}
}
}
}

30
cctp-test-element/.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Compiled class file
*.class
# Log file
*.log
*.log.gz
logs/
*/logs/
*/*/logs/
# Temp file
*.temp
temp
*/temp
# IDEA profile dir
.idea/
*/.idea/
*/*/.idea/
# IDEA project file
*.iml
*/*.iml
*/*/*.iml
*/*/*/*.iml
target/
*/target/
*/*/target/

View File

@ -11,7 +11,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>cctp-test-element-core</artifactId>
<version>1.0.1-RELEASE</version>
<version>1.0.2-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>

View File

@ -6,5 +6,7 @@ public enum InputType {
//单选
select,
//多选
selects;
selects,
element;
}

View File

@ -28,7 +28,7 @@
</dependencies>
<build>
<finalName>NK.Desktop-1.75</finalName>
<finalName>NK.Desktop-1.80</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -23,7 +23,7 @@ public class DesktopLibrary extends AbstractLibrary {
private static final Javadoc2Libdoc JAVA_DOC_2_LIB_DOC = new Javadoc2Libdoc(DesktopLibrary.class);
private static final String NAME = "NK.Desktop";
private static final String CH_NAME = "PC端操作组件库";
private static final String VERSION = "1.79";
private static final String VERSION = "1.80";
private static final Integer TYPE = LibraryConstant.LIB_TYPE_PCBS;
/**

View File

@ -328,7 +328,7 @@ public class DesktopTools {
}
/**
*
*
* @param id id
* @param title
* @param targets
@ -336,12 +336,11 @@ public class DesktopTools {
* @param delayBefore
* @param delayAfter
* @param waitForReady
* @param value
* @param deviceDriver PCwebSocket
* @param executeContent
* @return
*/
@Keyword(alias = "断言元素是否存在", attributes = "23")
@Keyword(alias = "断言元素存在", attributes = "23")
@Return(name = "result", comment = "返回值", type = DataType.BOOLEAN)
public Boolean assertElement(
@Argument(name = "id", comment = "步骤id", type = DataType.STRING, scope = ParamScope.STEP, defaultDisplay = false) String id,
@ -351,7 +350,7 @@ public class DesktopTools {
@Argument(name = "delayBefore",comment = "执行前等待",type = DataType.FLOAT, required = false, notNull = false, defaultValue = "0.35", defaultDisplay = false) Float delayBefore,
@Argument(name = "delayAfter",comment = "执行后等待",type = DataType.FLOAT, required = false, notNull = false, defaultValue = "0.35", defaultDisplay = false) Float delayAfter,
@Argument(name = "waitForReady",comment = "就绪等待",type = DataType.INTEGER, required = false, enumValues = WaitForReadyEnum.class, notNull = false, defaultValue ="1", inputType = InputType.select, defaultDisplay = false) Integer waitForReady,
@Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value,
@Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "", required = false) String value,
@Argument(name = "formSelector", comment = "窗体元素表达式", type = DataType.STRING, required = false, notNull = false, defaultDisplay = false) String formSelector,
@Argument(name = "__deviceDriver", comment = "与PC机器连接的webSocket客户端", type = DataType.OBJECT, scope = ParamScope.CONTEXT, defaultDisplay = false) DeviceDriver deviceDriver,
IExecuteContext executeContent
@ -696,7 +695,7 @@ public class DesktopTools {
@Argument(name = "__deviceDriver", comment = "与PC机器连接的webSocket客户端", type = DataType.OBJECT, scope = ParamScope.CONTEXT, defaultDisplay = false) DeviceDriver deviceDriver,
IExecuteContext executeContent
){
if (StrUtil.isBlank(id) || CollectionUtil.isEmpty(targets) || StrUtil.isBlank(value)){
if (StrUtil.isBlank(id) || CollectionUtil.isEmpty(targets)){
throw new ParamMistakeException(ErrorMessageConstant.REQUEST_PARAMS_IS_BLANK);
}
if (deviceDriver == null){
@ -725,7 +724,7 @@ public class DesktopTools {
}
/**
*
*
* @param id id
* @param title
* @param targets
@ -733,12 +732,11 @@ public class DesktopTools {
* @param delayBefore
* @param delayAfter
* @param waitForReady
* @param value
* @param deviceDriver PCwebSocket
* @param executeContent
* @return
*/
@Keyword(alias = "断言是否包含文本", attributes = "23")
@Keyword(alias = "断言包含文本", attributes = "23")
@Return(name = "result", comment = "返回值", type = DataType.BOOLEAN)
public Boolean assertText(
@Argument(name = "id", comment = "步骤id", type = DataType.STRING, scope = ParamScope.STEP, defaultDisplay = false) String id,
@ -749,7 +747,7 @@ public class DesktopTools {
@Argument(name = "delayAfter",comment = "执行后等待",type = DataType.FLOAT, required = false, notNull = false, defaultValue = "0.35", defaultDisplay = false) Float delayAfter,
@Argument(name = "waitForReady",comment = "就绪等待",type = DataType.INTEGER, required = false, enumValues = WaitForReadyEnum.class, notNull = false, defaultValue ="1", inputType = InputType.select, defaultDisplay = false) Integer waitForReady,
@Argument(name = "containText", comment = "断言的文本", type = DataType.STRING,required = false) String containText,
@Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "") String value,
@Argument(name = "value", comment = "组件返回值", type = DataType.STRING, scope = ParamScope.OUTPUT, defaultValue = "", required = false) String value,
@Argument(name = "formSelector", comment = "窗体元素表达式", type = DataType.STRING, required = false, notNull = false, defaultDisplay = false) String formSelector,
@Argument(name = "__deviceDriver", comment = "与PC机器连接的webSocket客户端", type = DataType.OBJECT, scope = ParamScope.CONTEXT, defaultDisplay = false) DeviceDriver deviceDriver,
IExecuteContext executeContent

View File

@ -1,3 +1,8 @@
### 版本 1.80
更新于2024-07-06
1.修改组件名称为断言包含文本,断言存在元素
2.将断言的返回值返回值变为非必填项
### 版本 1.78
更新于2024-06-19
1.添加杭银OCR识别方式

View File

@ -17,7 +17,7 @@ public class MobileIosLibrary extends AbstractLibrary {
private static final Javadoc2Libdoc JAVA_DOC_2_LIB_DOC = new Javadoc2Libdoc(MobileIosLibrary.class);
private static final String NAME = "NK.MobileIos";
private static final String CH_NAME = "移动端ios通用组件库";
private static final String VERSION = "1.0.12";
private static final String VERSION = "1.0.15";
private static final Integer TYPE = LibraryConstant.LIB_TYPE_NOT;
@Override

View File

@ -83,4 +83,9 @@ public interface UpperParamKey {
*/
String USING_TYPE = "using_type";
/**
* id
*/
String TENANT_ID = "tenant_id";
}

View File

@ -8,7 +8,9 @@ import net.northking.cctp.element.core.annotation.Keyword;
import net.northking.cctp.element.core.annotation.Keywords;
import net.northking.cctp.element.core.annotation.Return;
import net.northking.cctp.element.core.exception.AssertException;
import net.northking.cctp.element.core.exception.EnvironmentException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.exception.ParamMistakeException;
import net.northking.cctp.element.core.type.DataType;
import net.northking.cctp.element.core.type.InputType;
import net.northking.cctp.element.core.type.ParamScope;
@ -23,7 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.*;
/**
* ios
@ -46,7 +48,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击控件(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "点击控件(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "点击结果", type = DataType.BOOLEAN)
public boolean clickElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -97,7 +99,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "是否存在元素ios新", category = "0", attributes = "9")
@Keyword(alias = "是否存在元素ios新", category = "0", attributes = "5")
@Return(name = "result", comment = "是否存在元素的结果", type = DataType.BOOLEAN)
public boolean ifElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -143,7 +145,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "标准滑动ios新", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "标准滑动ios新", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "滑动结果", type = DataType.BOOLEAN)
public boolean standardSwipe(
@Argument(name = "direction", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "up") String direction,
@ -187,7 +189,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "控件滑动ios新", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "控件滑动ios新", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "滑动的结果", type = DataType.BOOLEAN)
public boolean elementSwipe(IExecuteContext context,
@Argument(name = "direction", scope = ParamScope.ARGS, comment = "控件滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, defaultDisplay = false, inputType = InputType.select, defaultValue = "up") String direction,
@ -240,7 +242,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "控件输入文本ios新", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "控件输入文本ios新", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "输入的结果", type = DataType.BOOLEAN)
public boolean inputText(IExecuteContext context,
@Argument(name = "text", scope = ParamScope.VARIABLES, comment = "输入的文本", type = DataType.STRING) String text,
@ -294,7 +296,7 @@ public class IOSNewTools {
* @param value
* @return
*/
@Keyword(alias = "获取控件的值ios新", category = "0", attributes = "9")
@Keyword(alias = "获取控件的值ios新", category = "0", attributes = "5")
@Return(name = "result", comment = "获取控件值的结果", type = DataType.OBJECT)
public String getElementText(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -343,7 +345,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "长按控件(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "长按控件(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "长按结果", type = DataType.BOOLEAN)
public boolean longPress(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -395,7 +397,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "断言元素ios新", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "断言元素ios新", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "断言结果", type = DataType.BOOLEAN)
public boolean assertElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -445,7 +447,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "识别验证码ios新", category = "0", attributes = "9")
@Keyword(alias = "识别验证码ios新", category = "0", attributes = "5")
@Return(name = "result", comment = "验证码识别", type = DataType.STRING)
public String getCodeByOcr(
IExecuteContext context,
@ -493,7 +495,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击文本(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "点击文本(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "是否点击成功", type = DataType.BOOLEAN)
public Boolean clickText(
IExecuteContext context,
@ -538,7 +540,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击文本(新)(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "点击文本(新)(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "是否点击成功", type = DataType.BOOLEAN)
public Boolean clickTextNew(
IExecuteContext context,
@ -583,7 +585,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "是否存在文本(ios新)", category = "0", attributes = "9")
@Keyword(alias = "是否存在文本(ios新)", category = "0", attributes = "5")
@Return(name = "result", comment = "文本存在与不存在的结果", type = DataType.BOOLEAN)
public Boolean ifText(
IExecuteContext context,
@ -629,7 +631,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "app处理(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "app处理(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "app处理", type = DataType.BOOLEAN)
public boolean handleApp(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -666,7 +668,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击home键(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "点击home键(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "点击home键", type = DataType.BOOLEAN)
public boolean pressKeyHome(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -696,7 +698,7 @@ public class IOSNewTools {
* @param waitTime s
* @return
*/
@Keyword(alias = "等待(ios新)", category = "0", attributes = "9")
@Keyword(alias = "等待(ios新)", category = "0", attributes = "5")
@Return(name = "result", comment = "验证码识别", type = DataType.BOOLEAN)
public boolean wait(
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -766,7 +768,7 @@ public class IOSNewTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "密码输入(ocr)(ios新)", category = "0", attributes = "9", commonlyUse = true)
@Keyword(alias = "密码输入(ocr)(ios新)", category = "0", attributes = "5", commonlyUse = true)
@Return(name = "result", comment = "输入结果", type = DataType.BOOLEAN)
public boolean inputPasswordByOcr(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -812,7 +814,7 @@ public class IOSNewTools {
* @param value
* @return
*/
@Keyword(alias = "识别金额(ios新)", category = "0", attributes = "9")
@Keyword(alias = "识别金额(ios新)", category = "0", attributes = "5")
@Return(name = "result", comment = "输入的结果", type = DataType.OBJECT)
public String getElementMoneyText(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -848,4 +850,91 @@ public class IOSNewTools {
return result;
}
/**
* <p>ios</p>
*
* @param deviceDriver
* @param waitTimeout
* @param preExecuteWait
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "屏幕截图ios新", category = "0", attributes = "5")
@Return(name = "result", comment = "是否截图成功", type = DataType.BOOLEAN)
public Boolean screenShot(
IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout,
@Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait,
@Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait
) {
CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待
ElementHandleParam param = ElementHandleParam.builder(deviceDriver)
.useContext(context)
.waitTimeout(waitTimeout);
Boolean result = false;
try {
result = AutomationHandleUtil.screenShot(param);
} catch (InterruptedException ie) {
logger.warn("用户取消查询元素");
throw new ExecuteException("取消操作");
} catch (Exception e) {
logger.error("出现了其他的问题。。。。", e);
throw new ExecuteException(e.getMessage());
}
CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待
return result;
}
/**
* <p>iosOCR</p>
*
* @param deviceDriver
* @param targets
* @param waitTimeout
* @param waitStatusStr
* @param swipe 0-up-down-left-,right-
* @param swipeCount
* @param preExecuteWait
* @param sufExecuteWait
* @param value
* @return
*/
@Keyword(alias = "获取控件的值ios新OCR方式", category = "0", attributes = "5")
@Return(name = "result", comment = "输入的结果", type = DataType.OBJECT)
public String getElementValueByOcr(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List<IStepTarget> targets,
@Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout,
@Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr,
@Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe,
@Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer swipeCount,
@Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value,
@Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait,
@Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait) {
CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待
boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定
ElementHandleParam param = ElementHandleParam.builder(deviceDriver)
.useContext(context)
.withTargets(targets)
.waitTimeout(waitTimeout)
.searchSwipeDirection("0") //不滑动
.withSwipeCount(1)
.waitStatus(waitStatus);
String result = "";
try {
result = AutomationHandleUtil.getElementValueByOcr(param);
} catch (InterruptedException ie) {
logger.warn("用户取消查询元素");
throw new ExecuteException("取消操作");
} catch (Exception e) {
logger.error("出现了其他的问题。。。。", e);
throw new ExecuteException(e.getMessage());
}
CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待
CommonUtils.setParamValue(context, value, result);
return result;
}
}

View File

@ -991,10 +991,6 @@ public class AutomationHandleUtil {
logger.warn("不支持的获取元素值方式:{}", target.getUsing());
}
}
if (StringUtils.isBlank(result)) {
throw new ExecuteException("无法获取当前元素的文本");
}
return result;
}
@ -1034,4 +1030,54 @@ public class AutomationHandleUtil {
return result;
}
public static Boolean screenShot(ElementHandleParam param) throws Exception {
String screenShotUrl = "";
String token = UUID.randomUUID().toString();
String tenantId = (String) param.getContext().getContextVariable("__tenantId");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put(UpperParamKey.TENANT_ID, tenantId);
logger.info("参数:{}", JSON.toJSONString(paramMap));
logger.debug("tenantId{}",tenantId);
CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SCREEN_SHOT, token, paramMap);
Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout());
logger.debug("屏幕截图的结果:{}",o);
if (null != o) {
screenShotUrl = (String) o;
param.getContext().setVariable("mobileStepSnapshot",screenShotUrl);
return true;
}
return false;
}
public static String getElementValueByOcr(ElementHandleParam param) throws Exception{
String result = "";
List<IStepTarget> targets = param.getTargets();
for (IStepTarget target : targets) {
result = getElementValueByOcr(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount());
if (StringUtils.isNotBlank(result)) {
return result;
}
}
return result;
}
private static String getElementValueByOcr(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{
String result = "";
String token = UUID.randomUUID().toString();
Map<String, Object> paramMap = new HashMap<>();
JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
logger.info("拿到的xpath:{}", xpath);
paramMap.put(UpperParamKey.NODE_TREE, xpath);
paramMap.put(UpperParamKey.IS_SWIPE, swipe);
paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount);
CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_ELEMENT_VALUE_BY_PATH_OCR, token, paramMap);
Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout);
logger.debug("根据nodeTree查找,ocr识别的结果{}", o);
if (null != o) {
result = (String) o;
}
return result;
}
}

View File

@ -1,3 +1,8 @@
### 版本1.0.15
更新于2024-07-08
1.增加屏幕截图组件
### 版本1.0.8
更新于2024-06-11

View File

@ -20,7 +20,7 @@ public class MobileLibrary extends AbstractLibrary {
private static final Javadoc2Libdoc JAVA_DOC_2_LIB_DOC = new Javadoc2Libdoc(MobileLibrary.class);
private static final String NAME = "NK.mobile";
private static final String CH_NAME = "移动端通用组件库";
private static final String VERSION = "2.1.44";
private static final String VERSION = "2.1.47";
private static final Integer TYPE = LibraryConstant.LIB_TYPE_NOT;
@Override

View File

@ -0,0 +1,45 @@
package net.northking.cctp.element.mobile.entity;
import net.northking.cctp.element.core.IStepTarget;
public class StepTarget implements IStepTarget {
/**
*
*/
private String using;
/**
*
*/
private String value;
/**
* Or
*/
private String strategy;
@Override
public String getUsing() {
return using;
}
public void setUsing(String using) {
this.using = using;
}
@Override
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getStrategy() {
return strategy;
}
public void setStrategy(String strategy) {
this.strategy = strategy;
}
}

View File

@ -32,7 +32,7 @@ public class AndroidTools {
* @param deviceDriver
* @return
*/
@Keyword(alias = "点击返回健", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击返回健", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "点击返回健", type = DataType.BOOLEAN)
public boolean pressKeyBack(@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver) {
PressKeyBackThread pressKeyBackThread = new PressKeyBackThread(deviceDriver);
@ -87,7 +87,7 @@ public class AndroidTools {
* @param deviceDriver
* @return
*/
@Keyword(alias = "点击多任务键", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击多任务键", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "点击多任务键", type = DataType.BOOLEAN)
public boolean pressKeyMenu(@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver) {
PressKeyMenuThread pressKeyMenuThread = new PressKeyMenuThread(deviceDriver);

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.ReUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.android.nativekey.AndroidKey;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
@ -55,7 +56,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击文本", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击文本", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "是否点击成功", type = DataType.BOOLEAN)
public Boolean clickText(
IExecuteContext context,
@ -119,7 +120,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击文本(新)", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击文本(新)", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "是否点击成功", type = DataType.BOOLEAN)
public Boolean clickTextNew(
IExecuteContext context,
@ -184,7 +185,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "是否存在文本", category = "0", attributes = "45")
@Keyword(alias = "是否存在文本", category = "0", attributes = "4")
@Return(name = "result", comment = "文本存在与不存在的结果", type = DataType.BOOLEAN)
public Boolean ifText(
IExecuteContext context,
@ -251,7 +252,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击控件", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击控件", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "点击结果", type = DataType.BOOLEAN)
public boolean clickElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -321,7 +322,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "是否存在元素", category = "0", attributes = "45")
@Keyword(alias = "是否存在元素", category = "0", attributes = "4")
@Return(name = "result", comment = "是否存在元素的结果", type = DataType.BOOLEAN)
public boolean ifElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -386,7 +387,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "标准滑动", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "标准滑动", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "滑动结果", type = DataType.BOOLEAN)
public boolean standardSwipe(
@Argument(name = "direction", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "up") String direction,
@ -451,7 +452,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "控件滑动", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "控件滑动", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "滑动的结果", type = DataType.BOOLEAN)
public boolean elementSwipe(IExecuteContext context,
@Argument(name = "direction", scope = ParamScope.ARGS, comment = "控件滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, defaultDisplay = false, inputType = InputType.select, defaultValue = "up") String direction,
@ -505,6 +506,71 @@ public class CommonTools {
return aBoolean;
}
/**
* <p></p>
*
* @param direction
* @param deviceDriver
* @param targets
* @param waitTimeout
* @param waitStatusStr
* @param preExecuteWait
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "滑动查找控件", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "滑动的结果", type = DataType.BOOLEAN)
public boolean swipeAndFindTargetElement(IExecuteContext context,
@Argument(name = "direction", scope = ParamScope.ARGS, comment = "控件滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, defaultDisplay = false, inputType = InputType.select, defaultValue = "up") String direction,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@Argument(name = "targets", scope = ParamScope.STEP, comment = "目标元素", type = DataType.LIST, defaultDisplay = false,inputType = InputType.element) List<IStepTarget> targets,
@Argument(name = "references", scope = ParamScope.ARGS, comment = "滑动元素", type = DataType.LIST, defaultDisplay = false,inputType = InputType.element) String referencesStr,
@Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout,
@Argument(name = "index", scope = ParamScope.ARGS, comment = "多个时定位第几个", type = DataType.INTEGER, defaultValue = "1",defaultDisplay = false) Integer index,
@Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr,
@Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value,
@Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait,
@Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait) {
logger.debug("拿到的reference为{}",referencesStr);
SwipeAndFindTargetElementThread swipeAndFindTargetElementThread = new SwipeAndFindTargetElementThread(context,deviceDriver,targets,referencesStr,waitTimeout,waitStatusStr,direction,index,preExecuteWait,sufExecuteWait);
Future<Boolean> future = executorService.submit(swipeAndFindTargetElementThread);
Boolean aBoolean = null;
deviceDriver.setFuture(future);
try {
aBoolean = future.get(waitTimeout, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warn("该线程被中断了。。。。。。");
throw new ExecuteException("执行中断");
} catch (ExecutionException e) {
logger.warn("在执行的时候报错了。。。。");
Throwable cause = e.getCause();
if (cause instanceof ExecuteException) {
ExecuteException ee = (ExecuteException) cause;
throw ee;
} else if (cause instanceof ParamMistakeException) {
ParamMistakeException pe = (ParamMistakeException) cause;
throw pe;
} else if (cause instanceof NoSuchSessionException) {
NoSuchSessionException ne = (NoSuchSessionException) cause;
throw ne;
} else if (cause instanceof EnvironmentException) {
EnvironmentException ee = (EnvironmentException) cause;
throw ee;
}else{
logger.error("出现了其他的问题。。。。");
throw new ExecuteException(cause.getMessage());
}
} catch (TimeoutException e) {
logger.warn("处理超时了。。。。。。");
throw new ExecuteException("查找元素超时");
} catch (CancellationException e) {
logger.warn("该操作被取消了");
throw new ExecuteException("用户取消,执行失败");
}
setParamValue(context,value,aBoolean.toString());
return aBoolean;
}
/**
* <p></p>
*
@ -520,7 +586,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
/*@Keyword(alias = "输入文本", category = "0", attributes = "4", commonlyUse = true)
/*@Keyword(alias = "输入文本", category = "0", attributes = "45", commonlyUse = true)
@Return(name = "result", comment = "输入的结果", type = DataType.BOOLEAN)
public boolean inputText(IExecuteContext context,
@Argument(name = "text", scope = ParamScope.VARIABLES, comment = "输入的文本", type = DataType.STRING) String text,
@ -590,7 +656,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "控件输入文本", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "控件输入文本", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "输入的结果", type = DataType.BOOLEAN)
public boolean inputText(IExecuteContext context,
@Argument(name = "text", scope = ParamScope.VARIABLES, comment = "输入的文本", type = DataType.STRING) String text,
@ -661,7 +727,7 @@ public class CommonTools {
* @param value
* @return
*/
@Keyword(alias = "获取控件的值", category = "0", attributes = "45")
@Keyword(alias = "获取控件的值", category = "0", attributes = "4")
@Return(name = "result", comment = "输入的结果", type = DataType.OBJECT)
public String getElementText(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -729,7 +795,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "长按控件", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "长按控件", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "输入的结果", type = DataType.BOOLEAN)
public boolean longPress(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -795,7 +861,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "断言元素", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "断言元素", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "断言结果", type = DataType.BOOLEAN)
public boolean assertElement(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -861,7 +927,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "app处理", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "app处理", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "app处理", type = DataType.BOOLEAN)
public boolean handleApp(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -919,7 +985,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "点击home键", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "点击home键", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "点击home键", type = DataType.BOOLEAN)
public boolean pressKeyHome(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -978,7 +1044,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "识别验证码", category = "0", attributes = "45")
@Keyword(alias = "识别验证码", category = "0", attributes = "4")
@Return(name = "result", comment = "验证码识别", type = DataType.STRING)
public String getCodeByOcr(
IExecuteContext context,
@ -1061,7 +1127,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "monkey测试", category = "0", attributes = "45")
@Keyword(alias = "monkey测试", category = "0", attributes = "4")
@Return(name = "result", comment = "验证码识别", type = DataType.BOOLEAN)
public boolean monkeyTest(
IExecuteContext context,
@ -1134,7 +1200,7 @@ public class CommonTools {
* @param waitTime s
* @return
*/
@Keyword(alias = "等待", category = "0", attributes = "45")
@Keyword(alias = "等待", category = "0", attributes = "4")
@Return(name = "result", comment = "验证码识别", type = DataType.BOOLEAN)
public boolean wait(
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -1186,7 +1252,7 @@ public class CommonTools {
*
* @return
*/
@Keyword(alias = "接口方式获取验证码", category = "0", attributes = "45")
@Keyword(alias = "接口方式获取验证码", category = "0", attributes = "4")
@Return(name = "result", comment = "接口方式获取验证码", type = DataType.BOOLEAN)
public String getCodeByHttp(
IExecuteContext context,
@ -1222,7 +1288,7 @@ public class CommonTools {
return result;
}
@Keyword(alias = "模拟键盘输入", category = "0", attributes = "45")
@Keyword(alias = "模拟键盘输入", category = "0", attributes = "4")
@Return(name = "result", comment = "模拟键盘输入", type = DataType.BOOLEAN)
public Boolean inputFromKeyBoard(
IExecuteContext context,
@ -1274,7 +1340,7 @@ public class CommonTools {
return result;
}
@Keyword(alias = "输入密码", category = "0", attributes = "45")
@Keyword(alias = "输入密码", category = "0", attributes = "4")
@Return(name = "result", comment = "输入密码", type = DataType.BOOLEAN)
public Boolean inputPassword(
IExecuteContext context,
@ -1325,7 +1391,7 @@ public class CommonTools {
}
/**
* <p></p>
* <p>(ocr)</p>
*
* @param deviceDriver
* @param targets
@ -1334,7 +1400,7 @@ public class CommonTools {
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "密码输入(ocr)", category = "0", attributes = "45", commonlyUse = true)
@Keyword(alias = "密码输入(ocr)", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "输入结果", type = DataType.BOOLEAN)
public boolean inputPasswordByOcr(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -1396,7 +1462,7 @@ public class CommonTools {
* @param value
* @return
*/
@Keyword(alias = "识别金额", category = "0", attributes = "45")
@Keyword(alias = "识别金额", category = "0", attributes = "4")
@Return(name = "result", comment = "输入的结果", type = DataType.OBJECT)
public String getElementMoneyText(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@ -1447,6 +1513,127 @@ public class CommonTools {
return result;
}
/**
* <p></p>
*
* @param deviceDriver
* @param waitTimeout
* @param preExecuteWait
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "屏幕截图", category = "0", attributes = "4", commonlyUse = true)
@Return(name = "result", comment = "是否截图成功", type = DataType.BOOLEAN)
public Boolean screenShot(
IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout,
@Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait,
@Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait
) {
ScreenShotThread screenShotThread = new ScreenShotThread(context,deviceDriver,waitTimeout,preExecuteWait,sufExecuteWait);
Future<Boolean> future = executorService.submit(screenShotThread);
Boolean aBoolean = null;
deviceDriver.setFuture(future);
try {
aBoolean = future.get(waitTimeout, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warn("该线程被中断了。。。。。。");
throw new ExecuteException("执行中断");
} catch (ExecutionException e) {
logger.warn("在执行的时候报错了。。。。");
Throwable cause = e.getCause();
if (cause instanceof ExecuteException) {
ExecuteException ee = (ExecuteException) cause;
throw ee;
} else if (cause instanceof ParamMistakeException) {
ParamMistakeException pe = (ParamMistakeException) cause;
throw pe;
} else if (cause instanceof NoSuchSessionException) {
NoSuchSessionException ne = (NoSuchSessionException) cause;
throw ne;
} else if (cause instanceof EnvironmentException) {
EnvironmentException ee = (EnvironmentException) cause;
throw ee;
}else if (cause instanceof StaleElementReferenceException) {
throw new ExecuteException("当前操作的元素已经失效");
} else {
logger.error("出现了其他的问题。。。。", e);
throw new ExecuteException(cause.getMessage());
}
} catch (TimeoutException e) {
logger.warn("处理超时了。。。。。。");
throw new ExecuteException("查找文本超时");
} catch (CancellationException e) {
logger.warn("该操作被取消了");
throw new ExecuteException("用户取消,执行失败");
}
return aBoolean;
}
/**
* <p>OCR</p>
*
* @param deviceDriver
* @param targets
* @param waitTimeout
* @param preExecuteWait
* @param sufExecuteWait
* @return
*/
@Keyword(alias = "获取控件的值OCR方式", category = "0", attributes = "4")
@Return(name = "result", comment = "控件的值", type = DataType.OBJECT)
public String getElementValueByOcr(IExecuteContext context,
@Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver,
@Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List<IStepTarget> targets,
@Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value,
@Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout,
@Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait,
@Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait
) {
GetElementValueByOcrThread getElementValueByOcrThread = new GetElementValueByOcrThread(context,deviceDriver,targets,waitTimeout,preExecuteWait,sufExecuteWait);
Future<String> future = executorService.submit(getElementValueByOcrThread);
String result = null;
deviceDriver.setFuture(future);
try {
result = future.get(waitTimeout, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("该线程被中断了。。。。。。");
throw new ExecuteException("执行中断");
} catch (ExecutionException e) {
logger.warn("在执行的时候报错了。。。。");
Throwable cause = e.getCause();
if (cause instanceof ExecuteException) {
ExecuteException ee = (ExecuteException) cause;
throw ee;
} else if (cause instanceof ParamMistakeException) {
ParamMistakeException pe = (ParamMistakeException) cause;
throw pe;
} else if (cause instanceof NoSuchSessionException) {
NoSuchSessionException ne = (NoSuchSessionException) cause;
throw ne;
} else if (cause instanceof EnvironmentException) {
EnvironmentException ee = (EnvironmentException) cause;
throw ee;
} else if (cause instanceof StaleElementReferenceException) {
throw new ExecuteException("当前操作的元素已经失效");
} else {
logger.error("出现了其他的问题。。。。", e);
throw new ExecuteException(cause.getMessage());
}
} catch (TimeoutException e) {
logger.warn("处理超时了。。。。。。");
throw new ExecuteException("查找元素超时");
} catch (CancellationException e) {
logger.warn("该操作被取消了");
throw new ExecuteException("用户取消,执行失败");
}
setParamValue(context, value, result);
return result;
}
private void setParamValue(IExecuteContext context, String variable, String value) {
try {
if (variable.contains("#{") && variable.contains("}")) {

View File

@ -0,0 +1,189 @@
package net.northking.cctp.element.mobile.thread;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.ios.IOSDriver;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.exception.ParamMistakeException;
import net.northking.cctp.element.mobile.constants.ConfigPath;
import net.northking.cctp.element.mobile.constants.UsingType;
import net.northking.cctp.element.mobile.entity.PointMessage;
import net.northking.cctp.element.mobile.utils.CommonUtils;
import net.northking.cctp.element.mobile.utils.HttpUtils;
import net.northking.cctp.element.mobile.utils.phoneUtils.ElementUtil;
import net.northking.cctp.element.mobile.utils.phoneUtils.ScreenUtil;
import org.openqa.selenium.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class GetElementValueByOcrThread implements Callable<String> {
private Logger logger = LoggerFactory.getLogger(GetElementValueByOcrThread.class);
private IExecuteContext context;
private DeviceDriver deviceDriver;
private List<IStepTarget> targets;
private Integer waitTimeout;
private Float preExecuteWait;
private Float sufExecuteWait;
public GetElementValueByOcrThread(IExecuteContext context, DeviceDriver deviceDriver, List<IStepTarget> targets, Integer waitTimeout, Float preExecuteWait, Float sufExecuteWait) {
this.context = context;
this.deviceDriver = deviceDriver;
this.targets = targets;
this.waitTimeout = waitTimeout;
this.preExecuteWait = preExecuteWait;
this.sufExecuteWait = sufExecuteWait;
}
@Override
public String call() throws Exception {
CommonUtils.handlePreExecuteWait(preExecuteWait);
if (null == waitTimeout || waitTimeout <= 0) {
waitTimeout = 30;
}
String result = "";
AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) deviceDriver;
WebElement webElement = null;
for (IStepTarget target : targets) {
webElement = xpathFind(driver, target);
if (webElement != null) {
result = getElementValueByOcr(context, driver, webElement);
break;
}
}
CommonUtils.handleSufExecuteWait(sufExecuteWait);
return result;
}
/**
* xpath
*
* @param driver
* @param target
*/
private WebElement xpathFind(AppiumDriver<WebElement> driver, IStepTarget target) {
JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
logger.info("拿到的xpath:{}", xpath);
try {
driver.manage().timeouts().implicitlyWait(waitTimeout, TimeUnit.SECONDS);
} catch (NoSuchSessionException e) {
throw e;
}
WebElement webElement = null;
List<WebElement> elements = null;
try {
elements = driver.findElements(By.xpath(xpath));
} catch (WebDriverException e) {
logger.error("driver查找控件异常", e);
throw new ExecuteException("设备自动化驱动连接异常,查找控件失败");
}
logger.info("找到元素{}个", elements.size());
if (!CollectionUtils.isEmpty(elements)) { //xpath找到了
webElement = elements.get(0);
}
return webElement;
}
public String getElementValueByOcr(IExecuteContext context, AppiumDriver<WebElement> driver, WebElement webElement) {
String imgBase64 = null;
try {
imgBase64 = ocrFindCodeArea(webElement, driver, context);
} catch (NoSuchSessionException | ExecuteException | ParamMistakeException e) {
throw e;
} catch (Exception e) {
logger.error("查找区域截图失败", e);
throw new ExecuteException("查找区域截图失败");
}
if (null == imgBase64) {
throw new ExecuteException("未截取到区域");
}
String ocrAddress = context.getProperty(ConfigPath.OCR_GET_ALL_TEXT_NUM_KEY);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("img_base64", imgBase64);
ocrParamMap.put("targets", "");
HttpEntity<Map> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
List ocrResultList = null;
try {
logger.info("传参:{}", JSON.toJSONString(ocrParamMap));
ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
logger.info("得到的ocr结果{}", ocrResultList);
} catch (Exception e) {
logger.error("ocr失败", e);
}
List<String> dataList = new ArrayList<>();
if (!CollectionUtils.isEmpty(ocrResultList)) {
if (!CollectionUtils.isEmpty(ocrResultList)) {
for (Object o : ocrResultList) {
dataList.add(((Map) o).get("text") + "");
}
}
}
return JSON.toJSONString(dataList);
} catch (Exception e) {
logger.error("请求ocr接口失败接口地址{}", ocrAddress, e);
throw new ExecuteException("请求ocr识别接口失败请查看ocr识别服务是否开启");
}
}
public String ocrFindCodeArea(WebElement webElement, AppiumDriver<WebElement> driver, IExecuteContext context) {
String result = null;
Rectangle rect = webElement.getRect();
int x = rect.getX();
int y = rect.getY();
int width = rect.getWidth();
int height = rect.getHeight();
Map<String, Object> deviceInfo = context.getDeviceInfo();
String resolution = (String) deviceInfo.get("resolution");
String[] split = resolution.split("\\*");
int screenLength = Integer.parseInt(split[0]);
int screenWidth = Integer.parseInt(split[1]);
logger.info("通过xpth找到元素的x{}y{} width{} height{}screenLength{}screenWidth{}", x, y, width, height, screenLength,screenWidth);
Map<String, Object> paramMap = new HashMap<>();
if (driver instanceof IOSDriver) {
paramMap.put("platform", "1");
} else {
paramMap.put("platform", "0");
}
paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
paramMap.put("length", width);
paramMap.put("width", height);
paramMap.put("resolution", resolution);
paramMap.put("x", x);
paramMap.put("y", y);
paramMap.put("screenLength", screenLength);
paramMap.put("screenWidth", screenWidth);
logger.info("参数:{}", JSON.toJSONString(paramMap));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
String host = driver.getRemoteAddress().getHost(); //上位机地址
String port = (String) deviceInfo.get("port"); //上位机端口
String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
try {
result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
} catch (Exception e) {
logger.error("截图失败", e);
}
return result;
}
}

View File

@ -0,0 +1,45 @@
package net.northking.cctp.element.mobile.thread;
import io.appium.java_client.AppiumDriver;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.mobile.entity.PointMessage;
import net.northking.cctp.element.mobile.utils.CommonUtils;
import net.northking.cctp.element.mobile.utils.phoneUtils.ElementUtil;
import net.northking.cctp.element.mobile.utils.phoneUtils.HandleUtils;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.Callable;
public class ScreenShotThread implements Callable<Boolean> {
private Logger logger = LoggerFactory.getLogger(ScreenShotThread.class);
private IExecuteContext context;
private DeviceDriver deviceDriver;
private Integer waitTimeout;
private Float preExecuteWait;
private Float sufExecuteWait;
public ScreenShotThread(IExecuteContext context, DeviceDriver deviceDriver, Integer waitTimeout, Float preExecuteWait, Float sufExecuteWait) {
this.context = context;
this.deviceDriver = deviceDriver;
this.waitTimeout = waitTimeout;
this.preExecuteWait = preExecuteWait;
this.sufExecuteWait = sufExecuteWait;
}
@Override
public Boolean call() throws Exception {
CommonUtils.handlePreExecuteWait(preExecuteWait);
AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) deviceDriver;
CommonUtils.screenShot(context,driver);
CommonUtils.handleSufExecuteWait(sufExecuteWait);
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More