SuspensionServiceImpl.java 10.2 KB
package com.infoloop.tianting.service.impl;

import com.infoloop.tianting.mealorderservice.SingleDinerMealSuspensionRecordRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
import com.infoloop.tianting.service.SuspensionService;
import com.infoloop.tianting.service.client.MealOrderServiceRpcClient;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.HashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class SuspensionServiceImpl implements SuspensionService {

    private final MealOrderServiceRpcClient mealOrderServiceRpcClient;

    @Override
    public List<SingleDinerMealSuspensionRecordRpcResponse> getLatestSuspensionsForDiner(int enterpriseId, int clientId, int classId, int dinerId) {
        // 汇总三种维度(学校/班级/学生)各自“最新一条”停餐记录
        List<SingleDinerMealSuspensionRecordRpcResponse> latestSuspensionRecords = new ArrayList<>(3);

        // 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤
        List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId,
                List.of(clientId),
                null,
                List.of(0)
        );
        SingleDinerMealSuspensionRecordRpcResponse latestSchoolLevelRecord = pickLatest(
                schoolLevelRecords,
                record -> record.getClientId() == clientId && record.getClassId() == 0 && record.getDinerId() == 0
        );
        if (latestSchoolLevelRecord != null) {
            latestSuspensionRecords.add(latestSchoolLevelRecord);
        }

        // 班级维度:dinerId == 0,按 clientId + classId 过滤
        List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId,
                List.of(clientId),
                List.of(classId),
                List.of(0)
        );
        SingleDinerMealSuspensionRecordRpcResponse latestClassLevelRecord = pickLatest(
                classLevelRecords,
                record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == 0
        );
        if (latestClassLevelRecord != null) {
            latestSuspensionRecords.add(latestClassLevelRecord);
        }

        // 学生维度:三个 id 都不为 0
        List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId,
                List.of(clientId),
                List.of(classId),
                List.of(dinerId)
        );
        SingleDinerMealSuspensionRecordRpcResponse latestDinerLevelRecord = pickLatest(
                dinerLevelRecords,
                record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == dinerId
        );
        if (latestDinerLevelRecord != null) {
            latestSuspensionRecords.add(latestDinerLevelRecord);
        }

        return latestSuspensionRecords;
    }

    @Override
    public SuspensionWindow getSuspensionUnionWindowForDiner(int enterpriseId, int clientId, int classId, int dinerId) {
        // 取最新记录集合并生成并集窗口:\[min(startAt), max(endAt)\]
        List<SingleDinerMealSuspensionRecordRpcResponse> latestSuspensions = getLatestSuspensionsForDiner(enterpriseId, clientId, classId, dinerId);
        if (latestSuspensions == null || latestSuspensions.isEmpty()) {
            return null;
        }
        long minimalStartEpochMillis = Long.MAX_VALUE;
        long maximalEndEpochMillis = Long.MIN_VALUE;
        for (SingleDinerMealSuspensionRecordRpcResponse record : latestSuspensions) {
            if (record.getStartAt() < minimalStartEpochMillis) {
                minimalStartEpochMillis = record.getStartAt();
            }
            if (record.getEndAt() > maximalEndEpochMillis) {
                maximalEndEpochMillis = record.getEndAt();
            }
        }
        if (minimalStartEpochMillis == Long.MAX_VALUE || maximalEndEpochMillis == Long.MIN_VALUE) {
            return null;
        }
        return new SuspensionWindow(minimalStartEpochMillis, maximalEndEpochMillis);
    }

    @Override
    public Map<Integer, SuspensionWindow> getSuspensionUnionWindowsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners) {
        // 基于输入 diners 一次性拉取三类停餐记录,再按 diner 维度聚合出最新记录并计算并集窗口
        Map<Integer, SuspensionWindow> dinerIdToWindow = new HashMap<>();
        if (diners == null || diners.isEmpty()) {
            return dinerIdToWindow;
        }

        // 预构建索引集合
        List<Integer> clientIds = diners.stream().map(SingleDinerRpcResponse::getClientId).distinct().toList();
        List<Integer> classIds = diners.stream().map(SingleDinerRpcResponse::getClassId).distinct().toList();
        List<Integer> dinerIds = diners.stream().map(SingleDinerRpcResponse::getId).toList();

        // 查询三类记录
        List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId, clientIds, null, List.of(0));
        List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId, clientIds, classIds, List.of(0));
        List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
                enterpriseId, clientIds, classIds, dinerIds);

        // 学校维度:clientId -> latest
        Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() :
                schoolLevelRecords.stream()
                        .filter(Objects::nonNull)
                        .filter(r -> r.getClassId() == 0 && r.getDinerId() == 0)
                        .collect(Collectors.toMap(
                                SingleDinerMealSuspensionRecordRpcResponse::getClientId,
                                Function.identity(),
                                (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
                        ));

        // 班级维度:clientId+classId -> latest(使用合成 key)
        Map<String, SingleDinerMealSuspensionRecordRpcResponse> clientClassToLatest = classLevelRecords == null ? Map.of() :
                classLevelRecords.stream()
                        .filter(Objects::nonNull)
                        .filter(r -> r.getDinerId() == 0)
                        .collect(Collectors.toMap(
                                r -> r.getClientId() + "#" + r.getClassId(),
                                Function.identity(),
                                (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
                        ));

        // 学生维度:dinerId -> latest
        Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> dinerIdToLatest = dinerLevelRecords == null ? Map.of() :
                dinerLevelRecords.stream()
                        .filter(Objects::nonNull)
                        .collect(Collectors.toMap(
                                SingleDinerMealSuspensionRecordRpcResponse::getDinerId,
                                Function.identity(),
                                (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
                        ));

        // 计算每个 diner 的并集窗口
        for (SingleDinerRpcResponse diner : diners) {
            SingleDinerMealSuspensionRecordRpcResponse latestSchool = clientIdToLatestSchool.get(diner.getClientId());
            SingleDinerMealSuspensionRecordRpcResponse latestClass = clientClassToLatest.get(diner.getClientId() + "#" + diner.getClassId());
            SingleDinerMealSuspensionRecordRpcResponse latestDiner = dinerIdToLatest.get(diner.getId());

            long minStart = Long.MAX_VALUE;
            long maxEnd = Long.MIN_VALUE;

            if (latestSchool != null) {
                if (latestSchool.getStartAt() < minStart) {
                    minStart = latestSchool.getStartAt();
                }
                if (latestSchool.getEndAt() > maxEnd) {
                    maxEnd = latestSchool.getEndAt();
                }
            }
            if (latestClass != null) {
                if (latestClass.getStartAt() < minStart) {
                    minStart = latestClass.getStartAt();
                }
                if (latestClass.getEndAt() > maxEnd) {
                    maxEnd = latestClass.getEndAt();
                }
            }
            if (latestDiner != null) {
                if (latestDiner.getStartAt() < minStart) {
                    minStart = latestDiner.getStartAt();
                }
                if (latestDiner.getEndAt() > maxEnd) {
                    maxEnd = latestDiner.getEndAt();
                }
            }

            if (minStart != Long.MAX_VALUE && maxEnd != Long.MIN_VALUE) {
                dinerIdToWindow.put(diner.getId(), new SuspensionWindow(minStart, maxEnd));
            }
        }

        return dinerIdToWindow;
    }
    @Nullable
    private SingleDinerMealSuspensionRecordRpcResponse pickLatest(List<SingleDinerMealSuspensionRecordRpcResponse> records,
                                                                  java.util.function.Predicate<SingleDinerMealSuspensionRecordRpcResponse> predicate) {
        // 在满足谓词条件的集合中,按 createdAt 取最新的一条
        if (records == null || records.isEmpty()) {
            return null;
        }
        return records.stream()
                .filter(Objects::nonNull)
                .filter(predicate)
                .max(Comparator.comparingLong(SingleDinerMealSuspensionRecordRpcResponse::getCreatedAt))
                .orElse(null);
    }
}