feat(backend): expose digital employee sync records

This commit is contained in:
baiyanyun
2026-06-05 18:40:44 +08:00
parent 17475bf0d7
commit dc14418963
9 changed files with 181 additions and 1 deletions

View File

@@ -1,8 +1,17 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
public interface DigitalEmployeeSyncApplicationService {
DigitalEmployeeSyncResponseDto syncOutbox(DigitalEmployeeSyncRequestDto request);
DigitalEmployeeSyncRecordPageDto queryRecords(
String clientKey,
String entityType,
String entityId,
Long pageNo,
Long pageSize
);
}

View File

@@ -0,0 +1,29 @@
package com.xspaceagi.agent.core.adapter.dto.digital;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
public class DigitalEmployeeSyncRecordDto {
private Long id;
@JsonProperty("client_key")
private String clientKey;
@JsonProperty("device_id")
private String deviceId;
@JsonProperty("outbox_id")
private String outboxId;
@JsonProperty("entity_type")
private String entityType;
@JsonProperty("entity_id")
private String entityId;
private String operation;
private String payload;
@JsonProperty("synced_at")
private Date syncedAt;
private Date modified;
private Date created;
}

View File

@@ -0,0 +1,18 @@
package com.xspaceagi.agent.core.adapter.dto.digital;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class DigitalEmployeeSyncRecordPageDto {
private Long total;
@JsonProperty("page_no")
private Long pageNo;
@JsonProperty("page_size")
private Long pageSize;
private List<DigitalEmployeeSyncRecordDto> records;
}

View File

@@ -1,7 +1,16 @@
package com.xspaceagi.agent.core.adapter.repository;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
public interface DigitalEmployeeSyncRecordRepository extends IService<DigitalEmployeeSyncRecord> {
IPage<DigitalEmployeeSyncRecord> queryRecords(
Long tenantId,
String clientKey,
String entityType,
String entityId,
long pageNo,
long pageSize
);
}

View File

@@ -2,11 +2,14 @@ package com.xspaceagi.agent.core.application.service;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncAckDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncItemDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeSyncRecordRepository;
@@ -23,6 +26,10 @@ import java.util.List;
@Service
public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmployeeSyncApplicationService {
private static final long DEFAULT_PAGE_NO = 1L;
private static final long DEFAULT_PAGE_SIZE = 20L;
private static final long MAX_PAGE_SIZE = 100L;
@Resource
private DigitalEmployeeSyncRecordRepository digitalEmployeeSyncRecordRepository;
@@ -81,6 +88,61 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
return DigitalEmployeeSyncResponseDto.builder().acked(acked).build();
}
@Override
public DigitalEmployeeSyncRecordPageDto queryRecords(
String clientKey,
String entityType,
String entityId,
Long pageNo,
Long pageSize
) {
long currentPage = pageNo == null || pageNo < 1 ? DEFAULT_PAGE_NO : pageNo;
long currentPageSize = pageSize == null || pageSize < 1
? DEFAULT_PAGE_SIZE
: Math.min(pageSize, MAX_PAGE_SIZE);
Long tenantId = RequestContext.get() == null ? null : RequestContext.get().getTenantId();
if (tenantId == null) {
return DigitalEmployeeSyncRecordPageDto.builder()
.total(0L)
.pageNo(currentPage)
.pageSize(currentPageSize)
.records(List.of())
.build();
}
IPage<DigitalEmployeeSyncRecord> page = digitalEmployeeSyncRecordRepository.queryRecords(
tenantId,
clientKey,
entityType,
entityId,
currentPage,
currentPageSize
);
return DigitalEmployeeSyncRecordPageDto.builder()
.total(page.getTotal())
.pageNo(page.getCurrent())
.pageSize(page.getSize())
.records(page.getRecords().stream().map(this::toRecordDto).toList())
.build();
}
private DigitalEmployeeSyncRecordDto toRecordDto(DigitalEmployeeSyncRecord record) {
return DigitalEmployeeSyncRecordDto.builder()
.id(record.getId())
.clientKey(record.getClientKey())
.deviceId(record.getDeviceId())
.outboxId(record.getOutboxId())
.entityType(record.getEntityType())
.entityId(record.getEntityId())
.operation(record.getOperation())
.payload(record.getPayload())
.syncedAt(record.getSyncedAt())
.modified(record.getModified())
.created(record.getCreated())
.build();
}
private String toJson(Object payload) {
if (payload == null) {
return "{}";

View File

@@ -1,13 +1,36 @@
package com.xspaceagi.agent.core.infra.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeSyncRecordRepository;
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeSyncRecordMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Service
public class DigitalEmployeeSyncRecordRepositoryImpl
extends ServiceImpl<DigitalEmployeeSyncRecordMapper, DigitalEmployeeSyncRecord>
implements DigitalEmployeeSyncRecordRepository {
@Override
public IPage<DigitalEmployeeSyncRecord> queryRecords(
Long tenantId,
String clientKey,
String entityType,
String entityId,
long pageNo,
long pageSize
) {
LambdaQueryWrapper<DigitalEmployeeSyncRecord> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeSyncRecord>()
.eq(tenantId != null, DigitalEmployeeSyncRecord::getTenantId, tenantId)
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeSyncRecord::getClientKey, clientKey)
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeSyncRecord::getEntityType, entityType)
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeSyncRecord::getEntityId, entityId)
.orderByDesc(DigitalEmployeeSyncRecord::getSyncedAt)
.orderByDesc(DigitalEmployeeSyncRecord::getId);
return page(new Page<>(pageNo, pageSize), queryWrapper);
}
}

View File

@@ -1,6 +1,7 @@
package com.xspaceagi.agent.web.ui.controller;
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
import com.xspaceagi.system.spec.dto.ReqResult;
@@ -8,6 +9,7 @@ import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -21,4 +23,21 @@ public class DigitalEmployeeSyncController {
public ReqResult<DigitalEmployeeSyncResponseDto> syncOutbox(@RequestBody DigitalEmployeeSyncRequestDto request) {
return ReqResult.success(digitalEmployeeSyncApplicationService.syncOutbox(request));
}
@RequestMapping(path = "/records", method = RequestMethod.GET)
public ReqResult<DigitalEmployeeSyncRecordPageDto> queryRecords(
@RequestParam(name = "client_key", required = false) String clientKey,
@RequestParam(name = "entity_type", required = false) String entityType,
@RequestParam(name = "entity_id", required = false) String entityId,
@RequestParam(name = "page_no", required = false) Long pageNo,
@RequestParam(name = "page_size", required = false) Long pageSize
) {
return ReqResult.success(digitalEmployeeSyncApplicationService.queryRecords(
clientKey,
entityType,
entityId,
pageNo,
pageSize
));
}
}

View File

@@ -16,6 +16,7 @@ CREATE TABLE `digital_employee_sync_record`
PRIMARY KEY (`id`),
UNIQUE KEY `uk_client_outbox` (`client_key`, `outbox_id`),
KEY `idx_client_entity` (`client_key`, `entity_type`, `entity_id`),
KEY `idx_tenant_client_entity_time` (`_tenant_id`, `client_key`, `entity_type`, `entity_id`, `synced_at`),
KEY `idx_tenant_user_time` (`_tenant_id`, `user_id`, `synced_at`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4

View File

@@ -470,10 +470,20 @@ qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-a
qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/.../DigitalEmployeeSyncRecord.java
```
后端观察接口:
```text
GET /api/digital-employee/sync/records
```
- 支持 `client_key``entity_type``entity_id``page_no``page_size` 查询参数。
- 按当前登录上下文租户隔离,默认每页 20 条,最大每页 100 条。
- 当前返回通用同步记录和原始 payload用于管理端页面或联调工具先查看同步结果后续再拆分为更丰富的 Plan / Task / Run / Event 查询模型。
下一步:
- 在有 Maven 的环境验证 qiming-backend 编译与接口启动。
- 数字员工页面继续细化同步状态交互,例如手动同步按钮和失败原因展开。
- 数字员工页面继续细化同步状态交互,例如失败原因展开。
## 管理端联动原则