执行任务的步骤截图,异步上传优化

1.文件服务的数据库cctp_attachment的表增加字段:存储路径storage_path,长度128位
2.cctp-commons关于的文件上传增加支持外部传入指定id的逻辑
3.由于使用了seaweedfs,上传完成后会使用volume id和file key重新作为附件的id存入到数据,所以增加了storage_path保存外部传入id
4.在下载附件的逻辑的增加判断,如果分割后的字符串存在多个/那么使用storagePath查询附件id再下载
5.执行引擎的修改是对pc的截图异步上传。
6.上位机的修改是ios和安卓的截图异步上传。
7.大致逻辑:步骤截图后,将文件名、文件路径、任务id、xy坐标、租户id、业务代码存入到本地h2数据库;通过定时任务,如果线程池是空闲,那么每次查询前20条数据,使用线程池进行遍历上传。
master
李杰应 2024-10-25 04:40:53 +08:00
parent c053ed2583
commit 8666c2cf92
39 changed files with 1092 additions and 18 deletions

View File

@ -0,0 +1,8 @@
package net.northking.cctp.se.attach;
public interface AttachInfoService {
AttachmentDB save(String filename, String filePath, String tenantId, String objectId, String bizCode);
AttachmentDB save(String filename, String filePath, String tenantId, int x, int y, String objectId, String bizCode);
}

View File

@ -0,0 +1,43 @@
package net.northking.cctp.se.attach;
import net.northking.cctp.se.repository.AttachmentInfoRepository;
import net.northking.cctp.se.util.AttachUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class AttachInfoServiceImpl implements AttachInfoService {
private static final Logger logger = LoggerFactory.getLogger(AttachInfoServiceImpl.class);
@Autowired
private AttachmentInfoRepository attachRepo;
@Override
public AttachmentDB save(String filename, String filePath, String tenantId, String objectId, String bizCode) {
return save(filename, filePath, tenantId, -1, -1, objectId, bizCode);
}
@Override
public AttachmentDB save(String filename, String filePath, String tenantId, int x, int y, String objectId, String bizCode) {
AttachmentDB attachmentDB = new AttachmentDB();
attachmentDB.setId(AttachUtils.createId(filename));
attachmentDB.setUrl(AttachUtils.exchangeToUrl(attachmentDB.getId(), tenantId));
attachmentDB.setFilename(filename);
attachmentDB.setFilePath(filePath);
attachmentDB.setCreatedTime(new Date());
attachmentDB.setTenantId(tenantId);
attachmentDB.setFailureTimes(0);
attachmentDB.setX(x);
attachmentDB.setY(y);
attachmentDB.setObjectId(objectId);
attachmentDB.setBizCode(bizCode);
attachRepo.saveAndFlush(attachmentDB);
return attachmentDB;
}
}

View File

@ -0,0 +1,54 @@
package net.northking.cctp.se.attach;
import cn.hutool.core.bean.BeanUtil;
import net.northking.cctp.se.file.Attachment;
import net.northking.cctp.se.repository.AttachmentInfoRepository;
import net.northking.cctp.se.util.HttpUtils;
import net.northking.cctp.se.util.SpringUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*
*/
public class AttachInfoUpload implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(AttachInfoUpload.class);
private AttachmentDB attachmentDB;
public AttachInfoUpload(AttachmentDB attachmentDB) {
this.attachmentDB = attachmentDB;
}
@Override
public void run() {
if (attachmentDB.getX() > 0 && attachmentDB.getY() > 0) {
addSignature();
}
AttachmentInfoRepository repo = SpringUtils.getBean(AttachmentInfoRepository.class);
try {
Attachment upload = HttpUtils.upload(attachmentDB.getFilePath(),
attachmentDB.getTenantId(), attachmentDB.getObjectId(),
attachmentDB.getBizCode(), attachmentDB.getId());
if (upload != null && StringUtils.isNotBlank(upload.getUrlPath())) {
// 上传成功
logger.debug("文件上传成功:{} storagePath -> {}", BeanUtil.beanToMap(upload).toString(), attachmentDB.getId());
// 删除数据库信息
repo.deleteById(attachmentDB.getId());
}
} catch (Exception e) {
logger.error("附件上传失败", e);
// 附件信息的失败次数加1并更新数据库数据
attachmentDB.setFailureTimes(attachmentDB.getFailureTimes() + 1);
repo.saveAndFlush(attachmentDB);
}
}
private void addSignature() {
// 根据信息添加红点或者其他标记
}
}

View File

@ -0,0 +1,57 @@
package net.northking.cctp.se.attach;
import lombok.Data;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
*
*/
@Data
@Entity
@Table(name = "attachment_info")
@ToString
public class AttachmentDB {
// 格式:/yyyy/MM/dd/32位uuid.文件类型
@Id
private String id;
// 格式: tenantId_yyyy_MM_dd_32位uuid.文件类型
private String url;
private String filename;
private String filePath;
/**
*
*/
private Date createdTime;
/**
* x
*/
private int x;
/**
* y
*/
private int y;
/**
* id
*/
private String tenantId;
/**
*
*/
private int failureTimes;
private String objectId;
private String bizCode;
}

View File

@ -2,6 +2,7 @@ package net.northking.cctp.se.config;
import com.google.common.annotations.VisibleForTesting;
import lombok.Data;
import net.northking.cctp.se.thread.AttachInfoExecutorPool;
import net.northking.cctp.se.thread.ExecutorPool;
import net.northking.cctp.se.util.SpringUtils;
import org.slf4j.Logger;
@ -26,6 +27,11 @@ public class ExecutorPoolConfig {
return executorPool;
}
@Bean
public AttachInfoExecutorPool createAttachInfoPool() {
AttachInfoExecutorPool pool = new AttachInfoExecutorPool();
return pool;
}
@ConfigurationProperties(prefix = "atu.engine.executor-pool")
@Configuration

View File

@ -13,9 +13,12 @@ import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.pc.driver.dto.EngineResultDto;
import net.northking.cctp.pc.driver.socket.PCDriver;
import net.northking.cctp.se.attach.AttachInfoService;
import net.northking.cctp.se.attach.AttachmentDB;
import net.northking.cctp.se.file.Attachment;
import net.northking.cctp.se.util.HttpUtils;
import net.northking.cctp.se.util.JsonUtils;
import net.northking.cctp.se.util.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
@ -281,11 +284,13 @@ public class PCDeviceConnection extends AbstractDeviceConnection {
log.debug("截图上传入参tenantId{}",tenantId);
log.debug("截图上传入参initData{}", JSONObject.toJSONString(initData));
log.debug("截图入参taskId,{}", initData.get("taskId"));
Attachment upload = HttpUtils.upload(localPath, tenantId, (String)initData.get("taskId"), FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
// Attachment upload = HttpUtils.upload(localPath, tenantId, (String)initData.get("taskId"), FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
String filename = localPath.substring(localPath.lastIndexOf("\\") + 1);
AttachmentDB upload = SpringUtils.getBean(AttachInfoService.class).save(filename, localPath, tenantId, (String)initData.get("taskId"), FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
if (ObjectUtil.isNull(upload) || StringUtils.isBlank(upload.getId())){
throw new RuntimeException("截图文件上传信息为空");
}
return upload.getUrlPath();
return upload.getUrl();
}catch (Exception e){
log.error("截图上传失败", e);
throw new RuntimeException(e.getMessage());

View File

@ -0,0 +1,11 @@
package net.northking.cctp.se.repository;
import net.northking.cctp.se.attach.AttachmentDB;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AttachmentInfoRepository extends JpaRepository<AttachmentDB, String> {
}

View File

@ -0,0 +1,64 @@
package net.northking.cctp.se.scheduler;
import lombok.extern.slf4j.Slf4j;
import net.northking.cctp.se.attach.AttachInfoUpload;
import net.northking.cctp.se.attach.AttachmentDB;
import net.northking.cctp.se.config.AtuServerConfig;
import net.northking.cctp.se.repository.AttachmentInfoRepository;
import net.northking.cctp.se.thread.AttachInfoExecutorPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* <p>Title: AttachUploadScheduler</p>
* <p>Description: </p>
*
*/
@Component
@Slf4j
public class AttachUploadScheduler {
@Autowired
private AtuServerConfig atuServerConfig;
@Autowired
private AttachmentInfoRepository repo;
@Autowired
private AttachInfoExecutorPool pool;
/**
*
* 20, 3
*/
@Scheduled(initialDelay = 20 * 1000, fixedDelay = 3 * 1000)
public void upload() {
try {
// 是否都空闲了
if (!pool.isUploadAvailable()) {
return;
}
// 查询前面20条数据
Sort orderBy = Sort.by(Sort.Order.asc("failureTimes"), Sort.Order.asc("createdTime"));
Pageable pageable = PageRequest.of(0, 10, orderBy);
Page<AttachmentDB> resultPage = repo.findAll(pageable);
List<AttachmentDB> content = resultPage.getContent();
if (!content.isEmpty()) {
for (AttachmentDB db : content) {
AttachInfoUpload uploadRunnable = new AttachInfoUpload(db);
pool.uploadTaskSubmit(uploadRunnable);
}
}
// 便利
}catch (Exception e){
log.error("发送任务执行心跳异常", e);
}
}
}

View File

@ -19,6 +19,8 @@ import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.exception.KeywordNotFoundException;
import net.northking.cctp.element.core.keyword.KeywordArgument;
import net.northking.cctp.element.core.type.LibraryConstant;
import net.northking.cctp.se.attach.AttachInfoService;
import net.northking.cctp.se.attach.AttachmentDB;
import net.northking.cctp.se.component.ComponentLibrary;
import net.northking.cctp.se.component.ComponentManagementService;
import net.northking.cctp.se.component.InternalComponent;
@ -1368,12 +1370,15 @@ public class ScriptRuntimeExecutor implements ScriptExecutor {
log.debug("localPath = " + stepSnapshot);
log.debug("tenantId = " + tenantId);
if (StrUtil.isNotEmpty(tenantId) && StrUtil.isNotEmpty(stepSnapshot)){
Attachment upload = HttpUtils.upload(stepSnapshot, tenantId, currentTaskId, FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
// Attachment upload = HttpUtils.upload(stepSnapshot, tenantId, currentTaskId, FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
String filename = stepSnapshot.substring(stepSnapshot.lastIndexOf("\\") + 1);
AttachmentDB upload = SpringUtils.getBean(AttachInfoService.class).save(filename, stepSnapshot, tenantId, currentTaskId, FileBusinessTypeEnum.PC_TASK_SCREENSHOT.getCode());
if (ObjectUtil.isNull(upload) || StringUtils.isBlank(upload.getId())) {
throw new RuntimeException("截图文件上传信息为空");
}
log.debug("截图文件上传结果,{}", JSON.toJSONString(upload));
stepRet.setActualImgUri(upload.getUrlPath());
stepRet.setActualImgUri(upload.getUrl());
}
} catch (Exception e) {
log.error("获取步骤执行时截图失败", e);

View File

@ -0,0 +1,61 @@
package net.northking.cctp.se.thread;
import org.springframework.stereotype.Component;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
*/
public class AttachInfoExecutorPool {
private ThreadPoolExecutor uploadPool;
private ThreadPoolExecutor savePool;
public AttachInfoExecutorPool() {
uploadPool = new ThreadPoolExecutor(1, 3,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
savePool = new ThreadPoolExecutor(1, 3,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
*
* @param task
*/
public void uploadTaskSubmit(Runnable task) {
uploadPool.submit(task);
}
/**
* 线
* @return
*/
public boolean isUploadAvailable() {
return uploadPool.getActiveCount() == 0;
}
/**
*
* @return
*/
public int uploadTaskLeft() {
return uploadPool.getQueue().size();
}
/**
*
* @param task
*/
public void saveTaskSubmit(Runnable task) {
savePool.submit(task);
}
}

View File

@ -0,0 +1,64 @@
package net.northking.cctp.se.util;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.element.core.exception.ExecuteException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*
*/
public class AttachUtils {
private static final Logger logger = LoggerFactory.getLogger(AttachUtils.class);
/**
* id
* /yyyy/MM/dd/32uuid[.]
*
* @param filename
* @return id
*/
public static String createId(String filename) {
if (StringUtils.isBlank(filename)) {
logger.error("上传资源失败:附件名称不能为空");
throw new ExecuteException("上传资源失败:附件名称不能为空");
}
String id;
int i = filename.lastIndexOf(".");
if (i <= 0) { // 文件没有后缀名称
id = createDatePath() + UUIDUtil.create32UUID();
} else { // 文件存在后缀名称
String postfix = filename.substring(i + 1);
id = createDatePath() + UUIDUtil.create32UUID() + "." + postfix;
}
logger.debug("文件[{}] --> id: {}", filename, id);
return id;
}
public static String exchangeToUrl(String attachId, String tenantId) {
return String.format("%s%s", tenantId, encodeAttachId(attachId));
}
private static String encodeAttachId(String id) {
return id.replace("/", "_");
}
/**
*
*
* @return
*/
private static String createDatePath()
{
SimpleDateFormat format = new SimpleDateFormat("/yyyy/MM/dd/");
return format.format(new Date());
}
}

View File

@ -120,13 +120,17 @@ public class HttpUtils {
return response.getBody();
}
public static Attachment upload(String filePath, String tenantId, String objId, String businessCode) {
return upload(filePath, tenantId, objId, businessCode, null);
}
/**
*
* @param filePath
* @param tenantId
* @return
*/
public static Attachment upload(String filePath, String tenantId, String objId, String businessCode) {
public static Attachment upload(String filePath, String tenantId, String objId, String businessCode, String attachId) {
if (StringUtils.isBlank(tenantId)) {
throw new ExecuteException("上传资源失败:缺少租户信息");
}
@ -141,6 +145,9 @@ public class HttpUtils {
multipartMap.add("tenantId", tenantId);
multipartMap.add("objId", objId);
multipartMap.add("businessCode", businessCode);
if (StringUtils.isNotBlank(attachId)) {
multipartMap.add("attachId", attachId);
}
FormHttpMessageConverter converter = new FormHttpMessageConverter();
converter.addPartConverter(new ResourceHttpMessageConverter());
template.getMessageConverters().add(converter);

View File

@ -37,6 +37,11 @@ spring:
pool:
max-active: 5
max-idle: 5
task:
scheduling:
pool:
size: 3
thread-name-prefix: scheduler-task-
management:
endpoints:

View File

@ -176,6 +176,14 @@
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package net.northking.cctp.upperComputer.attach;
public interface AttachInfoService {
AttachmentDB save(String filename, String filePath, String tenantId, String objectId, String bizCode);
AttachmentDB save(String filename, String filePath, String tenantId, int x, int y, String objectId, String bizCode);
}

View File

@ -0,0 +1,42 @@
package net.northking.cctp.upperComputer.attach;
import net.northking.cctp.upperComputer.utils.AttachUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class AttachInfoServiceImpl implements AttachInfoService {
private static final Logger logger = LoggerFactory.getLogger(AttachInfoServiceImpl.class);
@Autowired
private AttachmentInfoRepository attachRepo;
@Override
public AttachmentDB save(String filename, String filePath, String tenantId, String objectId, String bizCode) {
return save(filename, filePath, tenantId, -1, -1, objectId, bizCode);
}
@Override
public AttachmentDB save(String filename, String filePath, String tenantId, int x, int y, String objectId, String bizCode) {
AttachmentDB attachmentDB = new AttachmentDB();
attachmentDB.setId(AttachUtils.createId(filename));
attachmentDB.setUrl(AttachUtils.exchangeToUrl(attachmentDB.getId(), tenantId));
attachmentDB.setFilename(filename);
attachmentDB.setFilePath(filePath);
attachmentDB.setCreatedTime(new Date());
attachmentDB.setTenantId(tenantId);
attachmentDB.setFailureTimes(0);
attachmentDB.setX(x);
attachmentDB.setY(y);
attachmentDB.setObjectId(objectId);
attachmentDB.setBizCode(bizCode);
attachRepo.saveAndFlush(attachmentDB);
return attachmentDB;
}
}

View File

@ -0,0 +1,99 @@
package net.northking.cctp.upperComputer.attach;
import cn.hutool.core.bean.BeanUtil;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.entity.DebuggerDeviceInfo;
import net.northking.cctp.upperComputer.utils.HttpUtils;
import net.northking.cctp.upperComputer.utils.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
*
*
*/
public class AttachInfoUpload implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(AttachInfoUpload.class);
private AttachmentDB attachmentDB;
public AttachInfoUpload(AttachmentDB attachmentDB) {
this.attachmentDB = attachmentDB;
}
@Override
public void run() {
AttachmentInfoRepository repo = SpringUtils.getBean(AttachmentInfoRepository.class);
try {
String filePath = attachmentDB.getFilePath();
if (attachmentDB.getX() > 0 && attachmentDB.getY() > 0) {
filePath = addSignatureToImage(attachmentDB.getFilePath(), attachmentDB.getX(), attachmentDB.getY());
}
String serverAddr = SpringUtils.getProperties("nk.mobile-computer.serverAddr");
String publicUploadAddr = SpringUtils.getProperties("nk.mobile-computer.publicUploadAddr");
String uploadUrl = serverAddr + publicUploadAddr;
Attachment upload = HttpUtils.upload(uploadUrl,
filePath, attachmentDB.getTenantId(),
attachmentDB.getObjectId(), attachmentDB.getBizCode(),
attachmentDB.getId());
if (upload != null && StringUtils.isNotBlank(upload.getUrlPath())) {
// 上传成功
logger.debug("文件上传成功:{} storagePath -> {}", BeanUtil.beanToMap(upload).toString(), attachmentDB.getId());
// 删除数据库信息
repo.deleteById(attachmentDB.getId());
}
} catch (Exception e) {
logger.error("附件上传失败", e);
// 附件信息的失败次数加1并更新数据库数据
attachmentDB.setFailureTimes(attachmentDB.getFailureTimes() + 1);
repo.saveAndFlush(attachmentDB);
}
}
/**
*
* @param filePath
* @param x
* @param y
* @return
*/
private String addSignatureToImage(String filePath, Integer x, Integer y) {
if (x == null || y == null) {
return filePath;
}
File originFile = new File(filePath);
if (!originFile.exists()) {
logger.info("截图文件不存在:{}", originFile.getAbsolutePath());
return filePath;
}
String fullName = originFile.getName();
String suffix = fullName.substring(fullName.lastIndexOf(".") + 1);
String name = fullName.substring(0, fullName.lastIndexOf("."));
File targetFile = new File(originFile.getParentFile().getAbsolutePath() + File.separator + name + "_with_dot" + "." + suffix);
try {
BufferedImage bi = ImageIO.read(originFile);
Graphics2D g2d = bi.createGraphics(); // 生成画布
g2d.setColor(Color.RED); // 红色
g2d.fillOval(x, y, 20, 20); // 在图片的xy上画上一个直径20的实心原点
g2d.dispose();
ImageIO.write(bi, suffix, targetFile);
} catch (IOException e) {
logger.error("io异常", e);
} catch (Exception e) {
logger.error("添加红点失败", e);
}
if (!targetFile.exists()) {
targetFile = originFile;
}
return targetFile.getAbsolutePath();
}
}

View File

@ -0,0 +1,155 @@
package net.northking.cctp.upperComputer.attach;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
*
*/
@Table(name = "attachment_info")
public class AttachmentDB {
@Id
private String id;
private String url;
private String filename;
private String filePath;
/**
*
*/
private Date createdTime;
/**
* x
*/
private int x;
/**
* y
*/
private int y;
/**
* id
*/
private String tenantId;
/**
*
*/
private int failureTimes;
private String objectId;
private String bizCode;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public int getFailureTimes() {
return failureTimes;
}
public void setFailureTimes(int failureTimes) {
this.failureTimes = failureTimes;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public String getBizCode() {
return bizCode;
}
public void setBizCode(String bizCode) {
this.bizCode = bizCode;
}
@Override
public String toString() {
return "AttachmentDB{" +
"id='" + id + '\'' +
", url='" + url + '\'' +
", filename='" + filename + '\'' +
", filePath='" + filePath + '\'' +
", createdTime=" + createdTime +
", x=" + x +
", y=" + y +
", tenantId='" + tenantId + '\'' +
", failureTimes=" + failureTimes +
", objectId='" + objectId + '\'' +
", bizCode='" + bizCode + '\'' +
'}';
}
}

View File

@ -0,0 +1,10 @@
package net.northking.cctp.upperComputer.attach;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AttachmentInfoRepository extends JpaRepository<AttachmentDB, String> {
}

View File

@ -0,0 +1,21 @@
package net.northking.cctp.upperComputer.config;
import net.northking.cctp.upperComputer.thread.AttachInfoExecutorPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ExecutorPoolConfig {
private static final Logger logger = LoggerFactory.getLogger(ExecutorPoolConfig.class);
@Bean
public AttachInfoExecutorPool createAttachInfoPool() {
AttachInfoExecutorPool pool = new AttachInfoExecutorPool();
return pool;
}
}

View File

@ -7,6 +7,8 @@ import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import net.northking.cctp.upperComputer.attach.AttachInfoService;
import net.northking.cctp.upperComputer.attach.AttachmentDB;
import net.northking.cctp.upperComputer.config.MobileProperty;
import net.northking.cctp.upperComputer.deviceManager.screen.AndroidScreenResponseThread;
import net.northking.cctp.upperComputer.deviceManager.thread.AndroidDeviceInitThread;
@ -40,10 +42,14 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static io.appium.java_client.remote.AndroidMobileCapabilityType.RESET_KEYBOARD;
@ -70,6 +76,9 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService {
private Adb adb;
@Autowired
private AttachInfoService attachInfoService;
@Value("${appium.server.host:127.0.0.1}")
private String appiumHost;
@ -731,11 +740,13 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService {
String tenantId = info.getTenantId();
logger.debug("开始上传截图,任务信息:{}",JSON.toJSONString(info));
String taskId = info.getTaskId();
Attachment upload = HttpUtils.upload(mobileProperty.getServerAddr() + mobileProperty.getPublicUploadAddr(), file.getAbsolutePath(), tenantId, taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
// Attachment upload = HttpUtils.upload(mobileProperty.getServerAddr() + mobileProperty.getPublicUploadAddr(), file.getAbsolutePath(), tenantId, taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
// todo: 把截图信息保存到数据库待处理
AttachmentDB upload = attachInfoService.save(file.getName(), file.getAbsolutePath(), tenantId, info.getX(), info.getY(), taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
if (null != upload && StringUtils.isNotBlank(upload.getId())) {
logger.debug("文件上传成功返回id{}", upload.getId());
path = upload.getUrlPath();
path = upload.getUrl();
}
} catch (ExecuteException e) {
logger.error("上传截图失败", e);

View File

@ -4,6 +4,8 @@ import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.AppiumDriver;
import net.northking.cctp.upperComputer.attach.AttachInfoService;
import net.northking.cctp.upperComputer.attach.AttachmentDB;
import net.northking.cctp.upperComputer.config.MobileProperty;
import net.northking.cctp.upperComputer.deviceManager.common.PyMobileDevice;
import net.northking.cctp.upperComputer.deviceManager.screen.IosScreenResponseThread;
@ -58,6 +60,9 @@ public class IosDebuggerServiceImpl extends AbstractDebuggerService {
super.setMobileProperty(mobileProperty);
}
@Autowired
private AttachInfoService attachInfoService;
@Override
public Object getUiTree(String deviceId) {
PhoneEntity phoneEntity = IOSDeviceManager.getInstance().getPhoneEntity(deviceId);
@ -194,17 +199,19 @@ public class IosDebuggerServiceImpl extends AbstractDebuggerService {
@Override
public String uploadShotAllScreen(DebuggerDeviceInfo info) {
File file = ScreenShotUtils.getIOSMobileScreenShot(info.getDeviceId());
File file = deviceHandleHelper.getScreenShotFile(info.getDeviceId());
String path = null;
try {
//租户id
String tenantId = info.getTenantId();
String taskId = info.getTaskId();
logger.debug("开始上传截图,任务信息:{}",JSON.toJSONString(info));
Attachment upload = HttpUtils.upload(mobileProperty.getServerAddr() + mobileProperty.getPublicUploadAddr(), file.getAbsolutePath(), tenantId, taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
// Attachment upload = HttpUtils.upload(mobileProperty.getServerAddr() + mobileProperty.getPublicUploadAddr(), file.getAbsolutePath(), tenantId, taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
// todo: 把截图信息保存到数据库待处理
AttachmentDB upload = attachInfoService.save(file.getName(), file.getAbsolutePath(), tenantId, info.getX(), info.getY(), taskId, FileBusinessTypeEnum.MOBILE_TASK_SCREENSHOT.getCode());
if (null != upload && org.apache.commons.lang3.StringUtils.isNotBlank(upload.getId())) {
logger.debug("文件上传成功返回id{}", upload.getId());
path = upload.getUrlPath();
path = upload.getUrl();
}
} catch (ExecuteException e) {
logger.error("截图失败",e);

View File

@ -0,0 +1,59 @@
package net.northking.cctp.upperComputer.thread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
*/
public class AttachInfoExecutorPool {
private ThreadPoolExecutor uploadPool;
private ThreadPoolExecutor savePool;
public AttachInfoExecutorPool() {
uploadPool = new ThreadPoolExecutor(1, 3,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
savePool = new ThreadPoolExecutor(1, 3,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
*
* @param task
*/
public void uploadTaskSubmit(Runnable task) {
uploadPool.submit(task);
}
/**
* 线
* @return
*/
public boolean isUploadAvailable() {
return uploadPool.getActiveCount() == 0;
}
/**
*
* @return
*/
public int uploadTaskLeft() {
return uploadPool.getQueue().size();
}
/**
*
* @param task
*/
public void saveTaskSubmit(Runnable task) {
savePool.submit(task);
}
}

View File

@ -0,0 +1,64 @@
package net.northking.cctp.upperComputer.utils;
import net.northking.cctp.upperComputer.exception.ParamMistakeException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
*
*/
public class AttachUtils {
private static final Logger logger = LoggerFactory.getLogger(AttachUtils.class);
/**
* id
* /yyyy/MM/dd/32uuid[.]
*
* @param filename
* @return id
*/
public static String createId(String filename) {
if (StringUtils.isBlank(filename)) {
logger.error("上传资源失败:附件名称不能为空");
throw new ParamMistakeException("上传资源失败:附件名称不能为空");
}
String id;
int i = filename.lastIndexOf(".");
if (i <= 0) { // 文件没有后缀名称
id = createDatePath() + UUID.randomUUID().toString().replace("-","");
} else { // 文件存在后缀名称
String postfix = filename.substring(i + 1);
id = createDatePath() + UUID.randomUUID().toString().replace("-","") + "." + postfix;
}
logger.debug("文件[{}] --> id: {}", filename, id);
return id;
}
public static String exchangeToUrl(String attachId, String tenantId) {
return String.format("%s%s", tenantId, encodeAttachId(attachId));
}
private static String encodeAttachId(String id) {
return id.replace("/", "_");
}
/**
*
*
* @return
*/
private static String createDatePath()
{
SimpleDateFormat format = new SimpleDateFormat("/yyyy/MM/dd/");
return format.format(new Date());
}
}

View File

@ -311,6 +311,7 @@ public class HttpUtils {
}
return result;
}
/**
*
* @param filePath
@ -318,6 +319,16 @@ public class HttpUtils {
* @return
*/
public static Attachment upload(String url, String filePath, String tenantId, String objId, String businessCode) {
return upload(url, filePath, tenantId, objId, businessCode, null);
}
/**
*
* @param filePath
* @param tenantId
* @return
*/
public static Attachment upload(String url, String filePath, String tenantId, String objId, String businessCode, String attachId) {
if (StringUtils.isBlank(tenantId)) {
throw new ExecuteException("上传资源失败:缺少租户信息");
}
@ -330,6 +341,9 @@ public class HttpUtils {
multipartMap.add("tenantId", tenantId);
multipartMap.add("objId", objId);
multipartMap.add("businessCode", businessCode);
if (StringUtils.isNotBlank(attachId)) {
multipartMap.add("attachId", attachId);
}
FormHttpMessageConverter converter = new FormHttpMessageConverter();
converter.addPartConverter(new ResourceHttpMessageConverter());
template.getMessageConverters().add(converter);

View File

@ -14,6 +14,29 @@ spring:
jackson:
serialization:
write-dates-as-timestamps: true
jpa:
show-sql: false
hibernate:
ddl-auto: update
database: h2
open-in-view: false
datasource:
url: jdbc:h2:file:./.h2/db
username: engine
password: 123456
driver-class-name: org.h2.Driver
h2:
console:
path: /h2
enabled: true
settings:
trace: false
web-allow-others: true
task:
scheduling:
pool:
size: 3
thread-name-prefix: scheduler-task-
nk:
mobile-computer:
password: 123456

View File

@ -19,6 +19,7 @@ public class S3File implements NKFile
private String tenantId;
private String objId;
private String businessCode;
private String storagePath;
public S3File()
{
@ -47,6 +48,33 @@ public class S3File implements NKFile
}
}
public S3File(String bucket, String tenantId, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId) {
this.bucket = bucket;
this.tenantId = tenantId;
this.contentType = multipartFile.getContentType();
this.length = multipartFile.getSize();
this.fileName = multipartFile.getOriginalFilename();
this.objId = objId;
this.businessCode = businessCode.getCode();
assert fileName != null;
int index = fileName.lastIndexOf(".");
if (null != attachId && attachId.length() > 0) {
this.id = attachId;
this.storagePath = attachId;
} else {
if (index <= 0) {
// 文件没有后缀名
this.id = createDatePath() + UUIDUtil.create32UUID();
this.storagePath = this.id;
} else {
String postfix = fileName.substring(index + 1);
// 文件ID长度= 32 + 11 + 32 + 后缀名(<10
this.id = createDatePath() + UUIDUtil.create32UUID() + "." + postfix;
this.storagePath = this.id;
}
}
}
public S3File(String bucket, String tenantId, String suffixName)
{
this.bucket = bucket;
@ -209,4 +237,12 @@ public class S3File implements NKFile
public void setBusinessCode(String businessCode) {
this.businessCode = businessCode;
}
public String getStoragePath() {
return storagePath;
}
public void setStoragePath(String storagePath) {
this.storagePath = storagePath;
}
}

View File

@ -24,6 +24,18 @@ public interface SimpleStorageService
* @throws FileUploadException
*/
NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException, IOException;
/**
*
*
* @param tenant
* @param multipartFile
* @param objId Id
* @param businessCode
* @param attachId id
* @return
* @throws FileUploadException
*/
NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId) throws FileUploadException, IOException;
/**
*

View File

@ -73,6 +73,29 @@ public class MinioFileStorage implements SimpleStorageService {
return s3File;
}
/**
*
*
* @param tenant
* @param multipartFile
* @param objId Id
* @param businessCode
* @param attachId id
* @return
* @throws FileUploadException
*/
@Override
public NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId) throws FileUploadException, IOException {
S3File s3File = new S3File(properties.getBucket(), tenant, multipartFile, objId, businessCode, attachId);
putObject(s3File, multipartFile.getInputStream());
if (properties.isUseMsgQueue() && attachmentProducer != null)
{
attachmentProducer.process(s3File);
}
return s3File;
}
public NKFile uploadChunk(String tenantId, MultipartFile multipartFile, String uploadId, String chunkIndex) throws FileUploadException, IOException
{
S3File s3File = new S3File(properties.getBucket(), tenantId, multipartFile, uploadId, chunkIndex);

View File

@ -74,6 +74,30 @@ public class SeaweedFileStorage implements SimpleStorageService {
return s3File;
}
/**
*
*
* @param tenant
* @param multipartFile
* @param objId Id
* @param businessCode
* @param attachId id
* @return
* @throws FileUploadException
*/
@Override
public NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId) throws FileUploadException, IOException {
logger.debug("配置动态刷新AmazonS3Properties:{}", JSON.toJSONString(properties));
S3File s3File = new S3File(properties.getBucket(), tenant, multipartFile, objId, businessCode, attachId);
putObject(s3File, multipartFile.getInputStream());
if (properties.isUseMsgQueue() && attachmentProducer != null)
{
attachmentProducer.process(s3File);
}
return s3File;
}
public NKFile uploadChunk(String tenantId, MultipartFile multipartFile, String uploadId, String chunkIndex) throws FileUploadException, IOException
{
logger.debug("tenantId:{},uploadId:{},chunkIndex:{},fileName:{},size:{}",tenantId, uploadId, chunkIndex, multipartFile.getOriginalFilename(), multipartFile.getSize());

View File

@ -164,7 +164,8 @@ public class AttachFileCtrl
@RequestParam("file") MultipartFile multipartFile,
@RequestParam("tenantId") String tenantId,
@RequestParam(value = "objId", required = false) String objId,
@RequestParam("businessCode") String businessCode) {
@RequestParam("businessCode") String businessCode,
@RequestParam(value = "attachId", required = false) String attachId) {
ResultWrapper<Map<String, Object>> wrapper = new ResultWrapper<>();
FileBusinessTypeEnum fileBusinessTypeByCode = FileBusinessTypeEnum.getFileBusinessTypeByCode(businessCode);
if (null == fileBusinessTypeByCode) {
@ -176,7 +177,7 @@ public class AttachFileCtrl
return wrapper;
}
NKSecurityContext.setTenantId(tenantId);
return attachFileService.uploadResource(response, multipartFile, objId, fileBusinessTypeByCode);
return attachFileService.uploadResource(response, multipartFile, objId, fileBusinessTypeByCode, attachId);
}
@PostMapping(value = {"/v1/uploadForApp"},

View File

@ -68,7 +68,7 @@ public interface AttachFileService
ResultWrapper<String> mergeChunkFiles(ChunkFiles chunkFiles);
ResultWrapper<Map<String, Object>> uploadResource(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode);
ResultWrapper<Map<String, Object>> uploadResource(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId);
ResultWrapper<String> update(Map<String, String> file);

View File

@ -104,8 +104,15 @@ public class AttachFileServiceImpl implements AttachFileService
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
Attachment attachment = attachmentService.findByPK(tenantId, objectId);
String id = objectId;
// 判断objectId是id还是storagePath如果是id那么只有一个/
if (objectId.lastIndexOf("/") > 0) {
Attachment attachment = attachmentService.queryByStoragePath(tenantId, objectId); // 通过storagePath查询附件的id
if (attachment != null) {
id = attachment.getId();
}
}
Attachment attachment = attachmentService.findByPK(tenantId, id);
boolean check = false;
int i=0;
while (i<10){
@ -115,7 +122,7 @@ public class AttachFileServiceImpl implements AttachFileService
} catch (Exception e) {
logger.error("等待报错",e);
}
attachment = attachmentService.findByPK(tenantId, objectId);
attachment = attachmentService.findByPK(tenantId, id);
}else{
check=true;
break;
@ -194,7 +201,7 @@ public class AttachFileServiceImpl implements AttachFileService
}
@Override
public ResultWrapper<Map<String, Object>> uploadResource(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode)
public ResultWrapper<Map<String, Object>> uploadResource(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId)
{
ResultWrapper<Map<String, Object>> resultWrapper = new ResultWrapper<>();
resultWrapper.setData(new HashMap<>());
@ -202,7 +209,7 @@ public class AttachFileServiceImpl implements AttachFileService
String userId = NKSecurityContext.getUserId();
try
{
NKFile nkFile = storageService.upload(tenantId, multipartFile, objId, businessCode);
NKFile nkFile = storageService.upload(tenantId, multipartFile, objId, businessCode, attachId);
Attachment attachment = new Attachment();
BeanUtils.copyProperties(nkFile, attachment);
@ -211,6 +218,7 @@ public class AttachFileServiceImpl implements AttachFileService
attachment.setObjId(objId);
attachment.setBusinessCode(businessCode.getCode());
attachment.setOverTime(businessCode.getOverTime());
attachment.setStoragePath(attachId);
attachmentService.insert(attachment);
String urlPath = tenantId + encodeFileId(nkFile.getId());

View File

@ -50,4 +50,6 @@ public interface AttachmentDao extends AttachmentMapper {
List<Attachment> queryByOverTime(@Param("overTime") LocalDateTime overTime, @Param("tableSuffix") String tableSuffix);
List<Attachment> queryByBusiness(@Param("date") LocalDate date, @Param("tableSuffix") String tableSuffix);
Attachment queryByStoragePath(Attachment attachment);
}

View File

@ -39,6 +39,10 @@ public class Attachment extends TenantPartition implements NKFile, POJO
*
*/
private String objId;
/**
*
*/
private String storagePath;
/**
*
*/
@ -219,4 +223,12 @@ public class Attachment extends TenantPartition implements NKFile, POJO
public void setOverTime(Date overTime) {
this.overTime = overTime;
}
public String getStoragePath() {
return storagePath;
}
public void setStoragePath(String storagePath) {
this.storagePath = storagePath;
}
}

View File

@ -173,5 +173,15 @@ public class AttachmentServiceImpl extends TenantPaginationService<Attachment> i
return attachmentDao.findByPK(attachment);
}
@Override
public Attachment queryByStoragePath(String tenantId, String storagePath)
{
Attachment attachment = new Attachment();
attachment.setTenantId(tenantId);
attachment.setStoragePath(storagePath);
return attachmentDao.queryByStoragePath(attachment);
}
}

View File

@ -86,4 +86,6 @@ public interface AttachmentService extends BasicService<Attachment>
List<Attachment> queryByOverTime(LocalDateTime now);
List<Attachment> queryByBusiness(LocalDate now);
Attachment queryByStoragePath(String tenantId, String storagePath);
}

View File

@ -17,6 +17,8 @@
<result column="length" jdbcType="BIGINT" property="length"/>
<!-- 关联对象-->
<result column="obj_id" jdbcType="VARCHAR" property="objId"/>
<!-- 关联对象-->
<result column="storage_path" jdbcType="VARCHAR" property="storagePath"/>
<!-- 业务编码-->
<result column="business_code" jdbcType="VARCHAR" property="businessCode"/>
<!-- 过期时间-->
@ -41,6 +43,7 @@
,content_type
,length
,obj_id
,storage_path
,business_code
,over_time
,created_by
@ -112,6 +115,9 @@
</if>
<if test="objId != null">
AND obj_id=#{objId,jdbcType=VARCHAR}
</if>
<if test="storagePath != null">
AND storage_path=#{storage_path,jdbcType=VARCHAR}
</if>
<if test="businessCode != null">
AND business_code=#{businessCode,jdbcType=VARCHAR}
@ -156,6 +162,9 @@
<if test="objId != null">
obj_id,
</if>
<if test="storagePath != null">
storage_path,
</if>
<if test="businessCode != null">
business_code,
</if>
@ -197,6 +206,9 @@
<if test="objId != null">
#{objId,jdbcType=VARCHAR},
</if>
<if test="storagePath != null">
#{storagePath,jdbcType=VARCHAR},
</if>
<if test="businessCode != null">
#{businessCode,jdbcType=VARCHAR},
</if>
@ -233,6 +245,7 @@
content_type,
length,
obj_id,
storage_path,
business_code,
over_time,
created_by,
@ -250,6 +263,7 @@
#{item.contentType, jdbcType=VARCHAR},
#{item.length, jdbcType=BIGINT},
#{item.objId, jdbcType=VARCHAR},
#{item.storagePath, jdbcType=VARCHAR},
#{item.businessCode, jdbcType=VARCHAR},
#{item.overTime, jdbcType=TIMESTAMP},
#{item.createdBy, jdbcType=VARCHAR},
@ -278,6 +292,9 @@
<if test="objId != null">
obj_id = #{objId, jdbcType=VARCHAR},
</if>
<if test="storagePath != null">
storage_path = #{storagePath, jdbcType=VARCHAR},
</if>
<if test="businessCode != null">
business_code = #{businessCode, jdbcType=VARCHAR},
</if>
@ -327,6 +344,9 @@
<if test="objId != null">
AND obj_id = #{objId,jdbcType=VARCHAR}
</if>
<if test="storagePath != null">
AND storage_path = #{storagePath,jdbcType=VARCHAR}
</if>
<if test="businessCode != null">
AND business_code = #{businessCode,jdbcType=VARCHAR}
</if>
@ -370,6 +390,9 @@
<if test="objId != null">
AND obj_id=#{objId,jdbcType=VARCHAR}
</if>
<if test="storagePath != null">
AND storage_path=#{storagePath,jdbcType=VARCHAR}
</if>
<if test="businessCode != null">
AND business_code=#{businessCode,jdbcType=VARCHAR}
</if>

View File

@ -39,6 +39,7 @@
length BIGINT comment '文件长度',
business_code varchar(50) DEFAULT NULL,
obj_id varchar(50) DEFAULT NULL COMMENT '关联对象Id',
storage_path varchar(128) DEFAULT NULL COMMENT '存储路径',
over_time DATETIME DEFAULT NULL,
created_by VARCHAR(32) COMMENT '创建人' ,
created_time DATETIME COMMENT '创建时间' ,
@ -183,4 +184,13 @@
)
limit 5000
</select>
<select id="queryByStoragePath" resultType="net.northking.cctp.attachment.db.entity.Attachment" parameterType="net.northking.cctp.attachment.db.entity.Attachment">
SELECT
<include refid="Base_Column_List" />
FROM
<include refid="Table_Name" />
WHERE
storage_path = #{storagePath,jdbcType=VARCHAR}
limit 1
</select>
</mapper>