diff --git a/src/main/java/com/company/agentfoundation/config/FoundationProperties.java b/src/main/java/com/company/agentfoundation/config/FoundationProperties.java index 2037a1d..e54dc0b 100644 --- a/src/main/java/com/company/agentfoundation/config/FoundationProperties.java +++ b/src/main/java/com/company/agentfoundation/config/FoundationProperties.java @@ -2,7 +2,9 @@ package com.company.agentfoundation.config; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; @ConfigurationProperties(prefix = "foundation") @@ -124,6 +126,7 @@ public class FoundationProperties { private Integer agentId; private String secret; private String callbackPath; + private List adminUserIds = new ArrayList<>(); public String getName() { return name; } public void setName(String name) { this.name = name; } @@ -133,6 +136,8 @@ public class FoundationProperties { public void setSecret(String secret) { this.secret = secret; } public String getCallbackPath() { return callbackPath; } public void setCallbackPath(String callbackPath) { this.callbackPath = callbackPath; } + public List getAdminUserIds() { return adminUserIds; } + public void setAdminUserIds(List adminUserIds) { this.adminUserIds = adminUserIds; } } } diff --git a/src/main/java/com/company/agentfoundation/crm/report/CustomerDailyReportService.java b/src/main/java/com/company/agentfoundation/crm/report/CustomerDailyReportService.java index bf08ec1..ed3227e 100644 --- a/src/main/java/com/company/agentfoundation/crm/report/CustomerDailyReportService.java +++ b/src/main/java/com/company/agentfoundation/crm/report/CustomerDailyReportService.java @@ -1,5 +1,6 @@ package com.company.agentfoundation.crm.report; +import com.company.agentfoundation.config.FoundationProperties; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.dao.EmptyResultDataAccessException; @@ -40,10 +41,16 @@ public class CustomerDailyReportService { private final NamedParameterJdbcTemplate jdbc; private final ObjectMapper objectMapper; + private final FoundationProperties foundationProperties; - public CustomerDailyReportService(NamedParameterJdbcTemplate jdbc, ObjectMapper objectMapper) { + public CustomerDailyReportService( + NamedParameterJdbcTemplate jdbc, + ObjectMapper objectMapper, + FoundationProperties foundationProperties + ) { this.jdbc = jdbc; this.objectMapper = objectMapper; + this.foundationProperties = foundationProperties; } public Map generate(CustomerDailyReportRequest request) { @@ -116,8 +123,12 @@ public class CustomerDailyReportService { private Caller resolveCaller(String wecomUserId) { String safeUserId = clean(wecomUserId); + if (isSportsVenueAdmin(safeUserId)) { + return new Caller(safeUserId, true, safeUserId, "企业微信管理员", "", "", "企业微信应用管理员", + "", "WECOM_APP_ADMIN", "ALL_ORGANIZATION"); + } try { - return jdbc.queryForObject(""" + Caller direct = jdbc.queryForObject(""" SELECT COALESCE(NULLIF(u.user_id, ''), c.boss_user_id, '') user_id, COALESCE(NULLIF(u.name, ''), c.name, '') name, COALESCE(u.organization_id, '') organization_id, @@ -130,17 +141,26 @@ public class CustomerDailyReportService { (rs, row) -> caller(safeUserId, rs.getString("user_id"), rs.getString("name"), rs.getString("organization_id"), rs.getString("organization_name"), rs.getString("role_names"), rs.getString("manage_organizations"), "WECOM_USER_ID", "")); + return direct != null && direct.resolved() ? direct : resolveLegacy(safeUserId); } catch (EmptyResultDataAccessException ignored) { return resolveLegacy(safeUserId); } } + private boolean isSportsVenueAdmin(String wecomUserId) { + FoundationProperties.Wecom.App app = foundationProperties.getWecom().getApps().get("sports-venue"); + return app != null && app.getAdminUserIds() != null && app.getAdminUserIds().stream() + .filter(id -> id != null && !id.isBlank()) + .anyMatch(id -> id.trim().equalsIgnoreCase(wecomUserId)); + } + private Caller resolveLegacy(String wecomUserId) { Map legacy; try { legacy = jdbc.queryForMap(""" SELECT employee_name, COALESCE(xuebang_account, '') xuebang_account, - COALESCE(data_scope_type, '') data_scope_type + COALESCE(data_scope_type, '') data_scope_type, + COALESCE(primary_role_name, '') primary_role_name FROM employee_directory WHERE wecom_user_id = :wecomUserId LIMIT 1 """, Map.of("wecomUserId", wecomUserId)); } catch (EmptyResultDataAccessException ignored) { @@ -163,9 +183,14 @@ public class CustomerDailyReportService { """.formatted(USER), Map.of()).stream() .filter(row -> normalized.equals(normalizeName(text(row, "name")))) .toList(); - return matches.size() == 1 - ? callerFromRow(wecomUserId, matches.get(0), "NORMALIZED_UNIQUE_NAME", text(legacy, "data_scope_type")) - : Caller.unresolved(wecomUserId); + if (matches.size() == 1) { + return callerFromRow(wecomUserId, matches.get(0), "NORMALIZED_UNIQUE_NAME", text(legacy, "data_scope_type")); + } + if ("ALL_ORGANIZATION".equalsIgnoreCase(text(legacy, "data_scope_type"))) { + return new Caller(wecomUserId, true, wecomUserId, text(legacy, "employee_name"), "", "", + text(legacy, "primary_role_name"), "", "LEGACY_ADMIN_SCOPE", "ALL_ORGANIZATION"); + } + return Caller.unresolved(wecomUserId); } private Caller callerFromRow(String wecomUserId, Map row, String method, String legacyScope) { diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java index b3a54e5..556e1ba 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java @@ -27,7 +27,8 @@ import java.util.concurrent.ConcurrentHashMap; public class WecomCallbackController { private static final Logger log = LoggerFactory.getLogger(WecomCallbackController.class); private static final long MESSAGE_DEDUP_TTL_SECONDS = 600; - private static final String ERROR_REPLY = "语义识别或业务查询失败,请稍后重试"; + private static final String GENERAL_ERROR_REPLY = "扣子问答调用失败,请稍后重试。"; + private static final String SEMANTIC_ERROR_REPLY = "语义识别或业务查询失败,请稍后重试。"; private static final String GENERAL_APP = "general"; private static final String SPORTS_VENUE_APP = "sports-venue"; private final SemanticDispatchService semanticDispatchService; @@ -162,12 +163,13 @@ public class WecomCallbackController { ? cozeWorkflowService.chatDefault(userText) : semanticDispatchService.dispatch(msg.getFromUserName(), userText); } catch (Exception e) { - log.error("wecom callback semantic dispatch failed fromUser={} err={}", msg.getFromUserName(), e.getMessage(), e); - reply = ERROR_REPLY; + log.error("wecom callback processing failed mode={} fromUser={} err={}", + callbackMode, msg.getFromUserName(), e.getMessage(), e); + reply = errorReply(callbackMode); } if (reply == null || reply.isBlank()) { - log.warn("wecom callback semantic reply blank, fallback to error reply"); - reply = ERROR_REPLY; + log.warn("wecom callback reply blank mode={}, fallback to error reply", callbackMode); + reply = errorReply(callbackMode); } log.info("wecom callback sending semantic reply toUser={} replyPreview={}", msg.getFromUserName(), preview(reply)); wecomClient.sendText(appKey, msg.getFromUserName(), reply); @@ -229,6 +231,10 @@ public class WecomCallbackController { return text.length() <= 80 ? text : text.substring(0, 80) + "..."; } + private String errorReply(CallbackMode callbackMode) { + return callbackMode == CallbackMode.GENERAL_CHAT ? GENERAL_ERROR_REPLY : SEMANTIC_ERROR_REPLY; + } + private enum CallbackMode { GENERAL_CHAT, SEMANTIC_ROUTING diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7102493..174b7b0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,6 +64,11 @@ foundation: agent-id: 1000006 secret: V-fh1ROzjb06Hi9XB8VPb4HhE2ApI4j99NAjAXKYOb8 callback-path: /v1/integrations/wecom/sports-venue/callback + admin-user-ids: + - TangLong + - ChenWenJie + - XieChangLei + - ZhangYunYun semantic: agent-id: crm-assistant minimum-confidence: 0.60