管理员权限

This commit is contained in:
tanglong 2026-07-21 14:12:59 +08:00
parent aaaee18491
commit a6d7ec6349
4 changed files with 52 additions and 11 deletions

View File

@ -2,7 +2,9 @@ package com.company.agentfoundation.config;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
@ConfigurationProperties(prefix = "foundation") @ConfigurationProperties(prefix = "foundation")
@ -124,6 +126,7 @@ public class FoundationProperties {
private Integer agentId; private Integer agentId;
private String secret; private String secret;
private String callbackPath; private String callbackPath;
private List<String> adminUserIds = new ArrayList<>();
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = 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 void setSecret(String secret) { this.secret = secret; }
public String getCallbackPath() { return callbackPath; } public String getCallbackPath() { return callbackPath; }
public void setCallbackPath(String callbackPath) { this.callbackPath = callbackPath; } public void setCallbackPath(String callbackPath) { this.callbackPath = callbackPath; }
public List<String> getAdminUserIds() { return adminUserIds; }
public void setAdminUserIds(List<String> adminUserIds) { this.adminUserIds = adminUserIds; }
} }
} }

View File

@ -1,5 +1,6 @@
package com.company.agentfoundation.crm.report; package com.company.agentfoundation.crm.report;
import com.company.agentfoundation.config.FoundationProperties;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
@ -40,10 +41,16 @@ public class CustomerDailyReportService {
private final NamedParameterJdbcTemplate jdbc; private final NamedParameterJdbcTemplate jdbc;
private final ObjectMapper objectMapper; 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.jdbc = jdbc;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.foundationProperties = foundationProperties;
} }
public Map<String, Object> generate(CustomerDailyReportRequest request) { public Map<String, Object> generate(CustomerDailyReportRequest request) {
@ -116,8 +123,12 @@ public class CustomerDailyReportService {
private Caller resolveCaller(String wecomUserId) { private Caller resolveCaller(String wecomUserId) {
String safeUserId = clean(wecomUserId); String safeUserId = clean(wecomUserId);
if (isSportsVenueAdmin(safeUserId)) {
return new Caller(safeUserId, true, safeUserId, "企业微信管理员", "", "", "企业微信应用管理员",
"", "WECOM_APP_ADMIN", "ALL_ORGANIZATION");
}
try { try {
return jdbc.queryForObject(""" Caller direct = jdbc.queryForObject("""
SELECT COALESCE(NULLIF(u.user_id, ''), c.boss_user_id, '') user_id, SELECT COALESCE(NULLIF(u.user_id, ''), c.boss_user_id, '') user_id,
COALESCE(NULLIF(u.name, ''), c.name, '') name, COALESCE(NULLIF(u.name, ''), c.name, '') name,
COALESCE(u.organization_id, '') organization_id, 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, row) -> caller(safeUserId, rs.getString("user_id"), rs.getString("name"),
rs.getString("organization_id"), rs.getString("organization_name"), rs.getString("organization_id"), rs.getString("organization_name"),
rs.getString("role_names"), rs.getString("manage_organizations"), "WECOM_USER_ID", "")); rs.getString("role_names"), rs.getString("manage_organizations"), "WECOM_USER_ID", ""));
return direct != null && direct.resolved() ? direct : resolveLegacy(safeUserId);
} catch (EmptyResultDataAccessException ignored) { } catch (EmptyResultDataAccessException ignored) {
return resolveLegacy(safeUserId); 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) { private Caller resolveLegacy(String wecomUserId) {
Map<String, Object> legacy; Map<String, Object> legacy;
try { try {
legacy = jdbc.queryForMap(""" legacy = jdbc.queryForMap("""
SELECT employee_name, COALESCE(xuebang_account, '') xuebang_account, 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 FROM employee_directory WHERE wecom_user_id = :wecomUserId LIMIT 1
""", Map.of("wecomUserId", wecomUserId)); """, Map.of("wecomUserId", wecomUserId));
} catch (EmptyResultDataAccessException ignored) { } catch (EmptyResultDataAccessException ignored) {
@ -163,9 +183,14 @@ public class CustomerDailyReportService {
""".formatted(USER), Map.of()).stream() """.formatted(USER), Map.of()).stream()
.filter(row -> normalized.equals(normalizeName(text(row, "name")))) .filter(row -> normalized.equals(normalizeName(text(row, "name"))))
.toList(); .toList();
return matches.size() == 1 if (matches.size() == 1) {
? callerFromRow(wecomUserId, matches.get(0), "NORMALIZED_UNIQUE_NAME", text(legacy, "data_scope_type")) return callerFromRow(wecomUserId, matches.get(0), "NORMALIZED_UNIQUE_NAME", text(legacy, "data_scope_type"));
: Caller.unresolved(wecomUserId); }
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<String, Object> row, String method, String legacyScope) { private Caller callerFromRow(String wecomUserId, Map<String, Object> row, String method, String legacyScope) {

View File

@ -27,7 +27,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class WecomCallbackController { public class WecomCallbackController {
private static final Logger log = LoggerFactory.getLogger(WecomCallbackController.class); private static final Logger log = LoggerFactory.getLogger(WecomCallbackController.class);
private static final long MESSAGE_DEDUP_TTL_SECONDS = 600; 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 GENERAL_APP = "general";
private static final String SPORTS_VENUE_APP = "sports-venue"; private static final String SPORTS_VENUE_APP = "sports-venue";
private final SemanticDispatchService semanticDispatchService; private final SemanticDispatchService semanticDispatchService;
@ -162,12 +163,13 @@ public class WecomCallbackController {
? cozeWorkflowService.chatDefault(userText) ? cozeWorkflowService.chatDefault(userText)
: semanticDispatchService.dispatch(msg.getFromUserName(), userText); : semanticDispatchService.dispatch(msg.getFromUserName(), userText);
} catch (Exception e) { } catch (Exception e) {
log.error("wecom callback semantic dispatch failed fromUser={} err={}", msg.getFromUserName(), e.getMessage(), e); log.error("wecom callback processing failed mode={} fromUser={} err={}",
reply = ERROR_REPLY; callbackMode, msg.getFromUserName(), e.getMessage(), e);
reply = errorReply(callbackMode);
} }
if (reply == null || reply.isBlank()) { if (reply == null || reply.isBlank()) {
log.warn("wecom callback semantic reply blank, fallback to error reply"); log.warn("wecom callback reply blank mode={}, fallback to error reply", callbackMode);
reply = ERROR_REPLY; reply = errorReply(callbackMode);
} }
log.info("wecom callback sending semantic reply toUser={} replyPreview={}", msg.getFromUserName(), preview(reply)); log.info("wecom callback sending semantic reply toUser={} replyPreview={}", msg.getFromUserName(), preview(reply));
wecomClient.sendText(appKey, msg.getFromUserName(), reply); wecomClient.sendText(appKey, msg.getFromUserName(), reply);
@ -229,6 +231,10 @@ public class WecomCallbackController {
return text.length() <= 80 ? text : text.substring(0, 80) + "..."; 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 { private enum CallbackMode {
GENERAL_CHAT, GENERAL_CHAT,
SEMANTIC_ROUTING SEMANTIC_ROUTING

View File

@ -64,6 +64,11 @@ foundation:
agent-id: 1000006 agent-id: 1000006
secret: V-fh1ROzjb06Hi9XB8VPb4HhE2ApI4j99NAjAXKYOb8 secret: V-fh1ROzjb06Hi9XB8VPb4HhE2ApI4j99NAjAXKYOb8
callback-path: /v1/integrations/wecom/sports-venue/callback callback-path: /v1/integrations/wecom/sports-venue/callback
admin-user-ids:
- TangLong
- ChenWenJie
- XieChangLei
- ZhangYunYun
semantic: semantic:
agent-id: crm-assistant agent-id: crm-assistant
minimum-confidence: 0.60 minimum-confidence: 0.60