zhuyifan

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

...@@ -6,6 +6,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; ...@@ -6,6 +6,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper;
6 import com.infoloop.tianting.server.StorageService; 6 import com.infoloop.tianting.server.StorageService;
7 import com.infoloop.tianting.server.StorageServiceFactory; 7 import com.infoloop.tianting.server.StorageServiceFactory;
8 import com.infoloop.tianting.store.LoginCodeStore; 8 import com.infoloop.tianting.store.LoginCodeStore;
9 +import com.infoloop.tianting.store.WeeklyTaskStore;
9 import com.infoloop.tianting.utils.SmsUtil; 10 import com.infoloop.tianting.utils.SmsUtil;
10 import io.lettuce.core.api.StatefulRedisConnection; 11 import io.lettuce.core.api.StatefulRedisConnection;
11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.beans.factory.annotation.Autowired;
...@@ -16,6 +17,8 @@ import org.springframework.context.annotation.Configuration; ...@@ -16,6 +17,8 @@ import org.springframework.context.annotation.Configuration;
16 import org.springframework.context.annotation.Import; 17 import org.springframework.context.annotation.Import;
17 import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; 18 import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
18 19
20 +import java.time.Duration;
21 +
19 import static com.infoloop.tianting.constant.ConfigConstants.REDIS_CONNECTION; 22 import static com.infoloop.tianting.constant.ConfigConstants.REDIS_CONNECTION;
20 23
21 @Configuration 24 @Configuration
...@@ -29,6 +32,13 @@ public class AppConfig { ...@@ -29,6 +32,13 @@ public class AppConfig {
29 return new LoginCodeStore(commands, 300); 32 return new LoginCodeStore(commands, 300);
30 } 33 }
31 34
35 + @Bean
36 + public WeeklyTaskStore weeklyTaskStore(@Autowired @Qualifier(REDIS_CONNECTION) final StatefulRedisConnection<String, String> connection) {
37 + final var commands = connection.sync();
38 + final var expireSeconds = Duration.ofDays(7).getSeconds();
39 + return new WeeklyTaskStore(commands, expireSeconds);
40 + }
41 +
32 // init bean 42 // init bean
33 @Bean 43 @Bean
34 public SmsUtil smsUtil(@Autowired SmsConfig smsConfig) { 44 public SmsUtil smsUtil(@Autowired SmsConfig smsConfig) {
......
...@@ -10,8 +10,11 @@ import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse; ...@@ -10,8 +10,11 @@ import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
10 import com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse; 10 import com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse;
11 import com.infoloop.tianting.mealorderservice.SingleMealMenuRecordRpcResponse; 11 import com.infoloop.tianting.mealorderservice.SingleMealMenuRecordRpcResponse;
12 import com.infoloop.tianting.mealorderservice.SingleMealOrderRpcResponse; 12 import com.infoloop.tianting.mealorderservice.SingleMealOrderRpcResponse;
13 +import com.infoloop.tianting.mealorderservice.SingleMpAccountDinerRefRpcResponse;
13 import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient; 14 import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
15 +import com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient;
14 import com.infoloop.tianting.service.client.MealOrderServiceRpcClient; 16 import com.infoloop.tianting.service.client.MealOrderServiceRpcClient;
17 +import com.infoloop.tianting.store.WeeklyTaskStore;
15 import com.infoloop.tianting.utils.DateUtil; 18 import com.infoloop.tianting.utils.DateUtil;
16 import lombok.RequiredArgsConstructor; 19 import lombok.RequiredArgsConstructor;
17 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
...@@ -21,9 +24,12 @@ import org.springframework.stereotype.Component; ...@@ -21,9 +24,12 @@ import org.springframework.stereotype.Component;
21 import java.time.DayOfWeek; 24 import java.time.DayOfWeek;
22 import java.time.Instant; 25 import java.time.Instant;
23 import java.time.LocalDate; 26 import java.time.LocalDate;
27 +import java.time.LocalDateTime;
24 import java.time.LocalTime; 28 import java.time.LocalTime;
25 import java.time.ZonedDateTime; 29 import java.time.ZonedDateTime;
26 import java.time.temporal.TemporalAdjusters; 30 import java.time.temporal.TemporalAdjusters;
31 +import java.util.ArrayList;
32 +import java.util.Collections;
27 import java.util.List; 33 import java.util.List;
28 import java.util.Map; 34 import java.util.Map;
29 import java.util.Objects; 35 import java.util.Objects;
...@@ -40,10 +46,42 @@ public class AutoCreateMealOrderTask { ...@@ -40,10 +46,42 @@ public class AutoCreateMealOrderTask {
40 46
41 private final ClientInventoryServiceRpcClient clientInventoryServiceRpcClient; 47 private final ClientInventoryServiceRpcClient clientInventoryServiceRpcClient;
42 48
43 - @Scheduled(cron = "59 59 23 ? * SUN") 49 + private final DeliveryRuleServiceRpcClient deliveryRuleServiceRpcClient;
50 +
51 + private final WeeklyTaskStore weeklyTaskStore;
52 +
53 + @Scheduled(cron = "0 * * ? * *")
44 public void executeTask() { 54 public void executeTask() {
45 final int enterpriseId = 449; 55 final int enterpriseId = 449;
46 - final var today = LocalDate.now(); 56 + final var now = LocalDateTime.now();
57 + final var today = now.toLocalDate();
58 + // 判断是否本周已执行
59 + final var keyPrefix = "create-meal-order-task";
60 + if (weeklyTaskStore.isExecutedThisWeek(keyPrefix, enterpriseId, today)) return;
61 +
62 + final var rules = deliveryRuleServiceRpcClient.getDefaultDeliveryTemplateAndRule(enterpriseId).getRule();
63 + if (rules.getConfigJson().getRulesList().isEmpty()) {
64 + return;
65 + }
66 + final var rule = rules.getConfigJson().getRules(0);
67 + final var endTime = LocalTime.parse(rule.getEndTime(), DateUtil.HH_MM_SS);
68 + final var enabledDays = new ArrayList<DayOfWeek>();
69 + if (Boolean.TRUE.equals(rule.getMonday())) enabledDays.add(DayOfWeek.MONDAY);
70 + if (Boolean.TRUE.equals(rule.getTuesday())) enabledDays.add(DayOfWeek.TUESDAY);
71 + if (Boolean.TRUE.equals(rule.getWednesday())) enabledDays.add(DayOfWeek.WEDNESDAY);
72 + if (Boolean.TRUE.equals(rule.getThursday())) enabledDays.add(DayOfWeek.THURSDAY);
73 + if (Boolean.TRUE.equals(rule.getFriday())) enabledDays.add(DayOfWeek.FRIDAY);
74 + if (Boolean.TRUE.equals(rule.getSaturday())) enabledDays.add(DayOfWeek.SATURDAY);
75 + if (Boolean.TRUE.equals(rule.getSunday())) enabledDays.add(DayOfWeek.SUNDAY);
76 + if (enabledDays.isEmpty()) {
77 + return;
78 + }
79 + final var maxDay = Collections.max(enabledDays);
80 + final var endDateTime = getThisWeekDay(today, maxDay).atTime(endTime);
81 + if (now.isBefore(endDateTime)) {
82 + log.info("当前时间小于订餐结束时间:{},跳过自动订餐任务", endDateTime);
83 + return;
84 + }
47 final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE); 85 final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE);
48 final var thisWeekEnd = today.with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE); 86 final var thisWeekEnd = today.with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE);
49 final var nextWeekStart = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).atStartOfDay().atZone(DateUtil.CHINA_ZONE); 87 final var nextWeekStart = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).atStartOfDay().atZone(DateUtil.CHINA_ZONE);
...@@ -59,6 +97,9 @@ public class AutoCreateMealOrderTask { ...@@ -59,6 +97,9 @@ public class AutoCreateMealOrderTask {
59 log.info("无未订餐学生,跳过自动订餐任务"); 97 log.info("无未订餐学生,跳过自动订餐任务");
60 return; 98 return;
61 } 99 }
100 +
101 + 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));
102 +
62 // 所需关联数据查询 103 // 所需关联数据查询
63 final var clientIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList(); 104 final var clientIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList();
64 final var classIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClassId).distinct().toList(); 105 final var classIds = notReservedDiners.stream().map(SingleDinerRpcResponse::getClassId).distinct().toList();
...@@ -66,7 +107,7 @@ public class AutoCreateMealOrderTask { ...@@ -66,7 +107,7 @@ public class AutoCreateMealOrderTask {
66 final var gradeClassMap = mealOrderServiceRpcClient.getGradeClassesByIds(enterpriseId, classIds).stream().collect(Collectors.toMap(SingleGradeClassRpcResponse::getId, Function.identity())); 107 final var gradeClassMap = mealOrderServiceRpcClient.getGradeClassesByIds(enterpriseId, classIds).stream().collect(Collectors.toMap(SingleGradeClassRpcResponse::getId, Function.identity()));
67 final var latestMealMenuRecordMap = mealOrderServiceRpcClient.queryLatestMealMenuRecordsByClientIds(enterpriseId, clientIds).stream().collect(Collectors.toMap(SingleMealMenuRecordRpcResponse::getClientId, Function.identity())); 108 final var latestMealMenuRecordMap = mealOrderServiceRpcClient.queryLatestMealMenuRecordsByClientIds(enterpriseId, clientIds).stream().collect(Collectors.toMap(SingleMealMenuRecordRpcResponse::getClientId, Function.identity()));
68 // 构建创建请求 109 // 构建创建请求
69 - final var creations = notReservedDiners.stream().map(diner -> toMealOrderCreation(diner, clientMap, gradeClassMap, latestMealMenuRecordMap, nextWeekStart)).filter(Objects::nonNull).toList(); 110 + final var creations = notReservedDiners.stream().map(diner -> toMealOrderCreation(diner, clientMap, gradeClassMap, latestMealMenuRecordMap, mpAccountDinerRefMap, nextWeekStart)).filter(Objects::nonNull).toList();
70 if (creations.isEmpty()) { 111 if (creations.isEmpty()) {
71 log.info("无可创建自动订餐项,跳过任务"); 112 log.info("无可创建自动订餐项,跳过任务");
72 return; 113 return;
...@@ -78,21 +119,25 @@ public class AutoCreateMealOrderTask { ...@@ -78,21 +119,25 @@ public class AutoCreateMealOrderTask {
78 .build(); 119 .build();
79 mealOrderServiceRpcClient.batchCreateMealOrders(loginInfo, creations); 120 mealOrderServiceRpcClient.batchCreateMealOrders(loginInfo, creations);
80 log.info("本周未订餐学生已自动补单 {} 条", creations.size()); 121 log.info("本周未订餐学生已自动补单 {} 条", creations.size());
122 + // 标记本周已执行
123 + weeklyTaskStore.markExecuted(keyPrefix, enterpriseId, today);
81 } 124 }
82 125
83 private MealOrderCreation toMealOrderCreation(SingleDinerRpcResponse diner, 126 private MealOrderCreation toMealOrderCreation(SingleDinerRpcResponse diner,
84 Map<Integer, SingleClientRpcResponse> clientMap, 127 Map<Integer, SingleClientRpcResponse> clientMap,
85 Map<Integer, SingleGradeClassRpcResponse> classMap, 128 Map<Integer, SingleGradeClassRpcResponse> classMap,
86 Map<Integer, SingleMealMenuRecordRpcResponse> menuMap, 129 Map<Integer, SingleMealMenuRecordRpcResponse> menuMap,
130 + Map<Integer, SingleMpAccountDinerRefRpcResponse> mpAccountDinerRefMap,
87 ZonedDateTime startOfNextWeek) { 131 ZonedDateTime startOfNextWeek) {
88 final var menu = menuMap.get(diner.getClientId()); 132 final var menu = menuMap.get(diner.getClientId());
89 final var client = clientMap.get(diner.getClientId()); 133 final var client = clientMap.get(diner.getClientId());
90 final var gradeClass = classMap.get(diner.getClassId()); 134 final var gradeClass = classMap.get(diner.getClassId());
91 - if (menu == null || client == null || gradeClass == null) return null; 135 + final var mpAccountDinerRef = mpAccountDinerRefMap.get(diner.getId());
136 + if (menu == null || client == null || gradeClass == null || mpAccountDinerRef == null) return null;
92 return MealOrderCreation.newBuilder() 137 return MealOrderCreation.newBuilder()
93 .setClientId(diner.getClientId()) 138 .setClientId(diner.getClientId())
94 .setMenuId(menu.getId()) 139 .setMenuId(menu.getId())
95 - .setAccountId(0) 140 + .setAccountId(mpAccountDinerRef.getId())
96 .setOrderMethod(MealOrderOrderMethodEnum.MEAL_ORDER_METHOD_SYSTEM) 141 .setOrderMethod(MealOrderOrderMethodEnum.MEAL_ORDER_METHOD_SYSTEM)
97 .setDinerId(diner.getId()) 142 .setDinerId(diner.getId())
98 .setDinerName(diner.getName()) 143 .setDinerName(diner.getName())
...@@ -105,6 +150,11 @@ public class AutoCreateMealOrderTask { ...@@ -105,6 +150,11 @@ public class AutoCreateMealOrderTask {
105 .build(); 150 .build();
106 } 151 }
107 152
153 + private LocalDate getThisWeekDay(LocalDate now, DayOfWeek target) {
154 + LocalDate monday = now.with(DayOfWeek.MONDAY);
155 + return monday.plusDays(target.getValue() - 1L);
156 + }
157 +
108 private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart) { 158 private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart) {
109 return IntStream.range(0, 5) 159 return IntStream.range(0, 5)
110 .mapToObj(i -> MenuSchedule.newBuilder() 160 .mapToObj(i -> MenuSchedule.newBuilder()
......
...@@ -26,7 +26,6 @@ import java.time.DayOfWeek; ...@@ -26,7 +26,6 @@ import java.time.DayOfWeek;
26 import java.time.LocalDate; 26 import java.time.LocalDate;
27 import java.time.LocalDateTime; 27 import java.time.LocalDateTime;
28 import java.time.LocalTime; 28 import java.time.LocalTime;
29 -import java.time.temporal.TemporalAdjusters;
30 import java.util.ArrayList; 29 import java.util.ArrayList;
31 import java.util.Collections; 30 import java.util.Collections;
32 import java.util.List; 31 import java.util.List;
...@@ -50,14 +49,23 @@ public class MealOrderServiceImpl implements MealOrderService { ...@@ -50,14 +49,23 @@ public class MealOrderServiceImpl implements MealOrderService {
50 final var lastWeekStart = today.minusWeeks(1).with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli(); 49 final var lastWeekStart = today.minusWeeks(1).with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
51 final var lastWeekEnd = today.minusWeeks(1).with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli(); 50 final var lastWeekEnd = today.minusWeeks(1).with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
52 final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli(); 51 final var thisWeekStart = today.with(DayOfWeek.MONDAY).atStartOfDay().atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
52 + final var thisWeekEnd = today.with(DayOfWeek.SUNDAY).atTime(LocalTime.MAX).atZone(DateUtil.CHINA_ZONE).toInstant().toEpochMilli();
53 if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.COMPLETED) { 53 if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.COMPLETED) {
54 - mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() < lastWeekStart).toList(); 54 + mealOrderRpcResponses = mealOrderRpcResponses.stream()
55 + .filter(e -> e.getReserveDate() < lastWeekStart)
56 + .toList();
55 } else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.UNDER_WAY) { 57 } else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.UNDER_WAY) {
56 - mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() >= lastWeekStart && e.getReserveDate() <= lastWeekEnd).toList(); 58 + mealOrderRpcResponses = mealOrderRpcResponses.stream()
59 + .filter(e -> e.getReserveDate() >= lastWeekStart && e.getReserveDate() <= lastWeekEnd)
60 + .toList();
57 } else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.BE_BOOKED) { 61 } else if (queryMealOrderDTO.getStatus() == QueryMealOrderStatusEnum.BE_BOOKED) {
58 - mealOrderRpcResponses = mealOrderRpcResponses.stream().filter(e -> e.getReserveDate() > thisWeekStart).toList(); 62 + mealOrderRpcResponses = mealOrderRpcResponses.stream()
63 + .filter(e -> e.getReserveDate() >= thisWeekStart && e.getReserveDate() <= thisWeekEnd)
64 + .toList();
59 } 65 }
60 - return mealOrderRpcResponses.stream().map(this::getMealOrderById).toList(); 66 + return mealOrderRpcResponses.stream()
67 + .map(this::getMealOrderById)
68 + .toList();
61 } 69 }
62 70
63 @Override 71 @Override
...@@ -170,8 +178,13 @@ public class MealOrderServiceImpl implements MealOrderService { ...@@ -170,8 +178,13 @@ public class MealOrderServiceImpl implements MealOrderService {
170 final var minDay = Collections.min(enabledDays); 178 final var minDay = Collections.min(enabledDays);
171 final var maxDay = Collections.max(enabledDays); 179 final var maxDay = Collections.max(enabledDays);
172 final var today = now.toLocalDate(); 180 final var today = now.toLocalDate();
173 - final var startDateTime = today.with(TemporalAdjusters.previousOrSame(minDay)).atTime(startTime); 181 + final var startDateTime = getThisWeekDate(today, minDay).atTime(startTime);
174 - final var endDateTime = today.with(TemporalAdjusters.nextOrSame(maxDay)).atTime(endTime); 182 + final var endDateTime = getThisWeekDate(today,maxDay).atTime(endTime);
175 return !now.isBefore(startDateTime) && !now.isAfter(endDateTime); 183 return !now.isBefore(startDateTime) && !now.isAfter(endDateTime);
176 } 184 }
185 +
186 + private static LocalDate getThisWeekDate(LocalDate now, DayOfWeek day) {
187 + LocalDate monday = now.with(DayOfWeek.MONDAY);
188 + return monday.plusDays(day.getValue() - 1L); // MONDAY 是 1,SUNDAY 是 7
189 + }
177 } 190 }
......
1 +package com.infoloop.tianting.store;
2 +
3 +import io.lettuce.core.SetArgs;
4 +import io.lettuce.core.api.sync.RedisCommands;
5 +
6 +import java.time.DayOfWeek;
7 +import java.time.LocalDate;
8 +import java.time.temporal.WeekFields;
9 +
10 +public class WeeklyTaskStore {
11 +
12 + private final RedisCommands<String, String> commands;
13 + private final long expire; // 过期时间(秒)
14 +
15 + public WeeklyTaskStore(final RedisCommands<String, String> commands, final long expire) {
16 + this.commands = commands;
17 + this.expire = expire;
18 + }
19 +
20 + public boolean isExecutedThisWeek(String keyPrefix, int enterpriseId, LocalDate date) {
21 + String key = getWeeklyKey(keyPrefix, enterpriseId, date);
22 + return commands.exists(key) > 0;
23 + }
24 +
25 + public void markExecuted(String keyPrefix, int enterpriseId, LocalDate date) {
26 + String key = getWeeklyKey(keyPrefix, enterpriseId, date);
27 + commands.set(key, "1", new SetArgs().ex(expire));
28 + }
29 +
30 + private String getWeeklyKey(String keyPrefix, int enterpriseId, LocalDate date) {
31 + WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 1);
32 + int year = date.getYear();
33 + int week = date.get(weekFields.weekOfWeekBasedYear());
34 + return String.format("%s:%d:%d-W%d", keyPrefix, enterpriseId, year, week);
35 + }
36 +}