feat: filter digital sync links by device

This commit is contained in:
baiyanyun
2026-06-07 11:24:01 +08:00
parent 029ca6e7f6
commit bf25bc5a47
11 changed files with 27 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ public interface DigitalEmployeeSyncApplicationService {
DigitalEmployeeSyncRecordPageDto queryRecords( DigitalEmployeeSyncRecordPageDto queryRecords(
String clientKey, String clientKey,
String deviceId,
String entityType, String entityType,
String entityId, String entityId,
String outboxId, String outboxId,

View File

@@ -8,6 +8,7 @@ public interface DigitalEmployeeSyncRecordRepository extends IService<DigitalEmp
IPage<DigitalEmployeeSyncRecord> queryRecords( IPage<DigitalEmployeeSyncRecord> queryRecords(
Long tenantId, Long tenantId,
String clientKey, String clientKey,
String deviceId,
String entityType, String entityType,
String entityId, String entityId,
String outboxId, String outboxId,

View File

@@ -91,6 +91,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
@Override @Override
public DigitalEmployeeSyncRecordPageDto queryRecords( public DigitalEmployeeSyncRecordPageDto queryRecords(
String clientKey, String clientKey,
String deviceId,
String entityType, String entityType,
String entityId, String entityId,
String outboxId, String outboxId,
@@ -114,6 +115,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
IPage<DigitalEmployeeSyncRecord> page = digitalEmployeeSyncRecordRepository.queryRecords( IPage<DigitalEmployeeSyncRecord> page = digitalEmployeeSyncRecordRepository.queryRecords(
tenantId, tenantId,
clientKey, clientKey,
deviceId,
entityType, entityType,
entityId, entityId,
outboxId, outboxId,

View File

@@ -19,6 +19,7 @@ public class DigitalEmployeeSyncRecordRepositoryImpl
public IPage<DigitalEmployeeSyncRecord> queryRecords( public IPage<DigitalEmployeeSyncRecord> queryRecords(
Long tenantId, Long tenantId,
String clientKey, String clientKey,
String deviceId,
String entityType, String entityType,
String entityId, String entityId,
String outboxId, String outboxId,
@@ -28,6 +29,7 @@ public class DigitalEmployeeSyncRecordRepositoryImpl
LambdaQueryWrapper<DigitalEmployeeSyncRecord> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeSyncRecord>() LambdaQueryWrapper<DigitalEmployeeSyncRecord> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeSyncRecord>()
.eq(tenantId != null, DigitalEmployeeSyncRecord::getTenantId, tenantId) .eq(tenantId != null, DigitalEmployeeSyncRecord::getTenantId, tenantId)
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeSyncRecord::getClientKey, clientKey) .eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeSyncRecord::getClientKey, clientKey)
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeSyncRecord::getDeviceId, deviceId)
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeSyncRecord::getEntityType, entityType) .eq(StringUtils.isNotBlank(entityType), DigitalEmployeeSyncRecord::getEntityType, entityType)
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeSyncRecord::getEntityId, entityId) .eq(StringUtils.isNotBlank(entityId), DigitalEmployeeSyncRecord::getEntityId, entityId)
.eq(StringUtils.isNotBlank(outboxId), DigitalEmployeeSyncRecord::getOutboxId, outboxId) .eq(StringUtils.isNotBlank(outboxId), DigitalEmployeeSyncRecord::getOutboxId, outboxId)

View File

@@ -31,6 +31,7 @@ public class DigitalEmployeeSyncController {
@RequestMapping(path = "/records", method = RequestMethod.GET) @RequestMapping(path = "/records", method = RequestMethod.GET)
public ReqResult<DigitalEmployeeSyncRecordPageDto> queryRecords( public ReqResult<DigitalEmployeeSyncRecordPageDto> queryRecords(
@RequestParam(name = "client_key", required = false) String clientKey, @RequestParam(name = "client_key", required = false) String clientKey,
@RequestParam(name = "device_id", required = false) String deviceId,
@RequestParam(name = "entity_type", required = false) String entityType, @RequestParam(name = "entity_type", required = false) String entityType,
@RequestParam(name = "entity_id", required = false) String entityId, @RequestParam(name = "entity_id", required = false) String entityId,
@RequestParam(name = "outbox_id", required = false) String outboxId, @RequestParam(name = "outbox_id", required = false) String outboxId,
@@ -39,6 +40,7 @@ public class DigitalEmployeeSyncController {
) { ) {
return ReqResult.success(digitalEmployeeSyncApplicationService.queryRecords( return ReqResult.success(digitalEmployeeSyncApplicationService.queryRecords(
clientKey, clientKey,
deviceId,
entityType, entityType,
entityId, entityId,
outboxId, outboxId,

View File

@@ -217,6 +217,7 @@ const DigitalEmployeeSync: React.FC = () => {
const params = new URLSearchParams(location.search || ''); const params = new URLSearchParams(location.search || '');
const values = { const values = {
client_key: readSearchValue(params, 'client_key'), client_key: readSearchValue(params, 'client_key'),
device_id: readSearchValue(params, 'device_id'),
entity_type: readSearchValue(params, 'entity_type'), entity_type: readSearchValue(params, 'entity_type'),
entity_id: readSearchValue(params, 'entity_id'), entity_id: readSearchValue(params, 'entity_id'),
outbox_id: readSearchValue(params, 'outbox_id'), outbox_id: readSearchValue(params, 'outbox_id'),
@@ -228,6 +229,7 @@ const DigitalEmployeeSync: React.FC = () => {
formRef.current?.resetFields(); formRef.current?.resetFields();
formRef.current?.setFieldsValue({ formRef.current?.setFieldsValue({
client_key: undefined, client_key: undefined,
device_id: undefined,
entity_type: undefined, entity_type: undefined,
entity_id: undefined, entity_id: undefined,
outbox_id: undefined, outbox_id: undefined,
@@ -270,7 +272,10 @@ const DigitalEmployeeSync: React.FC = () => {
dataIndex: 'device_id', dataIndex: 'device_id',
width: 150, width: 150,
ellipsis: true, ellipsis: true,
hideInSearch: true, fieldProps: {
placeholder: '输入设备 ID',
allowClear: true,
},
renderText: (value) => value || '--', renderText: (value) => value || '--',
}, },
{ {
@@ -360,6 +365,7 @@ const DigitalEmployeeSync: React.FC = () => {
pageSize?: number; pageSize?: number;
current?: number; current?: number;
client_key?: string; client_key?: string;
device_id?: string;
entity_type?: string; entity_type?: string;
entity_id?: string; entity_id?: string;
outbox_id?: string; outbox_id?: string;
@@ -368,6 +374,7 @@ const DigitalEmployeeSync: React.FC = () => {
pageNo: params.current || 1, pageNo: params.current || 1,
pageSize: params.pageSize || 15, pageSize: params.pageSize || 15,
clientKey: params.client_key, clientKey: params.client_key,
deviceId: params.device_id,
entityType: params.entity_type, entityType: params.entity_type,
entityId: params.entity_id, entityId: params.entity_id,
outboxId: params.outbox_id, outboxId: params.outbox_id,
@@ -431,6 +438,10 @@ const DigitalEmployeeSync: React.FC = () => {
<Typography.Text strong> Key</Typography.Text> <Typography.Text strong> Key</Typography.Text>
{maskClientKey(payloadRecord.client_key)} {maskClientKey(payloadRecord.client_key)}
</Typography.Paragraph> </Typography.Paragraph>
<Typography.Paragraph>
<Typography.Text strong> ID</Typography.Text>
{payloadRecord.device_id || '--'}
</Typography.Paragraph>
<Typography.Paragraph> <Typography.Paragraph>
<Typography.Text strong></Typography.Text> <Typography.Text strong></Typography.Text>
{payloadRecord.entity_type} / {payloadRecord.entity_id} {payloadRecord.entity_type} / {payloadRecord.entity_id}

View File

@@ -274,6 +274,7 @@ export async function apiDigitalEmployeeSyncRecordList(
method: 'GET', method: 'GET',
params: { params: {
client_key: data.clientKey, client_key: data.clientKey,
device_id: data.deviceId,
entity_type: data.entityType, entity_type: data.entityType,
entity_id: data.entityId, entity_id: data.entityId,
outbox_id: data.outboxId, outbox_id: data.outboxId,

View File

@@ -542,6 +542,7 @@ export interface DigitalEmployeeSyncRecordInfo {
export interface DigitalEmployeeSyncRecordListParams export interface DigitalEmployeeSyncRecordListParams
extends SystemPaginationParams { extends SystemPaginationParams {
clientKey?: string; clientKey?: string;
deviceId?: string;
entityType?: string; entityType?: string;
entityId?: string; entityId?: string;
outboxId?: string; outboxId?: string;

View File

@@ -147,7 +147,7 @@ describe("digital employee sync service", () => {
id: "plan:upsert:plan-2", id: "plan:upsert:plan-2",
dueForRetry: false, dueForRetry: false,
managementRecordUrl: managementRecordUrl:
"https://manage.example.com/system/content/content-digital-employee-sync?entity_type=plan&entity_id=plan-2&outbox_id=plan%3Aupsert%3Aplan-2", "https://manage.example.com/system/content/content-digital-employee-sync?device_id=device-001&entity_type=plan&entity_id=plan-2&outbox_id=plan%3Aupsert%3Aplan-2",
attemptHistory: [ attemptHistory: [
expect.objectContaining({ expect.objectContaining({
attemptNo: 1, attemptNo: 1,

View File

@@ -636,6 +636,7 @@ function buildManagementRecordUrl(
try { try {
const endpoint = new URL(config.endpoint); const endpoint = new URL(config.endpoint);
const url = new URL(MANAGEMENT_SYNC_RECORD_PATH, endpoint.origin); const url = new URL(MANAGEMENT_SYNC_RECORD_PATH, endpoint.origin);
url.searchParams.set("device_id", getDeviceId());
url.searchParams.set("entity_type", row.entity_type); url.searchParams.set("entity_type", row.entity_type);
url.searchParams.set("entity_id", row.entity_id); url.searchParams.set("entity_id", row.entity_id);
url.searchParams.set("outbox_id", row.id); url.searchParams.set("outbox_id", row.id);

View File

@@ -448,7 +448,7 @@ crates/agent-electron-client/src/preload/webviewPerfBridge.ts
- 同步状态通过 bridge 返回最近失败 outbox 明细首页可直接看到实体类型、outbox ID、操作类型、重试次数、失败时间和下次自动重试时间。 - 同步状态通过 bridge 返回最近失败 outbox 明细首页可直接看到实体类型、outbox ID、操作类型、重试次数、失败时间和下次自动重试时间。
- 本地 SQLite 额外记录 `digital_sync_attempts`,每次 outbox 推送成功、失败或部分 ack 缺失都会留下一次尝试历史,默认保留最近 30 天。 - 本地 SQLite 额外记录 `digital_sync_attempts`,每次 outbox 推送成功、失败或部分 ack 缺失都会留下一次尝试历史,默认保留最近 30 天。
- 点击失败 outbox 可打开详情弹窗,查看完整错误、实体 ID、退避状态、最近重试历史并可复制或导出诊断 JSON也可触发手动 `flushSync` 立即重试。若已配置管理端同步地址,详情弹窗可直接打开对应的管理端同步记录深链。 - 点击失败 outbox 可打开详情弹窗,查看完整错误、实体 ID、退避状态、最近重试历史并可复制或导出诊断 JSON也可触发手动 `flushSync` 立即重试。若已配置管理端同步地址,详情弹窗可直接打开对应的管理端同步记录深链。
- qimingclaw 自动生成的管理端深链只携带 `entity_type``entity_id``outbox_id`,避免在 URL 中暴露客户端 Key管理端列表仍保留客户端 Key 手动筛选能力。 - qimingclaw 自动生成的管理端深链只携带 `device_id``entity_type``entity_id``outbox_id`,避免在 URL 中暴露客户端 Key,同时仍能区分同租户下的不同客户端;管理端列表仍保留客户端 Key 手动筛选能力。
新增 IPC / Bridge 新增 IPC / Bridge
@@ -482,10 +482,10 @@ qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-a
GET /api/digital-employee/sync/records GET /api/digital-employee/sync/records
``` ```
- 支持 `client_key``entity_type``entity_id``outbox_id``page_no``page_size` 查询参数。 - 支持 `client_key``device_id``entity_type``entity_id``outbox_id``page_no``page_size` 查询参数。
- 按当前登录上下文租户隔离,默认每页 20 条,最大每页 100 条。 - 按当前登录上下文租户隔离,默认每页 20 条,最大每页 100 条。
- 当前返回通用同步记录和原始 payload用于管理端页面或联调工具先查看同步结果后续再拆分为更丰富的 Plan / Task / Run / Event 查询模型。 - 当前返回通用同步记录和原始 payload用于管理端页面或联调工具先查看同步结果后续再拆分为更丰富的 Plan / Task / Run / Event 查询模型。
- 管理端已增加“数字员工同步记录”菜单、权限资源和列表页,可按客户端 Key、实体类型、实体 ID、Outbox ID 查询并查看 payloadURL 查询参数会预填筛选条件,并在命中 Outbox ID 时自动打开 payload 抽屉。 - 管理端已增加“数字员工同步记录”菜单、权限资源和列表页,可按客户端 Key、设备 ID、实体类型、实体 ID、Outbox ID 查询并查看 payloadURL 查询参数会预填筛选条件,并在命中 Outbox ID 时自动打开 payload 抽屉。
- 管理端同步记录页会从通用 payload 中提炼标题、状态、Plan/Task/Run 关联、发生时间等业务摘要,列表和详情抽屉不再只能依赖原始 JSON 排障。 - 管理端同步记录页会从通用 payload 中提炼标题、状态、Plan/Task/Run 关联、发生时间等业务摘要,列表和详情抽屉不再只能依赖原始 JSON 排障。
- 管理端列表和抽屉内的客户端 Key 仅脱敏展示,筛选框仍支持输入完整客户端 Key 精准查询。 - 管理端列表和抽屉内的客户端 Key 仅脱敏展示,筛选框仍支持输入完整客户端 Key 精准查询。