diff --git a/pom.xml b/pom.xml
index 7cee79e..b641ab6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -210,6 +210,12 @@
4.16.19
+
+
+ com.aliyun
+ mts20140618
+ 5.0.0
+
@@ -269,6 +275,17 @@
true
+
+ sonatype-nexus-staging
+ Sonatype Nexus Staging
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+ true
+
+
+ true
+
+
diff --git a/src/main/java/com/ycwl/basic/controller/extern/CustomDeviceController.java b/src/main/java/com/ycwl/basic/controller/extern/CustomDeviceController.java
new file mode 100644
index 0000000..6afecdc
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/controller/extern/CustomDeviceController.java
@@ -0,0 +1,83 @@
+package com.ycwl.basic.controller.extern;
+
+import com.ycwl.basic.annotation.IgnoreToken;
+import com.ycwl.basic.model.custom.req.AliyunCallbackReq;
+import com.ycwl.basic.model.custom.req.CreateUploadTaskReq;
+import com.ycwl.basic.model.custom.req.UploadCompleteReq;
+import com.ycwl.basic.model.custom.req.UploadFailedReq;
+import com.ycwl.basic.model.custom.resp.CreateUploadTaskResp;
+import com.ycwl.basic.service.custom.CustomUploadTaskService;
+import com.ycwl.basic.utils.ApiResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("/extern/custom-device")
+@IgnoreToken
+public class CustomDeviceController {
+
+ @Autowired
+ private CustomUploadTaskService customUploadTaskService;
+
+ @PostMapping("/upload/create")
+ public ApiResponse createUploadTask(@RequestBody CreateUploadTaskReq req) {
+ try {
+ CreateUploadTaskResp resp = customUploadTaskService.createUploadTask(req);
+ return ApiResponse.success(resp);
+ } catch (Exception e) {
+ log.error("创建上传任务失败", e);
+ return ApiResponse.fail(e.getMessage());
+ }
+ }
+
+ @PostMapping("/upload/complete")
+ public ApiResponse uploadComplete(@RequestBody UploadCompleteReq req) {
+ try {
+ customUploadTaskService.completeUpload(req.getAccessKey(), req.getTaskId());
+ return ApiResponse.success("上传完成,人脸识别任务已提交");
+ } catch (Exception e) {
+ log.error("上传完成处理失败", e);
+ return ApiResponse.fail(e.getMessage());
+ }
+ }
+
+ @PostMapping("/upload/failed")
+ public ApiResponse uploadFailed(@RequestBody UploadFailedReq req) {
+ try {
+ customUploadTaskService.markTaskFailed(req.getAccessKey(), req.getTaskId(), req.getErrorMsg());
+ return ApiResponse.success("任务已标记为失败");
+ } catch (Exception e) {
+ log.error("标记任务失败处理异常", e);
+ return ApiResponse.fail(e.getMessage());
+ }
+ }
+
+ @PostMapping("/aliyun/mps/callback")
+ public ApiResponse aliyunCallback(@RequestBody AliyunCallbackReq req) {
+ try {
+ customUploadTaskService.handleAliyunCallback(req.getJobId(), req.getStatus());
+ return ApiResponse.success("回调处理完成");
+ } catch (Exception e) {
+ log.error("阿里云回调处理失败", e);
+ return ApiResponse.fail(e.getMessage());
+ }
+ }
+
+ @GetMapping("/aliyun/mps/callback")
+ public ApiResponse aliyunCallback(@RequestParam("jobId") String jobId, @RequestParam("status") String status) {
+ try {
+ customUploadTaskService.handleAliyunCallback(jobId, status);
+ return ApiResponse.success("回调处理完成");
+ } catch (Exception e) {
+ log.error("阿里云回调处理失败", e);
+ return ApiResponse.fail(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/ycwl/basic/mapper/CustomUploadTaskMapper.java b/src/main/java/com/ycwl/basic/mapper/CustomUploadTaskMapper.java
new file mode 100644
index 0000000..0abaf8b
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/mapper/CustomUploadTaskMapper.java
@@ -0,0 +1,10 @@
+package com.ycwl.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ycwl.basic.model.custom.entity.CustomUploadTaskEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CustomUploadTaskMapper extends BaseMapper {
+ CustomUploadTaskEntity getByJobId(String jobId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/entity/CustomUploadTaskEntity.java b/src/main/java/com/ycwl/basic/model/custom/entity/CustomUploadTaskEntity.java
new file mode 100644
index 0000000..b03d444
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/entity/CustomUploadTaskEntity.java
@@ -0,0 +1,32 @@
+package com.ycwl.basic.model.custom.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("custom_upload_task")
+public class CustomUploadTaskEntity {
+
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+
+ private Long scenicId;
+
+ private Long deviceId;
+
+ private String savePath;
+
+ private String status;
+
+ private String jobId;
+
+ private String errorMsg;
+
+ private Date createTime;
+
+ private Date updateTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/entity/FaceData.java b/src/main/java/com/ycwl/basic/model/custom/entity/FaceData.java
new file mode 100644
index 0000000..12eba7c
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/entity/FaceData.java
@@ -0,0 +1,145 @@
+package com.ycwl.basic.model.custom.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class FaceData {
+ private String category;
+ private String name;
+ private List occurrences;
+ private Double ratio;
+
+ // Getters and setters
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getOccurrences() {
+ return occurrences;
+ }
+
+ public void setOccurrences(List occurrences) {
+ this.occurrences = occurrences;
+ }
+
+ public Double getRatio() {
+ return ratio;
+ }
+
+ public void setRatio(Double ratio) {
+ this.ratio = ratio;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Occurrence {
+ @JsonProperty("faceUrl")
+ private String faceUrl;
+
+ private Double from;
+ private Position position;
+ private String scene;
+ private Double score;
+ private Double timestamp;
+ private Double to;
+
+ // Getters and setters
+ public String getFaceUrl() {
+ return faceUrl;
+ }
+
+ public void setFaceUrl(String faceUrl) {
+ this.faceUrl = faceUrl;
+ }
+
+ public Double getFrom() {
+ return from;
+ }
+
+ public void setFrom(Double from) {
+ this.from = from;
+ }
+
+ public Position getPosition() {
+ return position;
+ }
+
+ public void setPosition(Position position) {
+ this.position = position;
+ }
+
+ public String getScene() {
+ return scene;
+ }
+
+ public void setScene(String scene) {
+ this.scene = scene;
+ }
+
+ public Double getScore() {
+ return score;
+ }
+
+ public void setScore(Double score) {
+ this.score = score;
+ }
+
+ public Double getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Double timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Double getTo() {
+ return to;
+ }
+
+ public void setTo(Double to) {
+ this.to = to;
+ }
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Position {
+ @JsonProperty("leftTop")
+ private List leftTop;
+
+ @JsonProperty("rightBottom")
+ private List rightBottom;
+
+ // Getters and setters
+ public List getLeftTop() {
+ return leftTop;
+ }
+
+ public void setLeftTop(List leftTop) {
+ this.leftTop = leftTop;
+ }
+
+ public List getRightBottom() {
+ return rightBottom;
+ }
+
+ public void setRightBottom(List rightBottom) {
+ this.rightBottom = rightBottom;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/req/AliyunCallbackReq.java b/src/main/java/com/ycwl/basic/model/custom/req/AliyunCallbackReq.java
new file mode 100644
index 0000000..97830b3
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/req/AliyunCallbackReq.java
@@ -0,0 +1,13 @@
+package com.ycwl.basic.model.custom.req;
+
+import lombok.Data;
+
+@Data
+public class AliyunCallbackReq {
+
+ private String jobId;
+
+ private String pipelineId;
+
+ private String status;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/req/CreateUploadTaskReq.java b/src/main/java/com/ycwl/basic/model/custom/req/CreateUploadTaskReq.java
new file mode 100644
index 0000000..2deb79b
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/req/CreateUploadTaskReq.java
@@ -0,0 +1,13 @@
+package com.ycwl.basic.model.custom.req;
+
+import lombok.Data;
+
+@Data
+public class CreateUploadTaskReq {
+
+ private String accessKey;
+
+ private String fileName;
+
+ private String type;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/req/UploadCompleteReq.java b/src/main/java/com/ycwl/basic/model/custom/req/UploadCompleteReq.java
new file mode 100644
index 0000000..9adbbe9
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/req/UploadCompleteReq.java
@@ -0,0 +1,11 @@
+package com.ycwl.basic.model.custom.req;
+
+import lombok.Data;
+
+@Data
+public class UploadCompleteReq {
+
+ private String accessKey;
+
+ private Long taskId;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/req/UploadFailedReq.java b/src/main/java/com/ycwl/basic/model/custom/req/UploadFailedReq.java
new file mode 100644
index 0000000..737c6ac
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/req/UploadFailedReq.java
@@ -0,0 +1,13 @@
+package com.ycwl.basic.model.custom.req;
+
+import lombok.Data;
+
+@Data
+public class UploadFailedReq {
+
+ private String accessKey;
+
+ private Long taskId;
+
+ private String errorMsg;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/model/custom/resp/CreateUploadTaskResp.java b/src/main/java/com/ycwl/basic/model/custom/resp/CreateUploadTaskResp.java
new file mode 100644
index 0000000..bd7547a
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/model/custom/resp/CreateUploadTaskResp.java
@@ -0,0 +1,13 @@
+package com.ycwl.basic.model.custom.resp;
+
+import lombok.Data;
+
+@Data
+public class CreateUploadTaskResp {
+
+ private Long taskId;
+
+ private String uploadUrl;
+
+ private String savePath;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java
new file mode 100644
index 0000000..14251de
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java
@@ -0,0 +1,339 @@
+package com.ycwl.basic.service.custom;
+
+import com.aliyun.credentials.Client;
+import com.aliyun.credentials.models.Config;
+import com.aliyun.mts20140618.models.QuerySmarttagJobRequest;
+import com.aliyun.mts20140618.models.QuerySmarttagJobResponse;
+import com.aliyun.mts20140618.models.QuerySmarttagJobResponseBody;
+import com.aliyun.mts20140618.models.SubmitSmarttagJobRequest;
+import com.aliyun.mts20140618.models.SubmitSmarttagJobResponse;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
+import com.ycwl.basic.facebody.entity.AddFaceResp;
+import com.ycwl.basic.mapper.DeviceMapper;
+import com.ycwl.basic.mapper.CustomUploadTaskMapper;
+import com.ycwl.basic.mapper.FaceSampleMapper;
+import com.ycwl.basic.mapper.ScenicMapper;
+import com.ycwl.basic.mapper.SourceMapper;
+import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
+import com.ycwl.basic.model.custom.entity.CustomUploadTaskEntity;
+import com.ycwl.basic.model.custom.entity.FaceData;
+import com.ycwl.basic.model.custom.req.CreateUploadTaskReq;
+import com.ycwl.basic.model.custom.resp.CreateUploadTaskResp;
+import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
+import com.ycwl.basic.model.pc.source.entity.SourceEntity;
+import com.ycwl.basic.service.pc.ScenicService;
+import com.ycwl.basic.storage.StorageFactory;
+import com.ycwl.basic.storage.adapters.IStorageAdapter;
+import com.ycwl.basic.utils.JacksonUtil;
+import com.ycwl.basic.utils.SnowFlakeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import static com.ycwl.basic.constant.StorageConstant.VIID_FACE;
+
+@Slf4j
+@Service
+public class CustomUploadTaskService {
+
+ @Autowired
+ private CustomUploadTaskMapper customUploadTaskMapper;
+
+ @Autowired
+ private DeviceMapper deviceMapper;
+
+ @Autowired
+ private ScenicService scenicService;
+ @Autowired
+ private FaceSampleMapper faceSampleMapper;
+ @Autowired
+ private SourceMapper sourceMapper;
+
+ private static final ThreadPoolExecutor executor;
+ static {
+ executor = new ThreadPoolExecutor(16, 32, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024));
+ }
+
+ public CreateUploadTaskResp createUploadTask(CreateUploadTaskReq req) {
+ if (StringUtils.isBlank(req.getAccessKey())) {
+ throw new RuntimeException("设备访问密钥不能为空");
+ }
+
+ if (StringUtils.isBlank(req.getFileName())) {
+ throw new RuntimeException("文件名不能为空");
+ }
+
+ if (StringUtils.isBlank(req.getType())) {
+ throw new RuntimeException("上传类型不能为空");
+ }
+
+ // 验证设备访问权限
+ DeviceEntity device = validateDeviceAccess(req.getAccessKey(), req.getType());
+
+ long taskId = SnowFlakeUtil.getLongId();
+ String savePath = generateSavePath(device.getScenicId(), req.getFileName());
+
+ CustomUploadTaskEntity task = new CustomUploadTaskEntity();
+ task.setId(taskId);
+ task.setScenicId(device.getScenicId());
+ task.setDeviceId(device.getId());
+ task.setSavePath(savePath);
+ task.setStatus("PENDING");
+ task.setCreateTime(new Date());
+ task.setUpdateTime(new Date());
+
+ customUploadTaskMapper.insert(task);
+
+ String uploadUrl = generateUploadUrl(savePath);
+
+ CreateUploadTaskResp resp = new CreateUploadTaskResp();
+ resp.setTaskId(taskId);
+ resp.setUploadUrl(uploadUrl);
+ resp.setSavePath(savePath);
+
+ return resp;
+ }
+
+ public void completeUpload(String accessKey, Long taskId) {
+ // 验证设备访问权限
+ DeviceEntity device = validateDeviceAccess(accessKey, null);
+
+ CustomUploadTaskEntity task = customUploadTaskMapper.selectById(taskId);
+ if (task == null) {
+ throw new RuntimeException("任务不存在");
+ }
+
+ // 验证任务属于该设备的景区
+ if (!device.getScenicId().equals(task.getScenicId())) {
+ throw new RuntimeException("无权限操作该任务");
+ }
+
+ task.setStatus("UPLOADING");
+ task.setUpdateTime(new Date());
+ customUploadTaskMapper.updateById(task);
+
+ try {
+ String jobId = submitSmarttagJob(task.getSavePath());
+
+ task.setJobId(jobId);
+ task.setStatus("COMPLETED");
+ task.setUpdateTime(new Date());
+ customUploadTaskMapper.updateById(task);
+
+ log.info("人脸识别任务提交成功,taskId: {}, jobId: {}", taskId, jobId);
+ } catch (Exception e) {
+ task.setStatus("FAILED");
+ task.setErrorMsg("人脸识别任务提交失败: " + e.getMessage());
+ task.setUpdateTime(new Date());
+ customUploadTaskMapper.updateById(task);
+
+ log.error("人脸识别任务提交失败,taskId: {}", taskId, e);
+ throw new RuntimeException("人脸识别任务提交失败");
+ }
+ }
+
+ public void markTaskFailed(String accessKey, Long taskId, String errorMsg) {
+ // 验证设备访问权限
+ DeviceEntity device = validateDeviceAccess(accessKey, null);
+
+ CustomUploadTaskEntity task = customUploadTaskMapper.selectById(taskId);
+ if (task == null) {
+ throw new RuntimeException("任务不存在");
+ }
+
+ // 验证任务属于该设备的景区
+ if (!device.getScenicId().equals(task.getScenicId())) {
+ throw new RuntimeException("无权限操作该任务");
+ }
+
+ task.setStatus("FAILED");
+ task.setErrorMsg(errorMsg);
+ task.setUpdateTime(new Date());
+ customUploadTaskMapper.updateById(task);
+
+ log.info("任务标记为失败,taskId: {}, errorMsg: {}", taskId, errorMsg);
+ }
+
+ public void handleAliyunCallback(String jobId, String status) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(CustomUploadTaskEntity::getJobId, jobId);
+
+ CustomUploadTaskEntity task = customUploadTaskMapper.selectOne(wrapper);
+ if (task == null) {
+ log.warn("未找到对应的上传任务,jobId: {}", jobId);
+ return;
+ }
+
+ log.info("收到阿里云回调,jobId: {}, status: {}, taskId: {}", jobId, status, task.getId());
+
+ handleAliyunMpsJobComplete(jobId);
+ }
+
+ private String generateSavePath(Long scenicId, String fileName) {
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ String extension = "";
+ if (fileName.contains(".")) {
+ extension = fileName.substring(fileName.lastIndexOf("."));
+ }
+ return String.format("custom-device/%d/%s%s", scenicId, timestamp, extension);
+ }
+
+ private String generateUploadUrl(String savePath) {
+ try {
+ IStorageAdapter adapter = StorageFactory.use();
+
+ Date expireDate = new Date(System.currentTimeMillis() + 3600 * 1000);
+ return adapter.getUrlForUpload(expireDate, null, savePath);
+ } catch (Exception e) {
+ log.error("生成上传URL失败,savePath: {}", savePath, e);
+ throw new RuntimeException("生成上传URL失败");
+ }
+ }
+
+ private String submitSmarttagJob(String inputPath) {
+ try {
+ log.info("提交人脸识别任务,inputPath: {}", inputPath);
+ String jobId = createAliyunMtsTask(inputPath);
+ executor.execute(() -> loopQueryJobStatus(jobId));
+ return jobId;
+ } catch (Exception e) {
+ log.error("提交人脸识别任务失败,inputPath: {}", inputPath, e);
+ throw new RuntimeException("提交人脸识别任务失败: " + e.getMessage());
+ }
+ }
+
+ private DeviceEntity validateDeviceAccess(String accessKey, String type) {
+ if (StringUtils.isBlank(accessKey)) {
+ throw new RuntimeException("设备访问密钥不能为空");
+ }
+
+ DeviceEntity device = deviceMapper.getByDeviceNo(accessKey);
+ if (device == null || device.getStatus() != 1) {
+ throw new RuntimeException("无效的设备访问密钥或设备已被禁用");
+ }
+
+ return device;
+ }
+
+ private String createAliyunMtsTask(String inputPath) {
+ try {
+ // 创建任务
+ com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+ .setAccessKeyId("LTAI5tCWgYJz9kZvvh2KVhkH")
+ .setAccessKeySecret("RObb3EZ1YsmR63ul1gh7tnIfT1foOc");
+ config.endpoint = "mts.cn-shanghai.aliyuncs.com";
+ com.aliyun.mts20140618.Client client = new com.aliyun.mts20140618.Client(config);
+ SubmitSmarttagJobRequest request = new SubmitSmarttagJobRequest();
+ request.setPipelineId("d791f854652e466bad66301e5c97b7bb");
+ request.setTemplateId("a004e08402bd496a8a9adbb2ba920973");
+ request.setTitle(String.valueOf(System.currentTimeMillis()));
+ request.setNotifyUrl("https://zhentuai.com/extern/custom-device/aliyun/mps/callback");
+ request.setInput(String.format("oss://frametour-assets/user-assets/%s", inputPath));
+ com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
+ SubmitSmarttagJobResponse submitSmarttagJobResponse = client.submitSmarttagJobWithOptions(request, runtime);
+ return submitSmarttagJobResponse.getBody().jobId;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void loopQueryJobStatus(String jobId) {
+ try {
+ Thread.sleep(10000L);
+ } catch (InterruptedException e) {
+ return;
+ }
+ handleAliyunMpsJobComplete(jobId);
+ }
+
+
+ public void handleAliyunMpsJobComplete(String jobId) {
+ CustomUploadTaskEntity task = customUploadTaskMapper.getByJobId(jobId);
+ try {
+ // 查询任务
+ com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+ .setAccessKeyId("LTAI5tCWgYJz9kZvvh2KVhkH")
+ .setAccessKeySecret("RObb3EZ1YsmR63ul1gh7tnIfT1foOc");
+ config.endpoint = "mts.cn-shanghai.aliyuncs.com";
+ com.aliyun.mts20140618.Client client = new com.aliyun.mts20140618.Client(config);
+ QuerySmarttagJobRequest request = new QuerySmarttagJobRequest();
+ request.setJobId(jobId);
+ QuerySmarttagJobResponse response = client.querySmarttagJob(request);
+ if (Strings.CI.equals(response.getBody().jobStatus, "Fail")) {
+ log.error("智能标签任务失败");
+ return;
+ }
+ if (!Strings.CI.equals(response.getBody().jobStatus, "Success")) {
+ log.info("jobId:{} 智能标签任务等待查询!", jobId);
+ executor.execute(() -> loopQueryJobStatus(jobId));
+ return;
+ }
+ List result = response.getBody().results.getResult();
+ QuerySmarttagJobResponseBody.QuerySmarttagJobResponseBodyResultsResult first = result.stream()
+ .filter(r -> "VideoLabel".equals(r.getType()))
+ .findFirst()
+ .orElse(null);
+ if (first == null) {
+ return;
+ }
+ List persons = JacksonUtil.getArray(first.getData(), "persons", FaceData.class);
+ if (persons == null || persons.isEmpty()) {
+ return;
+ }
+ IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(task.getScenicId());
+ IStorageAdapter storageAdapter = StorageFactory.use();
+ persons.stream()
+ .filter(p -> p.getRatio() > 0.1)
+ .map(FaceData::getOccurrences)
+ .filter(Objects::nonNull)
+ .forEach(occurrences -> {
+ if (occurrences.isEmpty()) {
+ return;
+ }
+ Long newFaceSampleId = SnowFlakeUtil.getLongId();
+ String faceUrl = occurrences.getFirst().getFaceUrl();
+ FaceSampleEntity faceSample = new FaceSampleEntity();
+ faceSample.setId(newFaceSampleId);
+ faceSample.setScenicId(task.getScenicId());
+ faceSample.setDeviceId(task.getDeviceId());
+ faceSample.setStatus(1);
+ faceSample.setCreateAt(task.getCreateTime());
+ faceSample.setFaceUrl(faceUrl);
+ faceSampleMapper.add(faceSample);
+ faceBodyAdapter.assureFaceDb(task.getScenicId().toString());
+ AddFaceResp addFaceResp = faceBodyAdapter.addFace(task.getScenicId().toString(), newFaceSampleId.toString(), faceUrl, newFaceSampleId.toString());
+ if (addFaceResp != null) {
+ faceSample.setScore(addFaceResp.getScore());
+ faceSampleMapper.updateScore(faceSample.getId(), addFaceResp.getScore());
+ }
+ SourceEntity source = new SourceEntity();
+ source.setId(SnowFlakeUtil.getLongId());
+ source.setDeviceId(task.getDeviceId());
+ source.setScenicId(task.getScenicId());
+ source.setFaceSampleId(newFaceSampleId);
+ source.setCreateTime(task.getCreateTime());
+ source.setType(1);
+ source.setUrl(faceUrl);
+ source.setVideoUrl(storageAdapter.getUrl(task.getSavePath()));
+ sourceMapper.add(source);
+ });
+ System.out.println(JacksonUtil.toJson(persons));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/mapper/CustomUploadTaskMapper.xml b/src/main/resources/mapper/CustomUploadTaskMapper.xml
new file mode 100644
index 0000000..7c34521
--- /dev/null
+++ b/src/main/resources/mapper/CustomUploadTaskMapper.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/com/ycwl/basic/service/custom/CustomUploadTaskServiceTest.java b/src/test/java/com/ycwl/basic/service/custom/CustomUploadTaskServiceTest.java
new file mode 100644
index 0000000..dae2229
--- /dev/null
+++ b/src/test/java/com/ycwl/basic/service/custom/CustomUploadTaskServiceTest.java
@@ -0,0 +1,13 @@
+package com.ycwl.basic.service.custom;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CustomUploadTaskServiceTest {
+
+ @Test
+ void handleAliyunMpsJobComplete() {
+ new CustomUploadTaskService().handleAliyunMpsJobComplete("2b9142e3efa73d3ea8edd707e0a70db3");
+ }
+}
\ No newline at end of file