feat: filter digital sync links by device
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 查询并查看 payload;URL 查询参数会预填筛选条件,并在命中 Outbox ID 时自动打开 payload 抽屉。
|
- 管理端已增加“数字员工同步记录”菜单、权限资源和列表页,可按客户端 Key、设备 ID、实体类型、实体 ID、Outbox ID 查询并查看 payload;URL 查询参数会预填筛选条件,并在命中 Outbox ID 时自动打开 payload 抽屉。
|
||||||
- 管理端同步记录页会从通用 payload 中提炼标题、状态、Plan/Task/Run 关联、发生时间等业务摘要,列表和详情抽屉不再只能依赖原始 JSON 排障。
|
- 管理端同步记录页会从通用 payload 中提炼标题、状态、Plan/Task/Run 关联、发生时间等业务摘要,列表和详情抽屉不再只能依赖原始 JSON 排障。
|
||||||
- 管理端列表和抽屉内的客户端 Key 仅脱敏展示,筛选框仍支持输入完整客户端 Key 精准查询。
|
- 管理端列表和抽屉内的客户端 Key 仅脱敏展示,筛选框仍支持输入完整客户端 Key 精准查询。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user