管理员权限

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 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<String> 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<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;
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<String, Object> 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<String, Object> 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<String, Object> row, String method, String legacyScope) {

View File

@ -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

View File

@ -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