feat: 支持批量查询订餐人多段停餐区间(getSuspensionIntervalsForDiners)完善SuspensionServiceImpl相关逻辑
Showing
6 changed files
with
187 additions
and
49 deletions
| ... | @@ -20,6 +20,12 @@ application { | ... | @@ -20,6 +20,12 @@ application { |
| 20 | '-Dlogging.path=logs'] | 20 | '-Dlogging.path=logs'] |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | +java { | ||
| 24 | + toolchain { | ||
| 25 | + languageVersion = JavaLanguageVersion.of(17) | ||
| 26 | + } | ||
| 27 | +} | ||
| 28 | + | ||
| 23 | ext { | 29 | ext { |
| 24 | grpcVersion = '1.62.2' | 30 | grpcVersion = '1.62.2' |
| 25 | protobufVersion = '3.25.3' | 31 | protobufVersion = '3.25.3' | ... | ... |
| ... | @@ -237,16 +237,16 @@ public class AutoCreateMealOrderTask { | ... | @@ -237,16 +237,16 @@ public class AutoCreateMealOrderTask { |
| 237 | final SingleMpAccountDinerRefRpcResponse mpAccountDinerRef = dinerIdToAccountRef.get(diner.getId()); | 237 | final SingleMpAccountDinerRefRpcResponse mpAccountDinerRef = dinerIdToAccountRef.get(diner.getId()); |
| 238 | if (menu == null || client == null || gradeClass == null) return null; | 238 | if (menu == null || client == null || gradeClass == null) return null; |
| 239 | 239 | ||
| 240 | - // 计算并集停餐窗口(学校/班级/学生维度的最新记录取并集) | 240 | + // 计算多段停餐区间(学校/班级/学生维度最新记录的并集,保留间隙) |
| 241 | - final var unionWindow = suspensionService.getSuspensionUnionWindowForDiner( | 241 | + final var intervals = suspensionService.getSuspensionIntervalsForDiner( |
| 242 | enterpriseId, | 242 | enterpriseId, |
| 243 | diner.getClientId(), | 243 | diner.getClientId(), |
| 244 | diner.getClassId(), | 244 | diner.getClassId(), |
| 245 | diner.getId() | 245 | diner.getId() |
| 246 | ); | 246 | ); |
| 247 | 247 | ||
| 248 | - // 构建下周的餐食安排(基于并集时间窗) | 248 | + // 构建下周的餐食安排(基于多段停餐区间) |
| 249 | - List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, unionWindow, dateStrToHolidayStatus); | 249 | + List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, intervals, dateStrToHolidayStatus); |
| 250 | 250 | ||
| 251 | if (meals.isEmpty()) { | 251 | if (meals.isEmpty()) { |
| 252 | return null; | 252 | return null; |
| ... | @@ -271,7 +271,7 @@ public class AutoCreateMealOrderTask { | ... | @@ -271,7 +271,7 @@ public class AutoCreateMealOrderTask { |
| 271 | // 已由并集窗口版本替代 | 271 | // 已由并集窗口版本替代 |
| 272 | 272 | ||
| 273 | private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart, | 273 | private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart, |
| 274 | - SuspensionService.SuspensionWindow unionWindow, | 274 | + java.util.List<SuspensionService.SuspensionInterval> intervals, |
| 275 | Map<String, Integer> holidayMap) { | 275 | Map<String, Integer> holidayMap) { |
| 276 | return IntStream.range(0, 7) | 276 | return IntStream.range(0, 7) |
| 277 | .mapToObj(i -> { | 277 | .mapToObj(i -> { |
| ... | @@ -284,12 +284,12 @@ public class AutoCreateMealOrderTask { | ... | @@ -284,12 +284,12 @@ public class AutoCreateMealOrderTask { |
| 284 | return null; | 284 | return null; |
| 285 | } | 285 | } |
| 286 | 286 | ||
| 287 | - if (unionWindow != null) { | 287 | + if (intervals != null && !intervals.isEmpty()) { |
| 288 | final var dateStart = date.with(LocalTime.MIN).toInstant().toEpochMilli(); | 288 | final var dateStart = date.with(LocalTime.MIN).toInstant().toEpochMilli(); |
| 289 | final var dateEnd = date.with(LocalTime.MAX).toInstant().toEpochMilli(); | 289 | final var dateEnd = date.with(LocalTime.MAX).toInstant().toEpochMilli(); |
| 290 | - if (!(dateEnd < unionWindow.startAt || dateStart > unionWindow.endAt)) { | 290 | + final boolean overlaps = intervals.stream() |
| 291 | - return null; | 291 | + .anyMatch(interval -> !(dateEnd < interval.startAt || dateStart > interval.endAt)); |
| 292 | - } | 292 | + if (overlaps) return null; |
| 293 | } | 293 | } |
| 294 | 294 | ||
| 295 | return MenuSchedule.newBuilder() | 295 | return MenuSchedule.newBuilder() | ... | ... |
| ... | @@ -6,6 +6,7 @@ import lombok.Builder; | ... | @@ -6,6 +6,7 @@ import lombok.Builder; |
| 6 | import lombok.Data; | 6 | import lombok.Data; |
| 7 | 7 | ||
| 8 | import java.util.Date; | 8 | import java.util.Date; |
| 9 | +import java.util.List; | ||
| 9 | 10 | ||
| 10 | @Data | 11 | @Data |
| 11 | @Builder | 12 | @Builder |
| ... | @@ -38,12 +39,20 @@ public class DinerMealSuspensionRecordVO { | ... | @@ -38,12 +39,20 @@ public class DinerMealSuspensionRecordVO { |
| 38 | @ApiModelProperty("学号") | 39 | @ApiModelProperty("学号") |
| 39 | private String studentNo; | 40 | private String studentNo; |
| 40 | 41 | ||
| 41 | - @ApiModelProperty("开始时间") | 42 | + @ApiModelProperty("停餐时间区间列表") |
| 42 | - private Date startAt; | 43 | + private List<Interval> intervals; |
| 43 | 44 | ||
| 44 | - @ApiModelProperty("结束时间") | 45 | + @Data |
| 45 | - private Date endAt; | 46 | + @Builder |
| 47 | + @ApiModel("停餐时间区间") | ||
| 48 | + public static class Interval { | ||
| 49 | + @ApiModelProperty("开始时间") | ||
| 50 | + private Date startAt; | ||
| 46 | 51 | ||
| 47 | - @ApiModelProperty("备注") | 52 | + @ApiModelProperty("结束时间") |
| 48 | - private String comment; | 53 | + private Date endAt; |
| 54 | + | ||
| 55 | + @ApiModelProperty("备注") | ||
| 56 | + private String comment; | ||
| 57 | + } | ||
| 49 | } | 58 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -25,6 +25,19 @@ public interface SuspensionService { | ... | @@ -25,6 +25,19 @@ public interface SuspensionService { |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | /** | 27 | /** |
| 28 | + * 多段时间窗对象(标准闭区间 \[startAt, endAt\]),用于表示并集后可能出现的多段停餐区间 | ||
| 29 | + */ | ||
| 30 | + class SuspensionInterval { | ||
| 31 | + public final long startAt; | ||
| 32 | + public final long endAt; | ||
| 33 | + | ||
| 34 | + public SuspensionInterval(long startAt, long endAt) { | ||
| 35 | + this.startAt = startAt; | ||
| 36 | + this.endAt = endAt; | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + /** | ||
| 28 | * 获取就餐人的停餐记录(学校/班级/学生三个维度各取最新一条),用于做并集判断 | 41 | * 获取就餐人的停餐记录(学校/班级/学生三个维度各取最新一条),用于做并集判断 |
| 29 | * | 42 | * |
| 30 | * @param enterpriseId 企业ID | 43 | * @param enterpriseId 企业ID |
| ... | @@ -46,6 +59,16 @@ public interface SuspensionService { | ... | @@ -46,6 +59,16 @@ public interface SuspensionService { |
| 46 | * 返回的 Map key 为 dinerId,仅为存在停餐窗口的订餐人返回条目。 | 59 | * 返回的 Map key 为 dinerId,仅为存在停餐窗口的订餐人返回条目。 |
| 47 | */ | 60 | */ |
| 48 | Map<Integer, SuspensionWindow> getSuspensionUnionWindowsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners); | 61 | Map<Integer, SuspensionWindow> getSuspensionUnionWindowsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners); |
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * 获取单个订餐人的停餐多段时间区间(对学校/班级/学生三个维度的最新记录做标准并集,保留间隙) | ||
| 65 | + */ | ||
| 66 | + List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId); | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * 批量获取多个订餐人的停餐多段时间区间。返回 Map<dinerId, List<Interval>>。 | ||
| 70 | + */ | ||
| 71 | + Map<Integer, List<SuspensionInterval>> getSuspensionIntervalsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners); | ||
| 49 | } | 72 | } |
| 50 | 73 | ||
| 51 | 74 | ... | ... |
| ... | @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; | ... | @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; |
| 22 | import org.springframework.stereotype.Service; | 22 | import org.springframework.stereotype.Service; |
| 23 | 23 | ||
| 24 | import java.time.DayOfWeek; | 24 | import java.time.DayOfWeek; |
| 25 | -import java.time.Instant; | ||
| 26 | import java.time.LocalDate; | 25 | import java.time.LocalDate; |
| 27 | import java.time.LocalDateTime; | 26 | import java.time.LocalDateTime; |
| 28 | import java.time.LocalTime; | 27 | import java.time.LocalTime; |
| ... | @@ -31,7 +30,6 @@ import java.util.Collections; | ... | @@ -31,7 +30,6 @@ import java.util.Collections; |
| 31 | import java.util.Date; | 30 | import java.util.Date; |
| 32 | import java.util.List; | 31 | import java.util.List; |
| 33 | import java.util.Map; | 32 | import java.util.Map; |
| 34 | -import java.util.Objects; | ||
| 35 | import java.util.function.Function; | 33 | import java.util.function.Function; |
| 36 | import java.util.stream.Collectors; | 34 | import java.util.stream.Collectors; |
| 37 | 35 | ||
| ... | @@ -109,16 +107,18 @@ public class MealOrderServiceImpl implements MealOrderService { | ... | @@ -109,16 +107,18 @@ public class MealOrderServiceImpl implements MealOrderService { |
| 109 | throw ClientEndExceptions.IncorrectRequestValue.build(ErrorCodeEnum.GRADE_CLASS_NOT_FOUND); | 107 | throw ClientEndExceptions.IncorrectRequestValue.build(ErrorCodeEnum.GRADE_CLASS_NOT_FOUND); |
| 110 | } | 108 | } |
| 111 | 109 | ||
| 112 | - // 5. 校验停餐记录(学校/班级/学生三个维度最新记录并集) | 110 | + // 5. 校验停餐记录(学校/班级/学生三个维度最新记录的多段并集区间) |
| 113 | - final var unionWindow = suspensionService.getSuspensionUnionWindowForDiner( | 111 | + final long nowMs = System.currentTimeMillis(); |
| 112 | + final var intervals = suspensionService.getSuspensionIntervalsForDiner( | ||
| 114 | loginInfo.getEnterpriseId(), | 113 | loginInfo.getEnterpriseId(), |
| 115 | dinerById.getClientId(), | 114 | dinerById.getClientId(), |
| 116 | dinerById.getClassId(), | 115 | dinerById.getClassId(), |
| 117 | dinerById.getId() | 116 | dinerById.getId() |
| 118 | ); | 117 | ); |
| 119 | - if (unionWindow != null) { | 118 | + if (intervals != null && !intervals.isEmpty()) { |
| 120 | - final long nowMs = System.currentTimeMillis(); | 119 | + final boolean inSuspension = intervals.stream() |
| 121 | - if (nowMs >= unionWindow.startAt && nowMs <= unionWindow.endAt) { | 120 | + .anyMatch(i -> nowMs >= i.startAt && nowMs <= i.endAt); |
| 121 | + if (inSuspension) { | ||
| 122 | throw ClientEndExceptions.IncorrectRequestValue.build(ErrorCodeEnum.DINER_MEAL_SUSPENDED); | 122 | throw ClientEndExceptions.IncorrectRequestValue.build(ErrorCodeEnum.DINER_MEAL_SUSPENDED); |
| 123 | } | 123 | } |
| 124 | } | 124 | } |
| ... | @@ -247,32 +247,43 @@ public class MealOrderServiceImpl implements MealOrderService { | ... | @@ -247,32 +247,43 @@ public class MealOrderServiceImpl implements MealOrderService { |
| 247 | // 3. 获取所有订餐人信息 | 247 | // 3. 获取所有订餐人信息 |
| 248 | var diners = mealOrderServiceRpcClient.getDinersByIds(loginInfo.getEnterpriseId(), dinerIds); | 248 | var diners = mealOrderServiceRpcClient.getDinersByIds(loginInfo.getEnterpriseId(), dinerIds); |
| 249 | 249 | ||
| 250 | - // 4. 使用公共停餐服务:批量计算三维度(学校/班级/学生)“最新记录并集”的停餐窗口,避免逐个调用 | 250 | + // 4. 使用公共停餐服务:批量计算每个订餐人的多段停餐区间 |
| 251 | Map<Integer, SingleDinerRpcResponse> dinerIdToDiner = diners.stream() | 251 | Map<Integer, SingleDinerRpcResponse> dinerIdToDiner = diners.stream() |
| 252 | .collect(Collectors.toMap(SingleDinerRpcResponse::getId, Function.identity())); | 252 | .collect(Collectors.toMap(SingleDinerRpcResponse::getId, Function.identity())); |
| 253 | 253 | ||
| 254 | - Map<Integer, SuspensionService.SuspensionWindow> dinerIdToUnionWindow = | 254 | + Map<Integer, List<SuspensionService.SuspensionInterval>> dinerIdToIntervals = |
| 255 | - suspensionService.getSuspensionUnionWindowsForDiners(loginInfo.getEnterpriseId(), diners); | 255 | + suspensionService.getSuspensionIntervalsForDiners(loginInfo.getEnterpriseId(), diners); |
| 256 | 256 | ||
| 257 | - List<DinerMealSuspensionRecordVO> suspensionList = dinerIdToUnionWindow.entrySet().stream() | 257 | + List<DinerMealSuspensionRecordVO> result = new ArrayList<>(); |
| 258 | - .map(entry -> { | 258 | + for (Map.Entry<Integer, List<SuspensionService.SuspensionInterval>> entry : dinerIdToIntervals.entrySet()) { |
| 259 | - Integer dinerId = entry.getKey(); | 259 | + Integer dinerId = entry.getKey(); |
| 260 | - SuspensionService.SuspensionWindow unionWindow = entry.getValue(); | 260 | + SingleDinerRpcResponse dinerInfo = dinerIdToDiner.get(dinerId); |
| 261 | - SingleDinerRpcResponse dinerInfo = dinerIdToDiner.get(dinerId); | 261 | + if (dinerInfo == null) { |
| 262 | - return DinerMealSuspensionRecordVO.builder() | 262 | + continue; |
| 263 | - .enterpriseId(Long.valueOf(dinerInfo.getEnterpriseId())) | 263 | + } |
| 264 | - .clientId(Long.valueOf(dinerInfo.getClientId())) | 264 | + List<SuspensionService.SuspensionInterval> intervals = entry.getValue(); |
| 265 | - .classId(Long.valueOf(dinerInfo.getClassId())) | 265 | + if (intervals == null || intervals.isEmpty()) { |
| 266 | - .dinerId(Long.valueOf(dinerInfo.getId())) | 266 | + continue; |
| 267 | - .dinerName(dinerInfo.getName()) | 267 | + } |
| 268 | - .studentNo(dinerInfo.getStudentNo()) | 268 | + List<DinerMealSuspensionRecordVO.Interval> voIntervals = intervals.stream() |
| 269 | - .startAt(new Date(unionWindow.startAt)) | 269 | + .map(i -> DinerMealSuspensionRecordVO.Interval.builder() |
| 270 | - .endAt(new Date(unionWindow.endAt)) | 270 | + .startAt(new Date(i.startAt)) |
| 271 | + .endAt(new Date(i.endAt)) | ||
| 271 | .comment("") | 272 | .comment("") |
| 272 | - .build(); | 273 | + .build()) |
| 273 | - }) | 274 | + .toList(); |
| 274 | - .toList(); | 275 | + |
| 275 | - return suspensionList; | 276 | + result.add(DinerMealSuspensionRecordVO.builder() |
| 277 | + .enterpriseId(Long.valueOf(dinerInfo.getEnterpriseId())) | ||
| 278 | + .clientId(Long.valueOf(dinerInfo.getClientId())) | ||
| 279 | + .classId(Long.valueOf(dinerInfo.getClassId())) | ||
| 280 | + .dinerId(Long.valueOf(dinerInfo.getId())) | ||
| 281 | + .dinerName(dinerInfo.getName()) | ||
| 282 | + .studentNo(dinerInfo.getStudentNo()) | ||
| 283 | + .intervals(voIntervals) | ||
| 284 | + .build()); | ||
| 285 | + } | ||
| 286 | + return result; | ||
| 276 | } | 287 | } |
| 277 | 288 | ||
| 278 | /** | 289 | /** | ... | ... |
| ... | @@ -27,6 +27,7 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -27,6 +27,7 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 27 | public List<SingleDinerMealSuspensionRecordRpcResponse> getLatestSuspensionsForDiner(int enterpriseId, int clientId, int classId, int dinerId) { | 27 | public List<SingleDinerMealSuspensionRecordRpcResponse> getLatestSuspensionsForDiner(int enterpriseId, int clientId, int classId, int dinerId) { |
| 28 | // 汇总三种维度(学校/班级/学生)各自“最新一条”停餐记录 | 28 | // 汇总三种维度(学校/班级/学生)各自“最新一条”停餐记录 |
| 29 | List<SingleDinerMealSuspensionRecordRpcResponse> latestSuspensionRecords = new ArrayList<>(3); | 29 | List<SingleDinerMealSuspensionRecordRpcResponse> latestSuspensionRecords = new ArrayList<>(3); |
| 30 | + final long now = System.currentTimeMillis(); | ||
| 30 | 31 | ||
| 31 | // 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤 | 32 | // 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤 |
| 32 | List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | 33 | List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( |
| ... | @@ -34,7 +35,7 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -34,7 +35,7 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 34 | List.of(clientId), | 35 | List.of(clientId), |
| 35 | null, | 36 | null, |
| 36 | List.of(0) | 37 | List.of(0) |
| 37 | - ); | 38 | + ).stream().filter(r -> r.getEndAt() > now).toList(); |
| 38 | SingleDinerMealSuspensionRecordRpcResponse latestSchoolLevelRecord = pickLatest( | 39 | SingleDinerMealSuspensionRecordRpcResponse latestSchoolLevelRecord = pickLatest( |
| 39 | schoolLevelRecords, | 40 | schoolLevelRecords, |
| 40 | record -> record.getClientId() == clientId && record.getClassId() == 0 && record.getDinerId() == 0 | 41 | record -> record.getClientId() == clientId && record.getClassId() == 0 && record.getDinerId() == 0 |
| ... | @@ -49,7 +50,7 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -49,7 +50,7 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 49 | List.of(clientId), | 50 | List.of(clientId), |
| 50 | List.of(classId), | 51 | List.of(classId), |
| 51 | List.of(0) | 52 | List.of(0) |
| 52 | - ); | 53 | + ).stream().filter(r -> r.getEndAt() > now).toList(); |
| 53 | SingleDinerMealSuspensionRecordRpcResponse latestClassLevelRecord = pickLatest( | 54 | SingleDinerMealSuspensionRecordRpcResponse latestClassLevelRecord = pickLatest( |
| 54 | classLevelRecords, | 55 | classLevelRecords, |
| 55 | record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == 0 | 56 | record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == 0 |
| ... | @@ -64,7 +65,7 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -64,7 +65,7 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 64 | List.of(clientId), | 65 | List.of(clientId), |
| 65 | List.of(classId), | 66 | List.of(classId), |
| 66 | List.of(dinerId) | 67 | List.of(dinerId) |
| 67 | - ); | 68 | + ).stream().filter(r -> r.getEndAt() > now).toList(); |
| 68 | SingleDinerMealSuspensionRecordRpcResponse latestDinerLevelRecord = pickLatest( | 69 | SingleDinerMealSuspensionRecordRpcResponse latestDinerLevelRecord = pickLatest( |
| 69 | dinerLevelRecords, | 70 | dinerLevelRecords, |
| 70 | record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == dinerId | 71 | record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == dinerId |
| ... | @@ -106,6 +107,7 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -106,6 +107,7 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 106 | if (diners == null || diners.isEmpty()) { | 107 | if (diners == null || diners.isEmpty()) { |
| 107 | return dinerIdToWindow; | 108 | return dinerIdToWindow; |
| 108 | } | 109 | } |
| 110 | + final long now = System.currentTimeMillis(); | ||
| 109 | 111 | ||
| 110 | // 预构建索引集合 | 112 | // 预构建索引集合 |
| 111 | List<Integer> clientIds = diners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList(); | 113 | List<Integer> clientIds = diners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList(); |
| ... | @@ -114,11 +116,11 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -114,11 +116,11 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 114 | 116 | ||
| 115 | // 查询三类记录 | 117 | // 查询三类记录 |
| 116 | List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | 118 | List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( |
| 117 | - enterpriseId, clientIds, null, List.of(0)); | 119 | + enterpriseId, clientIds, null, List.of(0)).stream().filter(r -> r.getEndAt() > now).toList(); |
| 118 | List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | 120 | List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( |
| 119 | - enterpriseId, clientIds, classIds, List.of(0)); | 121 | + enterpriseId, clientIds, classIds, List.of(0)).stream().filter(r -> r.getEndAt() > now).toList(); |
| 120 | List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | 122 | List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( |
| 121 | - enterpriseId, clientIds, classIds, dinerIds); | 123 | + enterpriseId, clientIds, classIds, dinerIds).stream().filter(r -> r.getEndAt() > now).toList(); |
| 122 | 124 | ||
| 123 | // 学校维度:clientId -> latest | 125 | // 学校维度:clientId -> latest |
| 124 | Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() : | 126 | Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() : |
| ... | @@ -193,6 +195,93 @@ public class SuspensionServiceImpl implements SuspensionService { | ... | @@ -193,6 +195,93 @@ public class SuspensionServiceImpl implements SuspensionService { |
| 193 | 195 | ||
| 194 | return dinerIdToWindow; | 196 | return dinerIdToWindow; |
| 195 | } | 197 | } |
| 198 | + | ||
| 199 | + @Override | ||
| 200 | + public List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId) { | ||
| 201 | + Map<Integer, List<SuspensionInterval>> map = getSuspensionIntervalsForDiners(enterpriseId, List.of( | ||
| 202 | + SingleDinerRpcResponse.newBuilder() | ||
| 203 | + .setEnterpriseId(clientId) // 注意:此处仅为构造占位,真正需要的是 clientId/classId/dinerId | ||
| 204 | + .setClientId(clientId) | ||
| 205 | + .setClassId(classId) | ||
| 206 | + .setId(dinerId) | ||
| 207 | + .build() | ||
| 208 | + )); | ||
| 209 | + return map.getOrDefault(dinerId, List.of()); | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + @Override | ||
| 213 | + public Map<Integer, List<SuspensionInterval>> getSuspensionIntervalsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners) { | ||
| 214 | + Map<Integer, List<SuspensionInterval>> result = new HashMap<>(); | ||
| 215 | + if (diners == null || diners.isEmpty()) { | ||
| 216 | + return result; | ||
| 217 | + } | ||
| 218 | + final long now = System.currentTimeMillis(); | ||
| 219 | + List<Integer> clientIds = diners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList(); | ||
| 220 | + List<Integer> classIds = diners.stream().map(SingleDinerRpcResponse::getClassId).distinct().toList(); | ||
| 221 | + List<Integer> dinerIds = diners.stream().map(SingleDinerRpcResponse::getId).toList(); | ||
| 222 | + | ||
| 223 | + List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | ||
| 224 | + enterpriseId, clientIds, null, List.of(0)); | ||
| 225 | + List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | ||
| 226 | + enterpriseId, clientIds, classIds, List.of(0)); | ||
| 227 | + List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( | ||
| 228 | + enterpriseId, clientIds, classIds, dinerIds); | ||
| 229 | + | ||
| 230 | + Map<Integer, List<SingleDinerMealSuspensionRecordRpcResponse>> clientIdToSchool = schoolLevelRecords == null ? Map.of() : | ||
| 231 | + schoolLevelRecords.stream() | ||
| 232 | + .filter(Objects::nonNull) | ||
| 233 | + .filter(r -> r.getClassId() == 0 && r.getDinerId() == 0) | ||
| 234 | + .collect(Collectors.groupingBy(SingleDinerMealSuspensionRecordRpcResponse::getClientId)); | ||
| 235 | + | ||
| 236 | + Map<String, List<SingleDinerMealSuspensionRecordRpcResponse>> clientClassToRecords = classLevelRecords == null ? Map.of() : | ||
| 237 | + classLevelRecords.stream() | ||
| 238 | + .filter(Objects::nonNull) | ||
| 239 | + .filter(r -> r.getDinerId() == 0) | ||
| 240 | + .collect(Collectors.groupingBy(r -> r.getClientId() + "#" + r.getClassId())); | ||
| 241 | + | ||
| 242 | + Map<Integer, List<SingleDinerMealSuspensionRecordRpcResponse>> dinerIdToRecords = dinerLevelRecords == null ? Map.of() : | ||
| 243 | + dinerLevelRecords.stream() | ||
| 244 | + .filter(Objects::nonNull) | ||
| 245 | + .collect(Collectors.groupingBy(SingleDinerMealSuspensionRecordRpcResponse::getDinerId)); | ||
| 246 | + | ||
| 247 | + for (SingleDinerRpcResponse diner : diners) { | ||
| 248 | + List<SuspensionInterval> intervals = new ArrayList<>(); | ||
| 249 | + | ||
| 250 | + List<SingleDinerMealSuspensionRecordRpcResponse> schoolRecs = clientIdToSchool.getOrDefault(diner.getClientId(), List.of()); | ||
| 251 | + List<SingleDinerMealSuspensionRecordRpcResponse> classRecs = clientClassToRecords.getOrDefault(diner.getClientId() + "#" + diner.getClassId(), List.of()); | ||
| 252 | + List<SingleDinerMealSuspensionRecordRpcResponse> dinerRecs = dinerIdToRecords.getOrDefault(diner.getId(), List.of()); | ||
| 253 | + | ||
| 254 | + for (SingleDinerMealSuspensionRecordRpcResponse rec : schoolRecs) { | ||
| 255 | + if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); | ||
| 256 | + } | ||
| 257 | + for (SingleDinerMealSuspensionRecordRpcResponse rec : classRecs) { | ||
| 258 | + if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); | ||
| 259 | + } | ||
| 260 | + for (SingleDinerMealSuspensionRecordRpcResponse rec : dinerRecs) { | ||
| 261 | + if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + // 归并并集:合并重叠区间、保留间隙 | ||
| 265 | + intervals.sort(Comparator.comparingLong(i -> i.startAt)); | ||
| 266 | + List<SuspensionInterval> merged = new ArrayList<>(); | ||
| 267 | + for (SuspensionInterval interval : intervals) { | ||
| 268 | + if (merged.isEmpty() || interval.startAt > merged.get(merged.size() - 1).endAt) { | ||
| 269 | + merged.add(new SuspensionInterval(interval.startAt, interval.endAt)); | ||
| 270 | + } else { | ||
| 271 | + SuspensionInterval last = merged.get(merged.size() - 1); | ||
| 272 | + long newEnd = Math.max(last.endAt, interval.endAt); | ||
| 273 | + merged.set(merged.size() - 1, new SuspensionInterval(last.startAt, newEnd)); | ||
| 274 | + } | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + if (!merged.isEmpty()) { | ||
| 278 | + result.put(diner.getId(), merged); | ||
| 279 | + } | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + return result; | ||
| 283 | + } | ||
| 284 | + | ||
| 196 | @Nullable | 285 | @Nullable |
| 197 | private SingleDinerMealSuspensionRecordRpcResponse pickLatest(List<SingleDinerMealSuspensionRecordRpcResponse> records, | 286 | private SingleDinerMealSuspensionRecordRpcResponse pickLatest(List<SingleDinerMealSuspensionRecordRpcResponse> records, |
| 198 | java.util.function.Predicate<SingleDinerMealSuspensionRecordRpcResponse> predicate) { | 287 | java.util.function.Predicate<SingleDinerMealSuspensionRecordRpcResponse> predicate) { | ... | ... |
-
Please register or login to post a comment