统计数据

This commit is contained in:
tanglong 2026-06-12 10:55:46 +08:00
parent d14002251f
commit c5eaaf4bb1
6 changed files with 168 additions and 9 deletions

View File

@ -41,9 +41,10 @@ public class CustomerDashboardDailyCapabilityHandler implements AgentCapabilityH
学员总数:暂未获取 学员总数:暂未获取
今日新增:暂未获取 今日新增:暂未获取
跟进成员数:暂未获取 跟进成员数:暂未获取
统计管理:暂未获取
今日收款:暂未接入 今日收款:暂未接入
""".trim(), """.trim(),
List.of("企业微信学员联系 API"), List.of("企业微信学员联系 API", "企业微信统计管理 API"),
Map.of("error", e.getMessage() == null ? "api forbidden" : e.getMessage()), Map.of("error", e.getMessage() == null ? "api forbidden" : e.getMessage()),
Instant.now() Instant.now()
); );
@ -60,15 +61,43 @@ public class CustomerDashboardDailyCapabilityHandler implements AgentCapabilityH
学员总数:%d 学员总数:%d
今日新增:%d 今日新增:%d
跟进成员数:%d 跟进成员数:%d
今日咨询数:%d
今日聊天数:%d
今日发送消息数:%d
今日回复率:%s
平均首次回复时长:%s
今日负反馈数:%d
今日收款:暂未接入 今日收款:暂未接入
""".formatted(stats.totalStudents(), stats.todayNewStudents(), stats.followUserCount()).trim(), """.formatted(
List.of("企业微信学员联系 API"), stats.totalStudents(),
stats.todayNewStudents(),
stats.followUserCount(),
stats.todayNewApplyCount(),
stats.todayChatCount(),
stats.todayMessageCount(),
percent(stats.todayReplyPercentage()),
seconds(stats.todayAvgReplySeconds()),
stats.todayNegativeFeedbackCount()
).trim(),
List.of("企业微信学员联系 API", "企业微信统计管理 API"),
Map.of( Map.of(
"totalStudents", stats.totalStudents(), "totalStudents", stats.totalStudents(),
"todayNewStudents", stats.todayNewStudents(), "todayNewStudents", stats.todayNewStudents(),
"followUserCount", stats.followUserCount() "followUserCount", stats.followUserCount(),
"todayNewApplyCount", stats.todayNewApplyCount(),
"todayChatCount", stats.todayChatCount(),
"todayMessageCount", stats.todayMessageCount(),
"todayNegativeFeedbackCount", stats.todayNegativeFeedbackCount()
), ),
Instant.now() Instant.now()
); );
} }
private String percent(Integer value) {
return value == null ? "暂无数据" : value + "%";
}
private String seconds(Integer value) {
return value == null ? "暂无数据" : value + "秒";
}
} }

View File

@ -89,6 +89,26 @@ public class WecomClient {
return response; return response;
} }
public WecomUserBehaviorDataResponse getStudentContactBehaviorData(List<String> userIds, long startTime, long endTime) {
String token = getAccessToken(studentContactSecret());
WecomUserBehaviorDataRequest request = new WecomUserBehaviorDataRequest(userIds, startTime, endTime);
WecomUserBehaviorDataResponse response = webClient.post()
.uri(uriBuilder -> uriBuilder.path("/cgi-bin/externalcontact/get_user_behavior_data")
.queryParam("access_token", token)
.build())
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.retrieve()
.bodyToMono(WecomUserBehaviorDataResponse.class)
.block();
if (response == null || !response.success()) {
log.error("wecom student contact behavior data failed err={}", response == null ? "null" : response.errmsg());
throw new IllegalStateException("WeCom student contact behavior data failed: " + (response == null ? "null" : response.errmsg()));
}
return response;
}
private String getAccessToken() { private String getAccessToken() {
if (cachedToken != null && Instant.now().isBefore(tokenExpireAt)) { if (cachedToken != null && Instant.now().isBefore(tokenExpireAt)) {
log.info("wecom gettoken use cached token expireAt={}", tokenExpireAt); log.info("wecom gettoken use cached token expireAt={}", tokenExpireAt);

View File

@ -3,5 +3,11 @@ package com.company.agentfoundation.integration.wecom;
public record WecomStudentContactStats( public record WecomStudentContactStats(
int totalStudents, int totalStudents,
int todayNewStudents, int todayNewStudents,
int followUserCount int followUserCount,
int todayNewApplyCount,
int todayChatCount,
int todayMessageCount,
int todayNegativeFeedbackCount,
Integer todayReplyPercentage,
Integer todayAvgReplySeconds
) {} ) {}

View File

@ -24,9 +24,11 @@ public class WecomStudentContactStatsService {
public WecomStudentContactStats fetchTodayStats() { public WecomStudentContactStats fetchTodayStats() {
List<String> followUsers = wecomClient.getStudentContactFollowUsers().followUser(); List<String> followUsers = wecomClient.getStudentContactFollowUsers().followUser();
if (followUsers == null || followUsers.isEmpty()) { if (followUsers == null || followUsers.isEmpty()) {
return new WecomStudentContactStats(0, 0, 0); return new WecomStudentContactStats(0, 0, 0, 0, 0, 0, 0, null, null);
} }
long todayStart = LocalDate.now(CHINA_ZONE).atStartOfDay(CHINA_ZONE).toEpochSecond();
long tomorrowStart = LocalDate.now(CHINA_ZONE).plusDays(1).atStartOfDay(CHINA_ZONE).toEpochSecond();
Set<String> allStudents = new HashSet<>(); Set<String> allStudents = new HashSet<>();
Map<String, Long> earliestCreateTimeByStudent = new HashMap<>(); Map<String, Long> earliestCreateTimeByStudent = new HashMap<>();
for (int start = 0; start < followUsers.size(); start += BATCH_SIZE) { for (int start = 0; start < followUsers.size(); start += BATCH_SIZE) {
@ -34,13 +36,22 @@ public class WecomStudentContactStatsService {
fetchBatch(batchUsers, allStudents, earliestCreateTimeByStudent); fetchBatch(batchUsers, allStudents, earliestCreateTimeByStudent);
} }
long todayStart = LocalDate.now(CHINA_ZONE).atStartOfDay(CHINA_ZONE).toEpochSecond();
long tomorrowStart = LocalDate.now(CHINA_ZONE).plusDays(1).atStartOfDay(CHINA_ZONE).toEpochSecond();
int todayNew = (int) earliestCreateTimeByStudent.values().stream() int todayNew = (int) earliestCreateTimeByStudent.values().stream()
.filter(createTime -> createTime != null && createTime >= todayStart && createTime < tomorrowStart) .filter(createTime -> createTime != null && createTime >= todayStart && createTime < tomorrowStart)
.count(); .count();
BehaviorSummary behaviorSummary = fetchBehaviorSummary(followUsers, todayStart, tomorrowStart - 1);
return new WecomStudentContactStats(allStudents.size(), todayNew, followUsers.size()); return new WecomStudentContactStats(
allStudents.size(),
todayNew,
followUsers.size(),
behaviorSummary.newApplyCount(),
behaviorSummary.chatCount(),
behaviorSummary.messageCount(),
behaviorSummary.negativeFeedbackCount(),
behaviorSummary.replyPercentage(),
behaviorSummary.avgReplySeconds()
);
} }
private void fetchBatch(List<String> userIds, Set<String> allStudents, Map<String, Long> earliestCreateTimeByStudent) { private void fetchBatch(List<String> userIds, Set<String> allStudents, Map<String, Long> earliestCreateTimeByStudent) {
@ -68,4 +79,60 @@ public class WecomStudentContactStatsService {
} }
} }
} }
private BehaviorSummary fetchBehaviorSummary(List<String> followUsers, long startTime, long endTime) {
int newApplyCount = 0;
int chatCount = 0;
int messageCount = 0;
int negativeFeedbackCount = 0;
int replyPercentageWeightedSum = 0;
int replyPercentageWeight = 0;
int avgReplyTimeWeightedSum = 0;
int avgReplyTimeWeight = 0;
for (int start = 0; start < followUsers.size(); start += BATCH_SIZE) {
List<String> batchUsers = followUsers.subList(start, Math.min(start + BATCH_SIZE, followUsers.size()));
WecomUserBehaviorDataResponse response = wecomClient.getStudentContactBehaviorData(batchUsers, startTime, endTime);
List<WecomUserBehaviorDataResponse.BehaviorData> behaviorData = response.behaviorData();
if (behaviorData == null) {
continue;
}
for (WecomUserBehaviorDataResponse.BehaviorData item : behaviorData) {
if (item == null) {
continue;
}
int itemChatCount = value(item.chatCnt());
int itemMessageCount = value(item.messageCnt());
newApplyCount += value(item.newApplyCnt());
chatCount += itemChatCount;
messageCount += itemMessageCount;
negativeFeedbackCount += value(item.negativeFeedbackCnt());
if (item.replyPercentage() != null && itemChatCount > 0) {
replyPercentageWeightedSum += item.replyPercentage() * itemChatCount;
replyPercentageWeight += itemChatCount;
}
if (item.avgReplyTime() != null && itemMessageCount > 0) {
avgReplyTimeWeightedSum += item.avgReplyTime() * itemMessageCount;
avgReplyTimeWeight += itemMessageCount;
}
}
}
Integer replyPercentage = replyPercentageWeight == 0 ? null : replyPercentageWeightedSum / replyPercentageWeight;
Integer avgReplySeconds = avgReplyTimeWeight == 0 ? null : avgReplyTimeWeightedSum / avgReplyTimeWeight;
return new BehaviorSummary(newApplyCount, chatCount, messageCount, negativeFeedbackCount, replyPercentage, avgReplySeconds);
}
private int value(Integer value) {
return value == null ? 0 : value;
}
private record BehaviorSummary(
int newApplyCount,
int chatCount,
int messageCount,
int negativeFeedbackCount,
Integer replyPercentage,
Integer avgReplySeconds
) {}
} }

View File

@ -0,0 +1,11 @@
package com.company.agentfoundation.integration.wecom;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public record WecomUserBehaviorDataRequest(
@JsonProperty("userid") List<String> userIds,
@JsonProperty("start_time") Long startTime,
@JsonProperty("end_time") Long endTime
) {}

View File

@ -0,0 +1,26 @@
package com.company.agentfoundation.integration.wecom;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public record WecomUserBehaviorDataResponse(
Integer errcode,
String errmsg,
@JsonProperty("behavior_data") List<BehaviorData> behaviorData
) {
public boolean success() {
return errcode == null || errcode == 0;
}
public record BehaviorData(
String statTime,
@JsonProperty("chat_cnt") Integer chatCnt,
@JsonProperty("message_cnt") Integer messageCnt,
@JsonProperty("reply_percentage") Integer replyPercentage,
@JsonProperty("avg_reply_time") Integer avgReplyTime,
@JsonProperty("negative_feedback_cnt") Integer negativeFeedbackCnt,
@JsonProperty("new_apply_cnt") Integer newApplyCnt,
@JsonProperty("new_contact_cnt") Integer newContactCnt
) {}
}