吸收数字员工管理端运营台后端

This commit is contained in:
baiyanyun
2026-06-12 20:07:55 +08:00
parent 1a8e0fb51f
commit 48ac9bdf19
8 changed files with 550 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
package com.xspaceagi.agent.core.adapter.application;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePlanBusinessViewDetailDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
@@ -39,4 +40,21 @@ public interface DigitalEmployeeSyncApplicationService {
String clientKey,
String deviceId
);
DigitalEmployeeAdminWorkbenchPageDto queryAdminWorkbench(
String view,
String clientKey,
String deviceId,
String entityType,
String entityId,
String status,
String kind,
String category,
String level,
String priority,
Date updatedFrom,
Date updatedTo,
Long pageNo,
Long pageSize
);
}

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 DigitalEmployeeAdminWorkbenchPageDto {
private Long total;
@JsonProperty("page_no")
private Long pageNo;
@JsonProperty("page_size")
private Long pageSize;
private List<DigitalEmployeeAdminWorkbenchRowDto> records;
}

View File

@@ -0,0 +1,57 @@
package com.xspaceagi.agent.core.adapter.dto.digital;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
import java.util.Map;
@Data
@Builder
public class DigitalEmployeeAdminWorkbenchRowDto {
private String view;
@JsonProperty("sync_record_id")
private Long syncRecordId;
@JsonProperty("sync_record_url")
private String syncRecordUrl;
@JsonProperty("entity_type")
private String entityType;
@JsonProperty("entity_id")
private String entityId;
private String operation;
private String title;
private String status;
private String kind;
private String category;
private String level;
private String priority;
private String summary;
@JsonProperty("plan_id")
private String planId;
@JsonProperty("task_id")
private String taskId;
@JsonProperty("run_id")
private String runId;
@JsonProperty("approval_id")
private String approvalId;
@JsonProperty("artifact_id")
private String artifactId;
@JsonProperty("notification_id")
private String notificationId;
@JsonProperty("report_id")
private String reportId;
@JsonProperty("route_decision_id")
private String routeDecisionId;
@JsonProperty("occurred_at")
private String occurredAt;
@JsonProperty("synced_at")
private Date syncedAt;
@JsonProperty("client_key_masked")
private String clientKeyMasked;
@JsonProperty("device_id")
private String deviceId;
@JsonProperty("business_view")
private Map<String, Object> businessView;
private Map<String, Object> payload;
}

View File

@@ -7,6 +7,8 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchRowDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewRowDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePlanBusinessViewDetailDto;
@@ -32,6 +34,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@Service
public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmployeeSyncApplicationService {
@@ -267,6 +270,197 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
.build();
}
@Override
public DigitalEmployeeAdminWorkbenchPageDto queryAdminWorkbench(
String view,
String clientKey,
String deviceId,
String entityType,
String entityId,
String status,
String kind,
String category,
String level,
String priority,
Date updatedFrom,
Date updatedTo,
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 adminWorkbenchPage(List.of(), currentPage, currentPageSize);
}
String normalizedView = normalizeAdminView(view);
IPage<DigitalEmployeeSyncRecord> page = digitalEmployeeSyncRecordRepository.queryBusinessRecords(
tenantId,
clientKey,
deviceId,
StringUtils.trimToNull(entityType),
entityId,
updatedFrom,
updatedTo,
null,
currentPage,
currentPageSize
);
List<DigitalEmployeeAdminWorkbenchRowDto> rows = page.getRecords().stream()
.map(record -> toAdminWorkbenchRow(record, normalizedView))
.filter(row -> matchesAdminView(normalizedView, row))
.filter(row -> matchesText(status, row.getStatus()))
.filter(row -> matchesText(kind, row.getKind()))
.filter(row -> matchesText(category, row.getCategory()))
.filter(row -> matchesText(level, row.getLevel()))
.filter(row -> matchesText(priority, row.getPriority()))
.toList();
return adminWorkbenchPage(rows, currentPage, currentPageSize);
}
private DigitalEmployeeAdminWorkbenchPageDto adminWorkbenchPage(List<DigitalEmployeeAdminWorkbenchRowDto> rows, long pageNo, long pageSize) {
int fromIndex = (int) Math.min(Math.max(pageNo - 1, 0) * pageSize, rows.size());
int toIndex = (int) Math.min(fromIndex + pageSize, rows.size());
return DigitalEmployeeAdminWorkbenchPageDto.builder()
.total((long) rows.size())
.pageNo(pageNo)
.pageSize(pageSize)
.records(rows.subList(fromIndex, toIndex))
.build();
}
private DigitalEmployeeAdminWorkbenchRowDto toAdminWorkbenchRow(DigitalEmployeeSyncRecord record, String view) {
DigitalEmployeeBusinessViewRowDto businessRow = toBusinessRow(record);
Map<String, Object> businessView = readJsonObject(record.getBusinessView());
Map<String, Object> payload = readJsonObject(record.getPayload());
String category = adminCategory(businessRow, businessView, payload);
return DigitalEmployeeAdminWorkbenchRowDto.builder()
.view(view)
.syncRecordId(record.getId())
.syncRecordUrl(syncRecordUrl(record.getOutboxId()))
.entityType(businessRow.getEntityType())
.entityId(businessRow.getEntityId())
.operation(record.getOperation())
.title(businessRow.getTitle())
.status(businessRow.getStatus())
.kind(businessRow.getKind())
.category(category)
.level(firstString(businessView.get("level"), payload.get("level")))
.priority(firstString(businessView.get("priority"), payload.get("priority")))
.summary(firstString(businessRow.getSummary(), businessView.get("reason"), payload.get("reason")))
.planId(businessRow.getPlanId())
.taskId(businessRow.getTaskId())
.runId(businessRow.getRunId())
.approvalId(firstString(businessView.get("approval_id"), payload.get("approval_id"), "approval".equals(record.getEntityType()) ? record.getEntityId() : null))
.artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null))
.notificationId(firstString(businessView.get("notification_id"), payload.get("notification_id")))
.reportId(firstString(businessView.get("report_id"), payload.get("report_id")))
.routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id")))
.occurredAt(businessRow.getOccurredAt())
.syncedAt(record.getSyncedAt())
.clientKeyMasked(maskClientKey(record.getClientKey()))
.deviceId(record.getDeviceId())
.businessView(businessView)
.payload(redactedPayload(payload))
.build();
}
private String normalizeAdminView(String view) {
String normalized = StringUtils.trimToEmpty(view).toLowerCase().replace('-', '_');
return StringUtils.isBlank(normalized) ? "business" : normalized;
}
private boolean matchesAdminView(String view, DigitalEmployeeAdminWorkbenchRowDto row) {
return switch (view) {
case "interventions" -> isInterventionRow(row);
case "artifact_lifecycle" -> isArtifactLifecycleRow(row);
case "notifications" -> isNotificationRow(row);
case "audits" -> isAuditRow(row);
case "daily_reports" -> isDailyReportRow(row);
case "route_governance" -> isRouteGovernanceRow(row);
default -> true;
};
}
private boolean isInterventionRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "approval_conflict")
|| StringUtils.equals(row.getCategory(), "route_intervention")
|| StringUtils.equals(row.getCategory(), "risk_approval")
|| StringUtils.equals(row.getCategory(), "action_notification");
}
private boolean isArtifactLifecycleRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getEntityType(), "artifact")
|| startsWithAny(row.getKind(), "artifact_")
|| StringUtils.equals(row.getCategory(), "artifact_lifecycle");
}
private boolean isNotificationRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "notification")
|| StringUtils.equals(row.getCategory(), "action_notification")
|| contains(row.getKind(), "notification")
|| StringUtils.isNotBlank(row.getNotificationId());
}
private boolean isAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return Set.of("sandbox_audit", "token_cost", "tool_call_audit", "governance", "sync_failure").contains(row.getCategory())
|| contains(row.getKind(), "audit")
|| contains(row.getKind(), "cost");
}
private boolean isDailyReportRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "daily_report")
|| startsWithAny(row.getKind(), "daily_report")
|| StringUtils.isNotBlank(row.getReportId());
}
private boolean isRouteGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getCategory(), "route_governance")
|| StringUtils.equals(row.getCategory(), "route_intervention")
|| StringUtils.equals(row.getCategory(), "governance")
|| startsWithAny(row.getKind(), "route_", "governance_")
|| StringUtils.isNotBlank(row.getRouteDecisionId());
}
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required")
|| contains(kind, "route_action_missing_context") || contains(kind, "route_action_not_mapped")) {
return "route_intervention";
}
String category = firstString(businessView.get("category"), payload.get("category"));
if (StringUtils.isNotBlank(category)) return category;
if (contains(kind, "risk_approval")) return "risk_approval";
if (contains(kind, "notification") || StringUtils.isNotBlank(firstString(businessView.get("notification_id"), payload.get("notification_id")))) {
String level = firstString(businessView.get("level"), payload.get("level"));
return StringUtils.equals(level, "action") ? "action_notification" : "notification";
}
if (startsWithAny(kind, "artifact_")) return "artifact_lifecycle";
if (startsWithAny(kind, "daily_report")) return "daily_report";
if (startsWithAny(kind, "governance_")) return "governance";
if (contains(kind, "audit")) return kind;
return row.getEntityType();
}
private Map<String, Object> redactedPayload(Map<String, Object> payload) {
Map<String, Object> redacted = new LinkedHashMap<>();
payload.forEach((key, value) -> {
if (!isSensitivePayloadKey(key)) {
redacted.put(key, value);
}
});
return redacted;
}
private boolean isSensitivePayloadKey(String key) {
String normalized = StringUtils.trimToEmpty(key).toLowerCase();
return Set.of("token", "authorization", "raw_input", "command", "prompt", "raw_prompt", "input").contains(normalized);
}
private List<DigitalEmployeeBusinessViewRowDto> rowsByType(List<DigitalEmployeeBusinessViewRowDto> rows, String entityType) {
return rows.stream().filter(row -> StringUtils.equals(row.getEntityType(), entityType)).toList();
}
@@ -353,6 +547,25 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
return StringUtils.isBlank(expected) || StringUtils.equals(expected, actual);
}
private boolean booleanValue(Object value) {
if (value instanceof Boolean bool) return bool;
if (value instanceof String text) return Boolean.parseBoolean(text.trim());
return false;
}
private boolean contains(String value, String fragment) {
return StringUtils.containsIgnoreCase(value, fragment);
}
private boolean startsWithAny(String value, String... prefixes) {
if (StringUtils.isBlank(value)) return false;
String normalized = value.toLowerCase();
for (String prefix : prefixes) {
if (normalized.startsWith(prefix)) return true;
}
return false;
}
private String normalizeBusinessEntityType(String value) {
String normalized = StringUtils.trimToEmpty(value).toLowerCase();
if (normalized.endsWith("s")) normalized = normalized.substring(0, normalized.length() - 1);

View File

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewRowDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchRowDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePlanBusinessViewDetailDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncItemDto;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
@@ -177,6 +179,87 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
verify(repository, never()).queryBusinessRecords(any(), any(), any(), any(), any(), any(), any(), any(), any(Long.class), any(Long.class));
}
@Test
void queryAdminWorkbenchInterventionsReturnsConflictAndRouteRowsOnly() {
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
.thenReturn(page(List.of(
record("outbox-approval-conflict", "approval", "approval-1", 101L, new Date(10_000),
Map.of(
"entity_type", "approval",
"title", "远端审批冲突",
"status", "pending",
"approval_conflict_active", true,
"approval_conflict_reason", "terminal_status_mismatch",
"approval_id", "approval-1",
"plan_id", "plan-1",
"priority", "high"
),
Map.of("id", "approval-1", "status", "pending")),
record("outbox-route-blocked", "event", "event-1", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "route_governance",
"kind", "route_decision_blocked",
"status", "blocked",
"title", "路由策略阻断",
"route_decision_id", "route-1",
"reason", "route_action_policy_blocked"
),
Map.of("id", "event-1", "kind", "route_decision_blocked")),
record("outbox-approval-normal", "approval", "approval-2", 101L, new Date(12_000),
Map.of("entity_type", "approval", "title", "普通审批", "status", "approved"),
Map.of("id", "approval-2", "status", "approved"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"interventions", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(2);
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
.containsExactly("approval-1", "event-1");
assertThat(result.getRecords().get(0).getView()).isEqualTo("interventions");
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("approval_conflict");
assertThat(result.getRecords().get(0).getApprovalId()).isEqualTo("approval-1");
assertThat(result.getRecords().get(1).getRouteDecisionId()).isEqualTo("route-1");
}
@Test
void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() {
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
.thenReturn(page(List.of(
record("outbox-audit", "event", "event-audit", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "tool_call_audit",
"kind", "tool_call_audit",
"status", "rejected",
"level", "warn",
"tool_name", "shell",
"server_id", "local"
),
Map.of(
"id", "event-audit",
"token", "secret-token",
"authorization", "Bearer secret",
"raw_input", "full prompt",
"command", "cat /private/key",
"prompt", "sensitive prompt",
"tool_name", "shell"
))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
);
assertThat(result.getTotal()).isEqualTo(1);
DigitalEmployeeAdminWorkbenchRowDto row = result.getRecords().get(0);
assertThat(row.getCategory()).isEqualTo("tool_call_audit");
assertThat(row.getPayload()).doesNotContainKeys("token", "authorization", "raw_input", "command", "prompt");
assertThat(row.getPayload()).containsEntry("tool_name", "shell");
}
private IPage<DigitalEmployeeSyncRecord> page(List<DigitalEmployeeSyncRecord> records) {
Page<DigitalEmployeeSyncRecord> page = new Page<>(1, 20, records.size());
page.setRecords(records);

View File

@@ -0,0 +1,80 @@
package com.xspaceagi.agent.web.ui.controller;
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
import com.xspaceagi.system.spec.annotation.RequireResource;
import com.xspaceagi.system.spec.dto.ReqResult;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
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;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST;
@RestController
@RequestMapping("/api/digital-employee/admin/workbench")
public class DigitalEmployeeAdminWorkbenchController {
@Resource
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
@RequestMapping(path = "/{view}", method = RequestMethod.GET)
public ReqResult<DigitalEmployeeAdminWorkbenchPageDto> queryWorkbench(
@PathVariable("view") String view,
@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_id", required = false) String entityId,
@RequestParam(name = "status", required = false) String status,
@RequestParam(name = "kind", required = false) String kind,
@RequestParam(name = "category", required = false) String category,
@RequestParam(name = "level", required = false) String level,
@RequestParam(name = "priority", required = false) String priority,
@RequestParam(name = "updated_from", required = false) String updatedFrom,
@RequestParam(name = "updated_to", required = false) String updatedTo,
@RequestParam(name = "page_no", required = false) Long pageNo,
@RequestParam(name = "page_size", required = false) Long pageSize
) {
return ReqResult.success(digitalEmployeeSyncApplicationService.queryAdminWorkbench(
view,
clientKey,
deviceId,
entityType,
entityId,
status,
kind,
category,
level,
priority,
parseDate(updatedFrom),
parseDate(updatedTo),
pageNo,
pageSize
));
}
private Date parseDate(String value) {
if (StringUtils.isBlank(value)) return null;
String trimmed = value.trim();
try {
return Date.from(Instant.parse(trimmed));
} catch (Exception ignored) {
try {
LocalDateTime localDateTime = LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
} catch (Exception ignoredAgain) {
return null;
}
}
}
}

View File

@@ -422,6 +422,11 @@ public enum ResourceEnum {
CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST(ResourceTypeEnum.OPERATION, "content_digital_employee_sync_query_list", "查询列表", "content_digital_employee_sync"),
// 2级模块 - 数字员工运营台
CONTENT_DIGITAL_EMPLOYEE_OPS(ResourceTypeEnum.MODULE, "content_digital_employee_ops", "数字员工运营台", "content_manage"),
CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST(ResourceTypeEnum.OPERATION, "content_digital_employee_ops_query_list", "查询列表", "content_digital_employee_ops"),
// ================== 多语言管理模块 ==================
I18N_LANG_MANAGE(ResourceTypeEnum.MODULE, "i18n_lang_manage", "多语言管理模块", "root"),
I18N_LANG_QUERY(ResourceTypeEnum.OPERATION, "i18n_lang_query", "查询", "i18n_lang_manage"),

View File

@@ -2,3 +2,79 @@ ALTER TABLE `digital_employee_sync_record`
ADD COLUMN `contract_version` VARCHAR(128) DEFAULT NULL COMMENT '客户端同步契约版本' AFTER `operation`,
ADD COLUMN `entity_summary` JSON DEFAULT NULL COMMENT '客户端实体摘要' AFTER `contract_version`,
ADD COLUMN `business_view` JSON DEFAULT NULL COMMENT '客户端业务视图投影' AFTER `entity_summary`;
-- Add digital employee admin operations workbench menu and resource.
-- Idempotent for existing tenants: creates menu/resource rows, binds menu to resource,
-- and grants super_admin default access. Other roles can be granted this menu
-- from the menu permission management UI.
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
SELECT parent.id, 'content_digital_employee_ops', '数字员工运营台', '数字员工运营台', 1, 1, 91, 1, parent.`_tenant_id`, 0, 'system', 1
FROM sys_resource parent
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
AND NOT EXISTS (
SELECT 1 FROM sys_resource r
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
);
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
SELECT parent.id, 'content_digital_employee_ops_query_list', '查询列表', '查询数字员工运营台', 1, 2, 1, 1, parent.`_tenant_id`, 0, 'system', 1
FROM sys_resource parent
WHERE parent.`code` = 'content_digital_employee_ops' AND parent.`yn` = 1
AND NOT EXISTS (
SELECT 1 FROM sys_resource r
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_ops_query_list' AND r.`yn` = 1
);
INSERT INTO sys_menu (`parent_id`, `code`, `name`, `description`, `source`, `path`, `open_type`, `icon`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
SELECT parent.id, 'content_digital_employee_ops', '数字员工运营台', '数字员工运营台', 1, '/system/content/content-digital-employee-ops', 1, '', 91, 1, parent.`_tenant_id`, 0, 'system', 1
FROM sys_menu parent
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
AND NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.`_tenant_id` = parent.`_tenant_id` AND m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
);
INSERT INTO sys_menu_resource (`menu_id`, `resource_id`, `resource_bind_type`, `_tenant_id`, `creator_id`, `creator`, `yn`)
SELECT m.id, r.id, 1, m.`_tenant_id`, 0, 'system', 1
FROM sys_menu m
JOIN sys_resource r ON r.`_tenant_id` = m.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
WHERE m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
AND NOT EXISTS (
SELECT 1 FROM sys_menu_resource mr
WHERE mr.`_tenant_id` = m.`_tenant_id` AND mr.`menu_id` = m.id AND mr.`resource_id` = r.id AND mr.`yn` = 1
);
UPDATE sys_menu_resource mr
JOIN sys_menu m ON m.id = mr.menu_id AND m.`_tenant_id` = mr.`_tenant_id` AND m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
JOIN sys_resource r ON r.id = mr.resource_id AND r.`_tenant_id` = mr.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
SET mr.`resource_bind_type` = 1,
mr.`modified` = CURRENT_TIMESTAMP
WHERE mr.`yn` = 1;
INSERT INTO sys_role_menu (`role_id`, `menu_id`, `menu_bind_type`, `resource_tree_json`, `_tenant_id`, `creator_id`, `creator`, `yn`)
SELECT role.id,
menu.id,
1,
JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
menu.`_tenant_id`,
0,
'system',
1
FROM sys_role role
JOIN sys_menu menu ON menu.`_tenant_id` = role.`_tenant_id` AND menu.`code` = 'content_digital_employee_ops' AND menu.`yn` = 1
JOIN sys_resource resource ON resource.`_tenant_id` = role.`_tenant_id` AND resource.`code` = 'content_digital_employee_ops' AND resource.`yn` = 1
WHERE role.`code` = 'super_admin' AND role.`yn` = 1
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.`_tenant_id` = role.`_tenant_id` AND rm.`role_id` = role.id AND rm.`menu_id` = menu.id AND rm.`yn` = 1
);
UPDATE sys_role_menu rm
JOIN sys_role role ON role.id = rm.role_id AND role.`_tenant_id` = rm.`_tenant_id` AND role.`code` = 'super_admin' AND role.`yn` = 1
JOIN sys_menu menu ON menu.id = rm.menu_id AND menu.`_tenant_id` = rm.`_tenant_id` AND menu.`code` = 'content_digital_employee_ops' AND menu.`yn` = 1
JOIN sys_resource resource ON resource.`_tenant_id` = rm.`_tenant_id` AND resource.`code` = 'content_digital_employee_ops' AND resource.`yn` = 1
SET rm.`menu_bind_type` = 1,
rm.`resource_tree_json` = JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
rm.`modified` = CURRENT_TIMESTAMP
WHERE rm.`yn` = 1;