zhuyifan

feat(task): 实现自动创建餐单的周任务

......@@ -6,6 +6,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.infoloop.tianting.server.StorageService;
import com.infoloop.tianting.server.StorageServiceFactory;
import com.infoloop.tianting.store.LoginCodeStore;
import com.infoloop.tianting.store.WeeklyTaskStore;
import com.infoloop.tianting.utils.SmsUtil;
import io.lettuce.core.api.StatefulRedisConnection;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -16,6 +17,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import java.time.Duration;
import static com.infoloop.tianting.constant.ConfigConstants.REDIS_CONNECTION;
@Configuration
......@@ -29,6 +32,13 @@ public class AppConfig {
return new LoginCodeStore(commands, 300);
}
@Bean
public WeeklyTaskStore weeklyTaskStore(@Autowired @Qualifier(REDIS_CONNECTION) final StatefulRedisConnection<String, String> connection) {
final var commands = connection.sync();
final var expireSeconds = Duration.ofDays(7).getSeconds();
return new WeeklyTaskStore(commands, expireSeconds);
}
// init bean
@Bean
public SmsUtil smsUtil(@Autowired SmsConfig smsConfig) {
......
......@@ -10,8 +10,11 @@ import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleMealMenuRecordRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleMealOrderRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleMpAccountDinerRefRpcResponse;
import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
import com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient;
import com.infoloop.tianting.service.client.MealOrderServiceRpcClient;
import com.infoloop.tianting.store.WeeklyTaskStore;
import com.infoloop.tianting.utils.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -21,9 +24,12 @@ import org.springframework.stereotype.Component;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
......@@ -40,10 +46,42 @@ public class AutoCreateMealOrderTask {
private final ClientInventoryServiceRpcClient clientInventoryServiceRpcClient;
@Scheduled(cron = "59 59 23 ? * SUN")
private final DeliveryRuleServiceRpcClient deliveryRuleServiceRpcClient;
private final WeeklyTaskStore weeklyTaskStore;
@Scheduled(cron = "0 * * ? * *")
public void executeTask() {
final int enterpriseId = 449;
final var today = LocalDate.now();
final var now = LocalDateTime.now();
final var today = now.toLocalDate();
// 判断是否本周已执行
final var keyPrefix = "create-meal-order-task";
if (weeklyTaskStore.isExecutedThisWeek(keyPrefix, enterpriseId, today)) return;
final var rules = deliveryRuleServiceRpcClient.getDefaultDeliveryTemplateAndRule(enterpriseId).getRule();
if (rules.getConfigJson().getRulesList().isEmpty()) {
return;
}
final var rule = rules.getConfigJson().getRules(0);
final var endTime = LocalTime.parse(rule.getEndTime(), DateUtil.HH_MM_SS);
final var enabledDays = new ArrayList<DayOfWeek>();
if (Boolean.TRUE.equals(rule.getMonday())) enabledDays.add(DayOfWeek.MONDAY);
if (Boolean.TRUE.equals(rule.getTuesday())) enabledDays.add(DayOfWeek.TUESDAY);
if (Boolean.TRUE.equals(rule.getWednesday())) enabledDays.add(DayOfWeek.WEDNESDAY);
if (Boolean.TRUE.equals(rule.getThursday())) enabledDays.add(DayOfWeek.THURSDAY);
if (Boolean.TRUE.equals(rule.getFriday())) enabledDays.add(DayOfWeek.FRIDAY);
if (Boolean.TRUE.equals(rule.getSaturday())) enabledDays.add(DayOfWeek.SATURDAY);
if (Boolean.TRUE.equals(rule.getSunday())) enabledDays.add(DayOfWeek.SUNDAY);
if (enabledDays.isEmpty()) {
return;
}
final var maxDay = Collections.max(enabledDays);
final var endDateTime = getThisWeekDay(today, maxDay).atTime(endTime);
if (now.isBefore(endDateTime)) {
log.info("当前时间小于订餐结束时间:{},跳过自动订餐任务", endDateTime);
return;
}
final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE);
final var thisWeekEnd = today.with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE);
final var nextWeekStart = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).atStartOfDay().atZone(DateUtil.CHINA_ZONE);
......@@ -59,6 +97,9 @@ public class AutoCreateMealOrderTask {
log.info("无未订餐学生,跳过自动订餐任务");
return;
}
final var mpAccountDinerRefMap = mealOrderServiceRpcClient.queryMpAccountDinerRefsByCondition(enterpriseId, Collections.emptyList(), notReservedDiners.stream().map(SingleDinerRpcResponse::getId).toList()).stream().collect(Collectors.toMap(SingleMpAccountDinerRefRpcResponse::getDinerId, Function.identity(), (v1, v2) -> v1));
// 所需关联数据查询
final var clientIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList();
final var classIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClassId).distinct().toList();
......@@ -66,7 +107,7 @@ public class AutoCreateMealOrderTask {
final var gradeClassMap = mealOrderServiceRpcClient.getGradeClassesByIds(enterpriseId, classIds).stream().collect(Collectors.toMap(SingleGradeClassRpcResponse::getId, Function.identity()));
final var latestMealMenuRecordMap = mealOrderServiceRpcClient.queryLatestMealMenuRecordsByClientIds(enterpriseId, clientIds).stream().collect(Collectors.toMap(SingleMealMenuRecordRpcResponse::getClientId, Function.identity()));
// 构建创建请求
final var creations = notReservedDiners.stream().map(diner -> toMealOrderCreation(diner, clientMap, gradeClassMap, latestMealMenuRecordMap, nextWeekStart)).filter(Objects::nonNull).toList();
final var creations = notReservedDiners.stream().map(diner -> toMealOrderCreation(diner, clientMap, gradeClassMap, latestMealMenuRecordMap, mpAccountDinerRefMap, nextWeekStart)).filter(Objects::nonNull).toList();
if (creations.isEmpty()) {
log.info("无可创建自动订餐项,跳过任务");
return;
......@@ -78,21 +119,25 @@ public class AutoCreateMealOrderTask {
.build();
mealOrderServiceRpcClient.batchCreateMealOrders(loginInfo, creations);
log.info("本周未订餐学生已自动补单 {} 条", creations.size());
// 标记本周已执行
weeklyTaskStore.markExecuted(keyPrefix, enterpriseId, today);
}
private MealOrderCreation toMealOrderCreation(SingleDinerRpcResponse diner,
Map<Integer, SingleClientRpcResponse> clientMap,
Map<Integer, SingleGradeClassRpcResponse> classMap,
Map<Integer, SingleMealMenuRecordRpcResponse> menuMap,
Map<Integer, SingleMpAccountDinerRefRpcResponse> mpAccountDinerRefMap,
ZonedDateTime startOfNextWeek) {
final var menu = menuMap.get(diner.getClientId());
final var client = clientMap.get(diner.getClientId());
final var gradeClass = classMap.get(diner.getClassId());
if (menu == null || client == null || gradeClass == null) return null;
final var mpAccountDinerRef = mpAccountDinerRefMap.get(diner.getId());
if (menu == null || client == null || gradeClass == null || mpAccountDinerRef == null) return null;
return MealOrderCreation.newBuilder()
.setClientId(diner.getClientId())
.setMenuId(menu.getId())
.setAccountId(0)
.setAccountId(mpAccountDinerRef.getId())
.setOrderMethod(MealOrderOrderMethodEnum.MEAL_ORDER_METHOD_SYSTEM)
.setDinerId(diner.getId())
.setDinerName(diner.getName())
......@@ -105,6 +150,11 @@ public class AutoCreateMealOrderTask {
.build();
}
private LocalDate getThisWeekDay(LocalDate now, DayOfWeek target) {
LocalDate monday = now.with(DayOfWeek.MONDAY);
return monday.plusDays(target.getValue() - 1L);
}
private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart) {
return IntStream.range(0, 5)
.mapToObj(i -> MenuSchedule.newBuilder()
......
......@@ -26,7 +26,6 @@ import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -50,14 +49,23 @@ public class MealOrderServiceImpl implements MealOrderService {
final var lastWeekStart = today.minusWeeks(1).with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
final var lastWeekEnd = today.minusWeeks(1).with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
final var thisWeekEnd = today.with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.COMPLETED) {
mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() < lastWeekStart).toList();
mealOrderRpcResponses = mealOrderRpcResponses.stream()
.filter(e -> e.getReserveDate() < lastWeekStart)
.toList();
} else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.UNDER_WAY) {
mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() >= lastWeekStart && e.getReserveDate() <= lastWeekEnd).toList();
mealOrderRpcResponses = mealOrderRpcResponses.stream()
.filter(e -> e.getReserveDate() >= lastWeekStart && e.getReserveDate() <= lastWeekEnd)
.toList();
} else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.BE_BOOKED) {
mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() > thisWeekStart).toList();
mealOrderRpcResponses = mealOrderRpcResponses.stream()
.filter(e -> e.getReserveDate() >= thisWeekStart && e.getReserveDate() <= thisWeekEnd)
.toList();
}
return mealOrderRpcResponses.stream().map(this::getMealOrderById).toList();
return mealOrderRpcResponses.stream()
.map(this::getMealOrderById)
.toList();
}
@Override
......@@ -170,8 +178,13 @@ public class MealOrderServiceImpl implements MealOrderService {
final var minDay = Collections.min(enabledDays);
final var maxDay = Collections.max(enabledDays);
final var today = now.toLocalDate();
final var startDateTime = today.with(TemporalAdjusters.previousOrSame(minDay)).atTime(startTime);
final var endDateTime = today.with(TemporalAdjusters.nextOrSame(maxDay)).atTime(endTime);
final var startDateTime = getThisWeekDate(today, minDay).atTime(startTime);
final var endDateTime = getThisWeekDate(today,maxDay).atTime(endTime);
return !now.isBefore(startDateTime) && !now.isAfter(endDateTime);
}
private static LocalDate getThisWeekDate(LocalDate now, DayOfWeek day) {
LocalDate monday = now.with(DayOfWeek.MONDAY);
return monday.plusDays(day.getValue() - 1L); // MONDAY 是 1,SUNDAY 是 7
}
}
......
package com.infoloop.tianting.store;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.sync.RedisCommands;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
public class WeeklyTaskStore {
private final RedisCommands<String, String> commands;
private final long expire; // 过期时间(秒)
public WeeklyTaskStore(final RedisCommands<String, String> commands, final long expire) {
this.commands = commands;
this.expire = expire;
}
public boolean isExecutedThisWeek(String keyPrefix, int enterpriseId, LocalDate date) {
String key = getWeeklyKey(keyPrefix, enterpriseId, date);
return commands.exists(key) > 0;
}
public void markExecuted(String keyPrefix, int enterpriseId, LocalDate date) {
String key = getWeeklyKey(keyPrefix, enterpriseId, date);
commands.set(key, "1", new SetArgs().ex(expire));
}
private String getWeeklyKey(String keyPrefix, int enterpriseId, LocalDate date) {
WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 1);
int year = date.getYear();
int week = date.get(weekFields.weekOfWeekBasedYear());
return String.format("%s:%d:%d-W%d", keyPrefix, enterpriseId, year, week);
}
}