推送名单优化
This commit is contained in:
parent
2f7ceeca45
commit
92e80b2d09
@ -232,6 +232,12 @@ public class WecomCallbackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String startedMessage(String reportLabel) {
|
private String startedMessage(String reportLabel) {
|
||||||
|
if ("学员运营预警数据".equals(reportLabel)) {
|
||||||
|
return "### 📤 正在准备推送\n"
|
||||||
|
+ "> **" + reportLabel + "**\n"
|
||||||
|
+ "> 正在获取预警名单并匹配对应馆长,请稍候……\n\n"
|
||||||
|
+ "<font color=\"comment\">名单准备完成后将立即发送,请勿重复提交推送指令。</font>";
|
||||||
|
}
|
||||||
return "### ⏳ 正在查询\n"
|
return "### ⏳ 正在查询\n"
|
||||||
+ "> **" + reportLabel + "**\n"
|
+ "> **" + reportLabel + "**\n"
|
||||||
+ "> 正在获取最新数据,请稍候……\n\n"
|
+ "> 正在获取最新数据,请稍候……\n\n"
|
||||||
|
|||||||
@ -32,8 +32,11 @@ public class LowHoursWarningPushIntentHandler implements SemanticIntentHandler {
|
|||||||
public String handle(SemanticRequestContext context, SemanticRecognition recognition) {
|
public String handle(SemanticRequestContext context, SemanticRecognition recognition) {
|
||||||
try {
|
try {
|
||||||
SemanticDateRangeResolver.DateRange range = dateRangeResolver.resolve(context, recognition);
|
SemanticDateRangeResolver.DateRange range = dateRangeResolver.resolve(context, recognition);
|
||||||
|
String command = context.userText() == null ? "" : context.userText();
|
||||||
|
boolean lowHoursOnly = command.contains("低课时")
|
||||||
|
&& !command.contains("高频缺勤") && !command.contains("预警数据");
|
||||||
LowHoursWarningPushService.PushSummary result = service.push(
|
LowHoursWarningPushService.PushSummary result = service.push(
|
||||||
context.wecomUserId(), range.startDate(), range.endDate());
|
context.wecomUserId(), range.startDate(), range.endDate(), true, !lowHoursOnly);
|
||||||
return "### ✅ 学员运营预警推送完成\n"
|
return "### ✅ 学员运营预警推送完成\n"
|
||||||
+ "> 日期:**" + range.startDate()
|
+ "> 日期:**" + range.startDate()
|
||||||
+ (range.startDate().equals(range.endDate()) ? "" : " 至 " + range.endDate()) + "**\n"
|
+ (range.startDate().equals(range.endDate()) ? "" : " 至 " + range.endDate()) + "**\n"
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import com.company.agentfoundation.security.ReportDataScopeService.Scope;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class LowHoursWarningPushService {
|
public class LowHoursWarningPushService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LowHoursWarningPushService.class);
|
||||||
private static final String STUDENT = "/api/student/findStudentsByInstitution";
|
private static final String STUDENT = "/api/student/findStudentsByInstitution";
|
||||||
private static final String CONTRACT = "/api/contract/findByInstitution";
|
private static final String CONTRACT = "/api/contract/findByInstitution";
|
||||||
private static final String CONTRACT_PRODUCT = "/api/contractProduct/findByInstitution";
|
private static final String CONTRACT_PRODUCT = "/api/contractProduct/findByInstitution";
|
||||||
@ -53,6 +56,12 @@ public class LowHoursWarningPushService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PushSummary push(String wecomUserId, LocalDate startDate, LocalDate endDate) {
|
public PushSummary push(String wecomUserId, LocalDate startDate, LocalDate endDate) {
|
||||||
|
return push(wecomUserId, startDate, endDate, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PushSummary push(String wecomUserId, LocalDate startDate, LocalDate endDate,
|
||||||
|
boolean includeLowHours, boolean includeFrequentAbsence) {
|
||||||
|
long startedAt = System.nanoTime();
|
||||||
if (wecomUserId == null || wecomUserId.isBlank()) {
|
if (wecomUserId == null || wecomUserId.isBlank()) {
|
||||||
throw new IllegalArgumentException("wecomUserId is required");
|
throw new IllegalArgumentException("wecomUserId is required");
|
||||||
}
|
}
|
||||||
@ -67,15 +76,29 @@ public class LowHoursWarningPushService {
|
|||||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
||||||
"仅管理员、区域总、城市总或馆长可以推送学员运营预警");
|
"仅管理员、区域总、城市总或馆长可以推送学员运营预警");
|
||||||
}
|
}
|
||||||
XuebangTeachingReportDataService.Snapshot snapshot = realtimeData.load(effectiveStart, effectiveEnd, scope);
|
if (!includeLowHours && !includeFrequentAbsence) {
|
||||||
List<Map<String, Object>> lowHoursAlerts = loadLowHoursAlerts(scope);
|
throw new IllegalArgumentException("at least one warning type is required");
|
||||||
|
}
|
||||||
|
XuebangTeachingReportDataService.Snapshot snapshot = includeFrequentAbsence
|
||||||
|
? realtimeData.load(effectiveStart, effectiveEnd, scope) : null;
|
||||||
|
long attendanceLoadedAt = System.nanoTime();
|
||||||
|
List<Map<String, Object>> lowHoursAlerts = includeLowHours ? loadLowHoursAlerts(scope) : List.of();
|
||||||
|
long warningsLoadedAt = System.nanoTime();
|
||||||
|
List<Map<String, Object>> absenceAlerts = snapshot == null
|
||||||
|
? List.of() : snapshot.frequentAbsenceStudents();
|
||||||
List<Map<String, Object>> alerts = new ArrayList<>(
|
List<Map<String, Object>> alerts = new ArrayList<>(
|
||||||
lowHoursAlerts.size() + snapshot.frequentAbsenceStudents().size());
|
lowHoursAlerts.size() + absenceAlerts.size());
|
||||||
alerts.addAll(lowHoursAlerts);
|
alerts.addAll(lowHoursAlerts);
|
||||||
alerts.addAll(snapshot.frequentAbsenceStudents());
|
alerts.addAll(absenceAlerts);
|
||||||
recipientService.enrich(alerts);
|
recipientService.enrich(alerts);
|
||||||
TeachingAlertPushService.PushResult pushed = pushService.pushToVenueManagers(alerts);
|
TeachingAlertPushService.PushResult pushed = pushService.pushToVenueManagers(alerts);
|
||||||
return new PushSummary(scope.label(), lowHoursAlerts.size(), snapshot.frequentAbsenceCount(),
|
log.info("teaching warning push completed userId={} scope={} lowHours={} frequentAbsence={} "
|
||||||
|
+ "attendanceMs={} lowHoursMs={} totalMs={} messages={}",
|
||||||
|
wecomUserId, scope.label(), lowHoursAlerts.size(), snapshot == null ? 0 : snapshot.frequentAbsenceCount(),
|
||||||
|
millis(startedAt, attendanceLoadedAt), millis(attendanceLoadedAt, warningsLoadedAt),
|
||||||
|
millis(startedAt, System.nanoTime()), pushed.sentMessageCount());
|
||||||
|
return new PushSummary(scope.label(), lowHoursAlerts.size(),
|
||||||
|
snapshot == null ? 0 : snapshot.frequentAbsenceCount(),
|
||||||
pushed.recipientCount(), pushed.sentMessageCount(), pushed.unmatchedAlertCount(),
|
pushed.recipientCount(), pushed.sentMessageCount(), pushed.unmatchedAlertCount(),
|
||||||
pushed.failedRecipientCount());
|
pushed.failedRecipientCount());
|
||||||
}
|
}
|
||||||
@ -152,6 +175,10 @@ public class LowHoursWarningPushService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long millis(long start, long end) {
|
||||||
|
return java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(end - start);
|
||||||
|
}
|
||||||
|
|
||||||
private static String text(JsonNode row, String field) {
|
private static String text(JsonNode row, String field) {
|
||||||
if (row == null) return "";
|
if (row == null) return "";
|
||||||
JsonNode value = row.path(field);
|
JsonNode value = row.path(field);
|
||||||
|
|||||||
@ -29,6 +29,11 @@ public class StudentTeachingAttendanceReportController {
|
|||||||
@PostMapping("/warning-push")
|
@PostMapping("/warning-push")
|
||||||
public LowHoursWarningPushService.PushSummary pushWarnings(
|
public LowHoursWarningPushService.PushSummary pushWarnings(
|
||||||
@RequestBody TeachingWarningPushRequest request) {
|
@RequestBody TeachingWarningPushRequest request) {
|
||||||
return warningPushService.push(request.wecomUserId(), request.startDate(), request.endDate());
|
return pushSelectedWarnings(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LowHoursWarningPushService.PushSummary pushSelectedWarnings(TeachingWarningPushRequest request) {
|
||||||
|
return warningPushService.push(request.wecomUserId(), request.startDate(), request.endDate(),
|
||||||
|
request.includes("LOW_HOURS"), request.includes("FREQUENT_ABSENCE"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -16,7 +17,7 @@ import java.util.Set;
|
|||||||
class TeachingAlertPushService {
|
class TeachingAlertPushService {
|
||||||
private static final String APP_KEY = "sports-venue";
|
private static final String APP_KEY = "sports-venue";
|
||||||
private static final int MAX_STUDENTS_PER_RECIPIENT = 100;
|
private static final int MAX_STUDENTS_PER_RECIPIENT = 100;
|
||||||
private static final int STUDENTS_PER_MESSAGE = 25;
|
private static final int STUDENTS_PER_MESSAGE = 30;
|
||||||
|
|
||||||
private final WecomClient wecom;
|
private final WecomClient wecom;
|
||||||
private final FoundationProperties properties;
|
private final FoundationProperties properties;
|
||||||
@ -71,12 +72,19 @@ class TeachingAlertPushService {
|
|||||||
int failedRecipients = 0;
|
int failedRecipients = 0;
|
||||||
for (RecipientBatch batch : batches.values()) {
|
for (RecipientBatch batch : batches.values()) {
|
||||||
int displayed = Math.min(batch.alerts().size(), MAX_STUDENTS_PER_RECIPIENT);
|
int displayed = Math.min(batch.alerts().size(), MAX_STUDENTS_PER_RECIPIENT);
|
||||||
|
List<Map<String, Object>> ordered = batch.alerts().stream()
|
||||||
|
.sorted(Comparator.comparing((Map<String, Object> value) -> campusName(value))
|
||||||
|
.thenComparing(value -> text(value, "warningType"))
|
||||||
|
.thenComparing(value -> text(value, "studentName")))
|
||||||
|
.limit(displayed)
|
||||||
|
.toList();
|
||||||
|
List<List<Map<String, Object>>> pages = paginateByCampus(ordered);
|
||||||
boolean failed = false;
|
boolean failed = false;
|
||||||
for (int offset = 0; offset < displayed; offset += STUDENTS_PER_MESSAGE) {
|
for (int pageIndex = 0; pageIndex < pages.size(); pageIndex++) {
|
||||||
List<Map<String, Object>> page = batch.alerts().subList(offset,
|
List<Map<String, Object>> page = pages.get(pageIndex);
|
||||||
Math.min(offset + STUDENTS_PER_MESSAGE, displayed));
|
|
||||||
try {
|
try {
|
||||||
wecom.sendMarkdown(APP_KEY, batch.userId(), format(batch, page, offset, displayed));
|
wecom.sendMarkdown(APP_KEY, batch.userId(),
|
||||||
|
format(batch, page, pageIndex + 1, pages.size(), displayed));
|
||||||
sentMessages++;
|
sentMessages++;
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
failed = true;
|
failed = true;
|
||||||
@ -94,30 +102,72 @@ class TeachingAlertPushService {
|
|||||||
return app.getAlertCopyUserIds().stream().map(String::trim).filter(value -> !value.isEmpty()).distinct().toList();
|
return app.getAlertCopyUserIds().stream().map(String::trim).filter(value -> !value.isEmpty()).distinct().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String format(RecipientBatch batch, List<Map<String, Object>> alerts, int offset, int displayed) {
|
private String format(RecipientBatch batch, List<Map<String, Object>> alerts,
|
||||||
|
int pageNumber, int pageCount, int displayed) {
|
||||||
StringBuilder message = new StringBuilder("### ⚠️ 学员运营预警\n")
|
StringBuilder message = new StringBuilder("### ⚠️ 学员运营预警\n")
|
||||||
.append("> 接收人:").append(batch.name().isEmpty() ? batch.userId() : batch.name()).append("\n")
|
.append("> 接收人:").append(batch.name().isEmpty() ? batch.userId() : batch.name()).append("\n")
|
||||||
.append("> 预警学员:<font color=\"warning\">").append(batch.alerts().size()).append("人</font>\n\n");
|
.append("> 预警记录:<font color=\"warning\">").append(batch.alerts().size()).append("条</font>");
|
||||||
for (int index = 0; index < alerts.size(); index++) {
|
if (pageCount > 1) message.append(" | 第").append(pageNumber).append("/").append(pageCount).append("页");
|
||||||
Map<String, Object> alert = alerts.get(index);
|
message.append("\n");
|
||||||
message.append(offset + index + 1).append(". **").append(text(alert, "studentName")).append("**")
|
|
||||||
.append(" ").append(text(alert, "campusName"));
|
Map<String, List<Map<String, Object>>> byCampus = new LinkedHashMap<>();
|
||||||
if ("LOW_REMAINING_HOURS".equals(text(alert, "warningType"))) {
|
for (Map<String, Object> alert : alerts) {
|
||||||
message.append(" 剩余<font color=\"warning\">").append(text(alert, "remainingHours"))
|
byCampus.computeIfAbsent(campusName(alert), ignored -> new ArrayList<>()).add(alert);
|
||||||
.append("</font>课时");
|
|
||||||
} else {
|
|
||||||
message.append(" 排课").append(text(alert, "scheduleCount"))
|
|
||||||
.append("次/缺勤<font color=\"warning\">").append(text(alert, "absenceCount")).append("</font>次");
|
|
||||||
}
|
|
||||||
message.append("\n");
|
|
||||||
}
|
}
|
||||||
if (offset + alerts.size() == displayed && batch.alerts().size() > displayed) {
|
for (Map.Entry<String, List<Map<String, Object>>> group : byCampus.entrySet()) {
|
||||||
|
message.append("\n**🏟 ").append(group.getKey()).append("**")
|
||||||
|
.append(" → ").append(group.getValue().size()).append("条\n");
|
||||||
|
for (int index = 0; index < group.getValue().size(); index++) {
|
||||||
|
Map<String, Object> alert = group.getValue().get(index);
|
||||||
|
message.append("> ").append(index + 1).append(". **")
|
||||||
|
.append(text(alert, "studentName")).append("** ");
|
||||||
|
if ("LOW_REMAINING_HOURS".equals(text(alert, "warningType"))) {
|
||||||
|
message.append("剩余 <font color=\"warning\">").append(text(alert, "remainingHours"))
|
||||||
|
.append("</font> 课时");
|
||||||
|
} else {
|
||||||
|
message.append("排课 ").append(text(alert, "scheduleCount"))
|
||||||
|
.append(" 次/缺勤 <font color=\"warning\">").append(text(alert, "absenceCount"))
|
||||||
|
.append("</font> 次");
|
||||||
|
}
|
||||||
|
message.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pageNumber == pageCount && batch.alerts().size() > displayed) {
|
||||||
message.append("\n> 名单较多,仅展示课时最低的").append(displayed).append("人,其余")
|
message.append("\n> 名单较多,仅展示课时最低的").append(displayed).append("人,其余")
|
||||||
.append(batch.alerts().size() - displayed).append("人请在学邦系统处理。");
|
.append(batch.alerts().size() - displayed).append("人请在学邦系统处理。");
|
||||||
}
|
}
|
||||||
return message.toString();
|
return message.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<List<Map<String, Object>>> paginateByCampus(List<Map<String, Object>> ordered) {
|
||||||
|
Map<String, List<Map<String, Object>>> groups = new LinkedHashMap<>();
|
||||||
|
ordered.forEach(alert -> groups.computeIfAbsent(campusName(alert), ignored -> new ArrayList<>()).add(alert));
|
||||||
|
List<List<Map<String, Object>>> pages = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> current = new ArrayList<>();
|
||||||
|
for (List<Map<String, Object>> group : groups.values()) {
|
||||||
|
for (int offset = 0; offset < group.size(); offset += STUDENTS_PER_MESSAGE) {
|
||||||
|
List<Map<String, Object>> part = group.subList(offset,
|
||||||
|
Math.min(offset + STUDENTS_PER_MESSAGE, group.size()));
|
||||||
|
if (!current.isEmpty() && current.size() + part.size() > STUDENTS_PER_MESSAGE) {
|
||||||
|
pages.add(List.copyOf(current));
|
||||||
|
current.clear();
|
||||||
|
}
|
||||||
|
current.addAll(part);
|
||||||
|
if (current.size() == STUDENTS_PER_MESSAGE) {
|
||||||
|
pages.add(List.copyOf(current));
|
||||||
|
current.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!current.isEmpty()) pages.add(List.copyOf(current));
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String campusName(Map<?, ?> alert) {
|
||||||
|
String value = text(alert, "campusName");
|
||||||
|
return value.isEmpty() ? "未匹配运动馆" : value;
|
||||||
|
}
|
||||||
|
|
||||||
private static String text(Map<?, ?> row, String key) {
|
private static String text(Map<?, ?> row, String key) {
|
||||||
Object value = row.get(key); return value == null ? "" : String.valueOf(value).trim();
|
Object value = row.get(key); return value == null ? "" : String.valueOf(value).trim();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
package com.company.agentfoundation.teaching.report;
|
package com.company.agentfoundation.teaching.report;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public record TeachingWarningPushRequest(
|
public record TeachingWarningPushRequest(
|
||||||
LocalDate startDate,
|
LocalDate startDate,
|
||||||
LocalDate endDate,
|
LocalDate endDate,
|
||||||
String wecomUserId
|
String wecomUserId,
|
||||||
) {}
|
List<String> warningTypes
|
||||||
|
) {
|
||||||
|
public boolean includes(String warningType) {
|
||||||
|
return warningTypes == null || warningTypes.isEmpty()
|
||||||
|
|| warningTypes.stream().anyMatch(warningType::equalsIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.company.agentfoundation.semantic.handler;
|
||||||
|
|
||||||
|
import com.company.agentfoundation.semantic.SemanticDateRangeResolver;
|
||||||
|
import com.company.agentfoundation.semantic.SemanticRecognition;
|
||||||
|
import com.company.agentfoundation.semantic.SemanticRequestContext;
|
||||||
|
import com.company.agentfoundation.teaching.report.LowHoursWarningPushService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class LowHoursWarningPushIntentHandlerTest {
|
||||||
|
@Test
|
||||||
|
void lowHoursCommandSkipsFrequentAbsenceQuery() {
|
||||||
|
LowHoursWarningPushService service = mock(LowHoursWarningPushService.class);
|
||||||
|
SemanticDateRangeResolver resolver = mock(SemanticDateRangeResolver.class);
|
||||||
|
LowHoursWarningPushIntentHandler handler = new LowHoursWarningPushIntentHandler(service, resolver);
|
||||||
|
SemanticRequestContext context = new SemanticRequestContext("YiNianYiQingJing", "推送本月低课时名单",
|
||||||
|
ZonedDateTime.of(2026, 7, 22, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai")));
|
||||||
|
SemanticRecognition recognition = new SemanticRecognition(
|
||||||
|
"PUSH_TEACHING_WARNING_DATA", 0.99D, Map.of(), "TEST");
|
||||||
|
SemanticDateRangeResolver.DateRange range = new SemanticDateRangeResolver.DateRange(
|
||||||
|
LocalDate.of(2026, 7, 1), LocalDate.of(2026, 7, 22));
|
||||||
|
when(resolver.resolve(context, recognition)).thenReturn(range);
|
||||||
|
when(service.push("YiNianYiQingJing", range.startDate(), range.endDate(), true, false))
|
||||||
|
.thenReturn(new LowHoursWarningPushService.PushSummary("全部组织", 3, 0, 1, 2, 0, 0));
|
||||||
|
|
||||||
|
handler.handle(context, recognition);
|
||||||
|
|
||||||
|
verify(service).push("YiNianYiQingJing", range.startDate(), range.endDate(), true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import static org.mockito.ArgumentMatchers.contains;
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@ -54,4 +55,31 @@ class TeachingAlertPushServiceTest {
|
|||||||
verify(wecom).sendMarkdown(org.mockito.ArgumentMatchers.eq("sports-venue"),
|
verify(wecom).sendMarkdown(org.mockito.ArgumentMatchers.eq("sports-venue"),
|
||||||
org.mockito.ArgumentMatchers.eq("YiNianYiQingJing"), contains("Student A"));
|
org.mockito.ArgumentMatchers.eq("YiNianYiQingJing"), contains("Student A"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void groupsStudentsByVenueInMessage() {
|
||||||
|
WecomClient wecom = mock(WecomClient.class);
|
||||||
|
TeachingAlertPushService service = new TeachingAlertPushService(wecom);
|
||||||
|
Map<String, Object> first = alert("s1", "学员1", "浦东金桥-跃动运动馆", "3.5");
|
||||||
|
Map<String, Object> second = alert("s2", "学员2", "浦东金桥-跃动运动馆", "8");
|
||||||
|
Map<String, Object> third = alert("s3", "学员3", "浦西-跃动运动馆", "6");
|
||||||
|
|
||||||
|
service.pushToVenueManagers(List.of(first, second, third));
|
||||||
|
|
||||||
|
ArgumentCaptor<String> content = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(wecom).sendMarkdown(org.mockito.ArgumentMatchers.eq("sports-venue"),
|
||||||
|
org.mockito.ArgumentMatchers.eq("manager1"), content.capture());
|
||||||
|
assertThat(content.getValue())
|
||||||
|
.contains("🏟 浦东金桥-跃动运动馆", "学员1", "学员2", "🏟 浦西-跃动运动馆", "学员3");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> alert(String id, String name, String campus, String hours) {
|
||||||
|
Map<String, Object> alert = new LinkedHashMap<>();
|
||||||
|
alert.put("studentId", id); alert.put("studentName", name);
|
||||||
|
alert.put("campusName", campus); alert.put("remainingHours", hours);
|
||||||
|
alert.put("warningType", "LOW_REMAINING_HOURS");
|
||||||
|
alert.put("recipients", List.of(
|
||||||
|
Map.of("role", "VENUE_MANAGER", "name", "Manager", "wecomUserId", "manager1")));
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user