Skip to content

如何在实际项目中正确使用Optional类?

约 5253 字大约 18 分钟

2025-09-23

在实际项目中,Optional 类的核心价值在于显式表达 "值可能缺失" 的语义,并通过函数式编程简化空值处理逻辑,减少 NullPointerException。但如果使用不当,反而会让代码变得冗余晦涩。以下是实际项目中的正确使用指南和最佳实践:

一、核心原则:明确 Optional 的设计定位

Optional是"方法返回值的包装器",用于明确告知调用者:"此方法可能返回空值,请妥善处理"。

禁止将其用作:

  • 方法参数(会增加调用方复杂度)
  • 类的成员变量(破坏序列化,且无实际意义)
  • 集合元素(集合本身已有 isEmpty() 等方法判断空)

二、正确使用场景与示例

1. 作为方法返回值,替代直接返回 null

当方法可能返回 null 时(如数据库查询、远程调用),用 Optional 包装返回值,强制调用者处理空值情况。

反例(传统方式)

// 问题:调用者可能忘记判空,直接调用user.getName()导致NPE
public User findUserById(Long id) {
    return userMapper.selectById(id); // 可能返回null
}

正例(使用 Optional)

// 明确告知调用者:结果可能为空
public Optional<User> findUserById(Long id) {
    return Optional.ofNullable(userMapper.selectById(id));
}

// 调用方必须显式处理空值
Optional<User> userOpt = userService.findUserById(1L);
String userName = userOpt.map(User::getName) // 若存在则取名字
                        .orElse("未知用户"); // 若不存在则用默认值

2. 用链式调用替代嵌套的 if-else

Optionalmap()flatMap()filter() 等方法支持链式调用,可将多层空值判断简化为流畅的函数式操作。

场景:获取 "用户的地址的城市名",需层层判空(用户可能为 null,地址可能为 null)。

反例(嵌套判空)

String city = "未知城市";
User user = userService.findUserById(1L);
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        String tempCity = address.getCity();
        if (tempCity != null) {
            city = tempCity;
        }
    }
}

正例(Optional 链式调用)

String city = userService.findUserById(1L) // 返回Optional<User>
    .map(User::getAddress) // 若用户存在,提取地址(Optional<Address>)
    .map(Address::getCity) // 若地址存在,提取城市(Optional<String>)
    .orElse("未知城市"); // 任意环节为空则用默认值

3. 用 ifPresent() 替代 "判空后执行操作"

当需要 "值存在时执行某个操作,否则不做处理" 时,用 ifPresent() 避免显式判空。

反例

User user = userService.findUserById(1L);
if (user != null) {
    log.info("用户存在:{}", user.getName());
    sendWelcomeMsg(user); // 发送欢迎消息
}

正例

userService.findUserById(1L)
    .ifPresent(user -> { // 仅当用户存在时执行
        log.info("用户存在:{}", user.getName());
        sendWelcomeMsg(user);
    });

4. 用 orElseThrow() 处理 "必须存在" 的场景

当业务要求 "值必须存在,否则抛异常" 时(如根据 ID 查询的记录必须存在),用 orElseThrow() 简化异常抛出逻辑。

反例

User user = userService.findUserById(1L);
if (user == null) {
    throw new BizException("用户不存在,ID:1");
}

正例

User user = userService.findUserById(1L)
    .orElseThrow(() -> new BizException("用户不存在,ID:1")); // 为空时直接抛异常

5. 结合集合 / Stream 处理批量空值

在处理集合时,用 Optional 过滤空元素,或转换为 Stream 进行批量处理。 示例

List<String> userNames = userList.stream()
    // 将每个元素包装为Optional,过滤空值
    .map(Optional::ofNullable)
    .filter(Optional::isPresent) // 保留非空元素
    .map(Optional::get) // 提取值
    .map(User::getName) // 提取用户名
    .collect(Collectors.toList());

三、常见反模式(禁止使用)

1. 用 isPresent() + get() 替代 null 检查

这是最常见的错误用法,完全失去了 Optional 的意义,与传统 if (obj != null) 没有区别。 反例

Optional<User> userOpt = userService.findUserById(1L);
if (userOpt.isPresent()) { // 等价于 if (user != null)
    User user = userOpt.get(); // 等价于直接使用user
    // ...
}

2. 用 Optional 包装已知非空的值

如果确定值一定非空,无需用 Optional 包装(徒增代码复杂度)。

反例

// 已知"admin"非空,无需包装
Optional<String> adminOpt = Optional.of("admin"); 
String admin = adminOpt.get();

3. 过度链式调用导致可读性下降

虽然链式调用很优雅,但过长的链会导致逻辑晦涩,建议拆分复杂逻辑。

反例

// 过长的链式调用,难以调试和维护
String result = service.findObj()
    .map(Obj::getA)
    .flatMap(A::getB)
    .filter(B::isValid)
    .map(B::getC)
    .orElseThrow(...);

优化

// 拆分步骤,提高可读性
Optional<Obj> objOpt = service.findObj();
Optional<A> aOpt = objOpt.map(Obj::getA);
Optional<B> bOpt = aOpt.flatMap(A::getB);
Optional<B> validBOpt = bOpt.filter(B::isValid);

String result = validBOpt.map(B::getC)
                        .orElseThrow(...);

4. 误用 orElse() 导致不必要的对象创建

orElse() 的参数无论值是否存在都会被执行,若默认值创建成本高(如数据库查询、复杂对象),应改用 orElseGet()(延迟执行)。

反例

// 即使user存在,getDefaultUser()也会被调用(浪费资源)
User user = userService.findUserById(1L)
    .orElse(getDefaultUser()); // getDefaultUser()是耗时操作

正例

// 仅当user不存在时,才调用getDefaultUser()
User user = userService.findUserById(1L)
    .orElseGet(() -> getDefaultUser()); // 延迟执行

四、总结:Optional 的核心价值

  1. 显式化空值语义:通过方法返回值明确告知 "可能为空",强制调用者处理,减少 NPE。
  2. 简化代码逻辑:用函数式链式调用替代嵌套的 if-else,让代码更流畅。
  3. 提高可读性:通过 map()orElse() 等方法,让空值处理逻辑一目了然。

记住:Optional 是 "工具" 而非 "银弹",合理使用能提升代码质量,过度或错误使用则会适得其反。

五、完整示例代码

/**
 * 投标人多轮报价详情查询服务实现类
 * 核心功能:查询指定标段的投标人报价详情,支持当前轮次与历史轮次查询,
 * 处理已报价/未报价两种场景,并封装前端所需的VO对象
 */
@Service
public class BidderQuoteDetailServiceImpl implements BidderQuoteDetailService {

    @Autowired
    private MultiQuoteService multiQuoteService;
    
    @Autowired
    private MultipleQuoteBidderMapper multipleQuoteBidderMapper;
    
    @Autowired
    private RemotePurchasePackageService remotePurchasePackageService;

    /**
     * 获取投标人多轮报价详情的主入口方法
     * 整体流程:参数校验→提取标段ID→查询最新报价→处理报价结果→返回封装后的VO
     * 采用Optional链式调用实现空值安全处理,确保任何环节为空都不会抛出NPE
     *
     * @param query 报价查询参数对象,包含:
     *              - packageId:标段ID(必填,定位查询的标段)
     *              - bidderId:投标人ID(必填,定位查询的投标人)
     * @return BidderMultipleQuoteVO 封装了报价详情的VO对象,包含:
     *         - 标段基础信息(编码、名称)
     *         - 报价记录(当前轮次或历史轮次)
     *         - 可报价状态(canQuote:是否仍可提交报价)
     *         - 报价表单详情(quoteForm:解析后的报价项列表)
     *         注:即使无任何数据,也返回非null的VO对象
     */
    @Override
    public BidderMultipleQuoteVO getQuoteDetail(BidderMultipleQuoteQuery query) {
        // 初始化返回结果VO,确保始终返回非null对象
        BidderMultipleQuoteVO resultVO = new BidderMultipleQuoteVO();
        
        // Optional链式调用处理空值安全流程:
        // 1. 检查query是否为null → 2. 提取packageId → 3. 查询该标段最新报价 → 4. 处理报价结果
        Optional.ofNullable(query)
                .map(BidderMultipleQuoteQuery::getPackageId)  // 提取标段ID(Function函数式接口)
                .flatMap(this::getLastQuote)                  // 查询最新报价记录(返回Optional<MultipleQuote>)
                .ifPresent(handleQuoteResult(resultVO, query));  // 处理报价结果(Consumer函数式接口)
        
        return resultVO;
    }
    
    /**
     * 构建报价结果处理器(核心业务流程封装)
     * 封装"设置标段信息→查询投标人报价→分支处理已报价/未报价状态"的完整逻辑
     * 返回Consumer函数便于在Optional链式调用中使用,实现逻辑解耦
     *
     * @param resultVO 待填充数据的结果VO对象
     * @param query 查询参数对象,提供标段ID、投标人ID等条件
     * @return Consumer<MultipleQuote> 接收最新报价记录的消费者函数:
     *         当存在最新报价记录时,执行此函数完成数据封装
     */
    private Consumer<MultipleQuote> handleQuoteResult(BidderMultipleQuoteVO resultVO, BidderMultipleQuoteQuery query) {
        // Lambda表达式实现Consumer接口的accept方法
        return lastQuote -> {
            // 步骤1:设置标段基础信息(编码、名称等)到结果VO
            setPackageInfo(resultVO, query.getPackageId(), lastQuote);
            
            // 步骤2:查询该投标人在当前轮次的报价记录
            Optional<MultipleQuoteBidder> quoteBidderOpt = findQuoteBidder(query, lastQuote);
            
            // 步骤3:根据是否存在报价记录分支处理
            if (quoteBidderOpt.isPresent()) {
                // 已报价场景:应用已报价处理器
                handleQuotedConsumer(resultVO, lastQuote).accept(quoteBidderOpt.get());
            } else {
                // 未报价场景:执行未报价处理器
                handleUnquotedRunnable(resultVO, query, lastQuote).run();
            }
        };
    }
    
    /**
     * 处理已报价状态的Consumer函数
     * 封装已报价场景下的数据处理逻辑:属性复制、报价JSON解析、可报价状态判断
     *
     * @param resultVO 待填充的结果VO对象
     * @param lastQuote 最新报价记录,提供报价截止时间等信息
     * @return Consumer<MultipleQuoteBidder> 接收投标人报价记录的消费者函数:
     *         执行时将报价信息填充到结果VO,并判断是否仍可修改报价
     */
    private Consumer<MultipleQuoteBidder> handleQuotedConsumer(BidderMultipleQuoteVO resultVO, MultipleQuote lastQuote) {
        // Lambda表达式实现Consumer接口
        return quoteBidder -> {
            // 复制报价记录的基础属性(id、totalPrice、quoteTime等)到结果VO
            BeanUtil.copyProperties(quoteBidder, resultVO);
            
            // 解析报价JSON字符串为表单VO列表(若存在)
            Optional.ofNullable(quoteBidder.getQuoteJson())
                    .map(parseQuoteJson())  // 应用JSON解析函数(Function接口)
                    .ifPresent(resultVO::setQuoteForm);  // 解析成功则设置到VO
            
            // 判断是否仍可报价:当前时间在截止时间前 且 报价状态为"正常"
            Optional.ofNullable(lastQuote.getQuoteEndTime())
                    .filter(endTime -> LocalDateTime.now().isBefore(endTime)  // 时间条件:未到截止时间
                            && BidderConstant.QUOTE_STATUS_NORMAL.equals(quoteBidder.getQuoteStatus()))  // 状态条件:报价有效
                    .ifPresent(endTime -> resultVO.setCanQuote(true));  // 满足条件则设置可报价
        };
    }
    
    /**
     * 处理未报价状态的Runnable函数
     * 封装未报价场景下的数据处理逻辑:查询历史报价参考、判断是否被邀请、设置可报价状态
     *
     * @param resultVO 待填充的结果VO对象
     * @param query 查询参数对象
     * @param lastQuote 最新报价记录,提供当前轮次ID和截止时间
     * @return Runnable 无参数无返回值的函数:执行时处理未报价场景的数据封装
     */
    private Runnable handleUnquotedRunnable(BidderMultipleQuoteVO resultVO,
                                            BidderMultipleQuoteQuery query, MultipleQuote lastQuote) {
        // Lambda表达式实现Runnable接口的run方法
        return () -> {
            // 子流程1:查询历史轮次报价作为参考(若存在)
            findHistoryQuoteBidders(query, lastQuote)
                    .ifPresent(historyBidderQuote -> {
                        // 复制历史报价属性到结果VO
                        BeanUtil.copyProperties(historyBidderQuote, resultVO);
                        // 解析历史报价JSON为表单VO
                        Optional.ofNullable(historyBidderQuote.getQuoteJson())
                                .map(parseQuoteJson())
                                .ifPresent(resultVO::setQuoteForm);
                    });
            
            // 子流程2:检查该投标人是否被邀请参与当前轮次报价
            Optional<MultipleQuoteBidder> invitedBidder = queryQuoteBidder(
                    lastQuote.getId(), 
                    query.getBidderId(), 
                    query.getPackageId(), 
                    null  // 不限制状态,仅判断是否被邀请
            );
            
            // 子流程3:若已被邀请且在截止时间前,则设置可报价状态
            if (invitedBidder.isPresent()) {
                Optional.ofNullable(lastQuote.getQuoteEndTime())
                        .filter(endTime -> LocalDateTime.now().isBefore(endTime))  // 未到截止时间
                        .ifPresent(endTime -> resultVO.setCanQuote(true));  // 设置可报价
            }
        };
    }
    
    /**
     * 解析报价JSON的Function函数
     * 封装JSON字符串到报价表单VO列表的转换逻辑,便于在map操作中复用
     *
     * @return Function<String, List<MultipleQuoteFormVO>> 函数式接口实现:
     *         - 输入:报价信息JSON字符串(格式为数组)
     *         - 输出:解析后的MultipleQuoteFormVO对象列表(前端展示的报价项详情)
     */
    private Function<String, List<MultipleQuoteFormVO>> parseQuoteJson() {
        // Lambda表达式实现Function接口的apply方法
        return json -> JSON.parseArray(json, MultipleQuoteFormVO.class);
    }
    
    /**
     * 设置标段信息到结果VO
     * 整合标段基础信息与最新报价记录,转换为MultipleQuoteVO并关联到结果对象
     *
     * @param resultVO 待填充的结果VO对象
     * @param packageId 标段ID,用于查询标段基础信息
     * @param lastQuote 最新报价记录,提供报价轮次ID、截止时间等信息
     */
    private void setPackageInfo(BidderMultipleQuoteVO resultVO, Long packageId, MultipleQuote lastQuote) {
        // 链式处理:查询标段信息→转换为VO→设置到结果
        Optional.ofNullable(packageId)
                .map(this::getPackageBaseInfo)  // 调用远程服务查询标段基础信息
                .ifPresent(packageVO -> {
                    // 自定义对象转换:将MultipleQuote转换为MultipleQuoteVO
                    MultipleQuoteVO multipleQuoteVO = ModelUtil.buildView(
                            lastQuote, 
                            MultipleQuoteVO.class, 
                            (source, target) -> {  // Lambda表达式定义转换规则
                                target.setPackageCode(packageVO.getPackageCode());  // 填充标段编码
                                target.setPackageName(packageVO.getPackageName());  // 填充标段名称
                                target.setQuoteId(source.getId());  // 设置报价轮次ID(关联主记录)
                            }
                    );
                    // 将标段+报价轮次信息设置到结果VO
                    resultVO.setMultipleQuoteInfo(multipleQuoteVO);
                });
    }
    
    /**
     * 查找投标人报价(当前轮次)
     * 封装当前轮次报价的查询逻辑,仅查询当前最新轮次的报价记录
     *
     * @param query 查询参数对象
     * @param lastQuote 最新报价记录,提供当前轮次ID
     * @return Optional<MultipleQuoteBidder> 包装了当前轮次报价记录的Optional:
     *         - 存在报价:返回包含记录的Optional
     *         - 无报价:返回空Optional
     */
    private Optional<MultipleQuoteBidder> findQuoteBidder(BidderMultipleQuoteQuery query, MultipleQuote lastQuote) {
        // 直接查询当前轮次报价(复用queryCurrentRoundQuote方法)
        return queryCurrentRoundQuote(query, lastQuote);
    }
    
    /**
     * 查询当前轮次报价
     * 通过最新报价记录的ID,查询该投标人在当前轮次的有效报价(状态为"已报价")
     *
     * @param query 查询参数对象
     * @param lastQuote 最新报价记录,提供当前轮次的主记录ID
     * @return Optional<MultipleQuoteBidder> 包装了当前轮次有效报价记录的Optional
     */
    private Optional<MultipleQuoteBidder> queryCurrentRoundQuote(BidderMultipleQuoteQuery query, MultipleQuote lastQuote) {
        // 链式处理:从最新报价记录提取ID→查询对应投标人的有效报价
        return Optional.ofNullable(lastQuote)
                .map(MultipleQuote::getId)  // 提取当前轮次报价主记录ID
                .flatMap(quoteMainId -> queryQuoteBidder(
                        quoteMainId, 
                        query.getBidderId(), 
                        query.getPackageId(), 
                        BidderConstant.QUOTE_STATUS_QUOTE  // 仅查询"已报价"状态的记录
                ));
    }
    
    /**
     * 查询历史轮次报价
     * 从当前轮次向前迭代查询(当前轮次-1 → 1),包含死循环防护和业务规则校验
     * 仅查询非首轮报价的历史记录(业务规则:首轮无历史可查)
     *
     * @param query 查询参数对象
     * @param lastQuote 最新报价记录,提供当前轮次信息(确定查询范围)
     * @return Optional<MultipleQuoteBidder> 包装了历史轮次报价记录的Optional:
     *         - 找到有效记录:返回包含记录的Optional
     *         - 未找到或不符合条件:返回空Optional
     */
    private Optional<MultipleQuoteBidder> findHistoryQuoteBidders(BidderMultipleQuoteQuery query, MultipleQuote lastQuote) {
        // 流程拆解:过滤首轮报价→迭代查询历史轮次→返回结果
        return Optional.ofNullable(lastQuote)
                .filter(this::isNotFirstRound)  // 过滤首轮报价(业务规则:首轮不查历史)
                .flatMap(quote -> {
                    int currentRound = quote.getRoundNum();  // 当前轮次(查询起点)
                    final int maxRounds = currentRound - 1;  // 最大迭代次数(防止死循环)
                    int iterations = 0;  // 迭代计数器(控制最大次数)
                    MultipleQuoteBidder quoteBidder = null;  // 存储查询到的报价记录
                    
                    // 循环查询条件:未找到报价 且 轮次>1 且 未超过最大迭代次数
                    while (Objects.isNull(quoteBidder) && currentRound > 1 && iterations < maxRounds) {
                        currentRound--;  // 轮次递减(查询上一轮)
                        iterations++;    // 计数器递增
                        
                        // 查询当前轮次报价,若存在则赋值,否则保持null
                        quoteBidder = queryHistoryRoundQuote(query, currentRound)
                                .orElse(quoteBidder);
                    }
                    
                    // 分支1:循环中找到报价记录,直接返回
                    if (Objects.nonNull(quoteBidder)) {
                        return Optional.of(quoteBidder);
                    }
                    
                    // 分支2:轮次已减到1,查询第一轮报价
                    if (currentRound == 1) {
                        return queryFirstRoundQuote(query);
                    }
                    
                    // 分支3:所有轮次查询无果,返回空Optional
                    return Optional.empty();
                });
    }
    
    /**
     * 查询指定历史轮次的报价
     * 封装单轮历史报价的查询逻辑:先查该轮次主记录→再查对应投标人的有效报价
     *
     * @param query 查询参数对象
     * @param round 要查询的历史轮次(正整数)
     * @return Optional<MultipleQuoteBidder> 包装了该轮次有效报价记录的Optional
     */
    private Optional<MultipleQuoteBidder> queryHistoryRoundQuote(BidderMultipleQuoteQuery query, int round) {
        // 链式处理:查询该轮次主记录→提取主记录ID→查询对应投标人的有效报价
        return Optional.ofNullable(multiQuoteService.queryLatestMultipleQuote(
                        query.getPackageId(), round, BidderConstant.QUOTE_ROUND_CANCEL))
                .map(MultipleQuote::getId)  // 提取该轮次报价主记录ID
                .flatMap(quoteMainId -> queryQuoteBidder(
                        quoteMainId, 
                        query.getBidderId(), 
                        query.getPackageId(), 
                        BidderConstant.QUOTE_STATUS_QUOTE  // 仅查询"已报价"状态
                ));
    }
    
    /**
     * 查询第一轮报价
     * 专门处理第一轮报价的查询逻辑(作为历史查询的最后尝试)
     * 注:首轮报价无需手动结束,因此"已报价"状态即有效
     *
     * @param query 查询参数对象
     * @return Optional<MultipleQuoteBidder> 包装了第一轮有效报价记录的Optional
     */
    private Optional<MultipleQuoteBidder> queryFirstRoundQuote(BidderMultipleQuoteQuery query) {
        // 固定查询第1轮报价(复用单轮查询逻辑)
        return Optional.ofNullable(multiQuoteService.queryLatestMultipleQuote(
                        query.getPackageId(), 1, BidderConstant.QUOTE_ROUND_CANCEL))
                .map(MultipleQuote::getId)
                .flatMap(quoteMainId -> queryQuoteBidder(
                        quoteMainId, 
                        query.getBidderId(), 
                        query.getPackageId(), 
                        BidderConstant.QUOTE_STATUS_QUOTE
                ));
    }
    
    /**
     * 查找历史报价记录(用于未报价时的参考)
     * 从当前轮次-1开始向前查询,直到找到包含有效报价JSON的记录(非空)
     * 用于未报价场景下展示历史报价作为参考
     *
     * @param query 查询参数对象
     * @param lastQuote 最新报价记录,提供当前轮次信息
     * @return Optional<MultipleQuoteBidder> 包装了有效历史报价记录的Optional:
     *         - 找到有有效JSON的记录:返回包含记录的Optional
     *         - 未找到或无有效JSON:返回空Optional
     */
    private Optional<MultipleQuoteBidder> findHistoryQuoteBidder(BidderMultipleQuoteQuery query, MultipleQuote lastQuote) {
        // 流程拆解:计算起始轮次→迭代查询→过滤有效记录→返回结果
        return Optional.ofNullable(lastQuote)
                .map(quote -> quote.getRoundNum() - 1)  // 起始轮次:当前轮次-1
                .flatMap(roundNum -> {
                    MultipleQuoteBidder quoteBidderCur = null;  // 存储查询到的记录
                    final int maxRounds = roundNum;  // 最大迭代次数(防止死循环)
                    int iterations = 0;  // 迭代计数器
                    
                    // 循环条件:轮次≥1 且 未超过最大迭代次数
                    while (roundNum >= 1 && iterations < maxRounds) {
                        iterations++;  // 计数器递增
                        
                        // 查询当前轮次报价,并过滤出包含有效JSON的记录
                        quoteBidderCur = queryHistoryRoundQuote(query, roundNum)
                                .filter(bidder -> Objects.nonNull(bidder.getQuoteJson()))  // 有效JSON校验(非空)
                                .orElse(quoteBidderCur);
                        
                        // 找到有效记录则退出循环
                        if (ObjectUtil.isNotNull(quoteBidderCur)) {
                            break;
                        }
                        
                        roundNum--;  // 轮次递减
                    }
                    
                    // 仅当轮次有效(≥1)时返回记录,否则返回空
                    return Optional.ofNullable(roundNum >= 1 ? quoteBidderCur : null);
                });
    }
    
    /**
     * 通用报价查询方法
     * 根据报价主记录ID、投标人ID、标段ID查询唯一的报价记录,支持按状态过滤
     * 封装重复的查询条件,减少代码冗余
     *
     * @param quoteMainId 报价主记录ID(关联到具体轮次)
     * @param bidderId 投标人ID(查询的目标投标人)
     * @param packageId 标段ID(查询的目标标段)
     * @param quoteStatus 报价状态(可选,为null时不限制状态)
     * @return Optional<MultipleQuoteBidder> 包装了查询结果的Optional:
     *         - 存在匹配记录:返回包含记录的Optional
     *         - 无匹配记录:返回空Optional
     */
    private Optional<MultipleQuoteBidder> queryQuoteBidder(Long quoteMainId, Long bidderId, Long packageId, String quoteStatus) {
        // 构建查询条件:必选条件(主记录ID+投标人ID+标段ID)+ 可选条件(状态)
        return Optional.ofNullable(multipleQuoteBidderMapper.selectOne(
                Wrappers.<MultipleQuoteBidder>lambdaQuery()
                        .eq(MultipleQuoteBidder::getQuoteMainId, quoteMainId)
                        .eq(MultipleQuoteBidder::getBidderId, bidderId)
                        .eq(MultipleQuoteBidder::getPackageId, packageId)
                        .eq(StrUtil.isNotBlank(quoteStatus), MultipleQuoteBidder::getQuoteStatus, quoteStatus)  // 状态条件(可选)
        ));
    }
    
    /**
     * 获取最新报价记录
     * 查询指定标段的最新一轮报价主记录(用于确定当前轮次信息)
     *
     * @param packageId 标段ID
     * @return Optional<MultipleQuote> 包装了最新报价记录的Optional:
     *         - 存在报价记录:返回包含记录的Optional
     *         - 无报价记录:返回空Optional
     */
    private Optional<MultipleQuote> getLastQuote(Long packageId) {
        return Optional.ofNullable(multiQuoteService.getLastQuote(packageId));
    }
    
    /**
     * 获取标段基础信息
     * 远程查询标段的基础信息(编码、名称等),用于前端展示
     *
     * @param packageId 标段ID
     * @return PurchasePackageVO 标段基础信息VO,可能为null(查询失败时)
     */
    private PurchasePackageVO getPackageBaseInfo(Long packageId) {
        // 调用远程服务查询,并解析返回结果(封装远程调用的通用逻辑)
        return RemoteParseUtil.getData(remotePurchasePackageService.getPackageBaseInfo(packageId));
    }
    
    /**
     * 检查是否非首轮报价(业务规则校验)
     * 用于过滤首轮报价场景(业务规定:首轮报价无历史记录可查询)
     *
     * @param quote 报价记录,包含轮次信息
     * @return boolean 校验结果:
     *         - true:非首轮报价(可以查询历史记录)
     *         - false:首轮报价(禁止查询历史记录)
     */
    private boolean isNotFirstRound(MultipleQuote quote) {
        return !BidderConstant.FIRST_QUOTE_ROUND.equals(quote.getRoundNum());
    }


    // ------------------------------ 使用示例代码 ------------------------------
    /**
     * 投标人多轮报价详情查询的使用示例
     * 包含三种典型场景:已报价、未报价但有历史记录、未报价且无历史记录
     */
    public static class UsageExample {
        @Autowired
        private BidderQuoteDetailService quoteDetailService;

        /**
         * 场景1:投标人已提交当前轮次报价
         * 预期结果:返回当前报价信息,包含可修改状态(若在截止时间内)
         */
        public void testQuotedScenario() {
            // 1. 构建查询参数(标段ID=1001,投标人ID=2001)
            BidderMultipleQuoteQuery query = new BidderMultipleQuoteQuery();
            query.setPackageId(1001L);
            query.setBidderId(2001L);

            // 2. 调用查询方法
            BidderMultipleQuoteVO result = quoteDetailService.getQuoteDetail(query);

            // 3. 处理结果
            System.out.println("标段名称:" + result.getMultipleQuoteInfo().getPackageName());
            System.out.println("当前轮次:" + result.getMultipleQuoteInfo().getRoundNum());
            System.out.println("报价总金额:" + result.getTotalPrice());
            System.out.println("是否可修改报价:" + result.isCanQuote());  // 若在截止时间内则为true
            System.out.println("报价项数量:" + result.getQuoteForm().size());
        }

        /**
         * 场景2:投标人未提交当前轮次报价,但有历史轮次报价
         * 预期结果:返回历史报价信息作为参考,包含可报价状态(若已被邀请且在截止时间内)
         */
        public void testUnquotedWithHistoryScenario() {
            // 1. 构建查询参数(标段ID=1001,投标人ID=2002)
            BidderMultipleQuoteQuery query = new BidderMultipleQuoteQuery();
            query.setPackageId(1001L);
            query.setBidderId(2002L);

            // 2. 调用查询方法
            BidderMultipleQuoteVO result = quoteDetailService.getQuoteDetail(query);

            // 3. 处理结果
            System.out.println("当前轮次是否已报价:否(展示历史报价)");
            System.out.println("历史轮次:" + result.getRoundNum());  // 显示最近一次历史轮次
            System.out.println("历史报价金额:" + result.getTotalPrice());
            System.out.println("是否可提交报价:" + result.isCanQuote());  // 若被邀请且在截止时间内则为true
        }

        /**
         * 场景3:投标人未提交当前轮次报价,且无历史报价记录
         * 预期结果:仅返回标段和轮次信息,无报价数据,包含可报价状态
         */
        public void testUnquotedWithoutHistoryScenario() {
            // 1. 构建查询参数(标段ID=1001,投标人ID=2003)
            BidderMultipleQuoteQuery query = new BidderMultipleQuoteQuery();
            query.setPackageId(1001L);
            query.setBidderId(2003L);

            // 2. 调用查询方法
            BidderMultipleQuoteVO result = quoteDetailService.getQuoteDetail(query);

            // 3. 处理结果
            System.out.println("标段信息:" + result.getMultipleQuoteInfo().getPackageName());
            System.out.println("当前轮次:" + result.getMultipleQuoteInfo().getRoundNum());
            System.out.println("是否有历史报价:" + (result.getQuoteForm() == null ? "" : ""));
            System.out.println("是否可提交报价:" + result.isCanQuote());  // 取决于是否被邀请和时间
        }
    }
}

Power by VuePress & vuepress-theme-plume