dubbo_integration.md 9.4 KB

Dubbo 集成指南

概述

RuoYi-Cloud-Plus 使用 Apache Dubbo 3.X 作为 RPC 远程调用框架,替代传统的 Feign 调用。

核心概念

概念 说明
Provider 服务提供者,暴露服务
Consumer 服务消费者,调用服务
Interface 服务接口定义
Registry 注册中心(Nacos)

服务提供者(Provider)

1. 定义接口(ruoyi-api 模块)

package org.dromara.backstage.api;

import org.dromara.backstage.api.domain.dto.RemoteRoomDto;
import org.dromara.backstage.api.domain.dto.RemoteRoomQueryDto;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.TableDataInfo;

import java.util.List;

/**
 * 房间信息远程调用接口
 */
public interface RemotePtRoomService {
    
    /**
     * 根据ID查询房间
     */
    R<RemoteRoomDto> selectRoomById(Long roomId);
    
    /**
     * 查询房间列表
     */
    List<RemoteRoomDto> selectRoomList();
    
    /**
     * 分页查询房间列表
     */
    TableDataInfo<RemoteRoomDto> selectRoomPage(RemoteRoomQueryDto queryDto);
    
    /**
     * 新增房间
     */
    R<Void> insertRoom(RemoteRoomDto dto);
    
    /**
     * 修改房间
     */
    R<Void> updateRoom(RemoteRoomDto dto);
    
    /**
     * 删除房间
     */
    R<Void> deleteRoomByIds(Long[] ids);
}

2. 定义 DTO(ruoyi-api 模块)

package org.dromara.backstage.api.domain.dto;

import lombok.Data;

import java.io.Serializable;

/**
 * 房间信息DTO
 */
@Data
public class RemoteRoomDto implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    /** 房间ID */
    private Long roomId;
    
    /** 房间名称 */
    private String roomName;
    
    /** 房间编码 */
    private String roomCode;
    
    /** 所属区域ID */
    private Long areaId;
    
    /** 房间类型 */
    private String roomType;
    
    /** 容纳人数 */
    private Integer capacity;
}

3. 定义 QueryDTO(ruoyi-api 模块)

package org.dromara.backstage.api.domain.dto;

import lombok.Data;

import java.io.Serializable;

/**
 * 房间查询DTO
 */
@Data
public class RemoteRoomQueryDto implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    /** 页码 */
    private Integer pageNum = 1;
    
    /** 每页条数 */
    private Integer pageSize = 10;
    
    /** 房间名称(模糊查询) */
    private String roomName;
    
    /** 房间编码(精确查询) */
    private String roomCode;
    
    /** 所属区域ID */
    private Long areaId;
    
    /** 房间类型 */
    private String roomType;
}

4. 实现服务(ruoyi-modules 模块)

package org.dromara.backstage.basics.dubbo;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.backstage.api.RemotePtRoomService;
import org.dromara.backstage.api.domain.dto.RemoteRoomDto;
import org.dromara.backstage.api.domain.dto.RemoteRoomQueryDto;
import org.dromara.backstage.basics.domain.bo.PtRoomBo;
import org.dromara.backstage.basics.domain.vo.PtRoomVo;
import org.dromara.backstage.basics.service.IPtRoomService;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 房间信息远程服务实现
 */
@Slf4j
@RequiredArgsConstructor
@Service
@DubboService
public class RemotePtRoomServiceImpl implements RemotePtRoomService {
    
    private final IPtRoomService roomService;
    
    @Override
    public R<RemoteRoomDto> selectRoomById(Long roomId) {
        PtRoomVo vo = roomService.queryById(roomId);
        if (vo == null) {
            return R.fail("房间不存在");
        }
        return R.ok(convertToDto(vo));
    }
    
    @Override
    public List<RemoteRoomDto> selectRoomList() {
        PtRoomBo bo = new PtRoomBo();
        List<PtRoomVo> voList = roomService.queryList(bo);
        return MapstructUtils.convert(voList, RemoteRoomDto.class);
    }
    
    @Override
    public TableDataInfo<RemoteRoomDto> selectRoomPage(RemoteRoomQueryDto queryDto) {
        // 1. 构建分页参数
        PageQuery pageQuery = new PageQuery();
        pageQuery.setPageNum(queryDto.getPageNum() != null ? Math.max(queryDto.getPageNum(), 1) : 1);
        pageQuery.setPageSize(queryDto.getPageSize() != null ? Math.min(Math.max(queryDto.getPageSize(), 1), 500) : 10);
        
        // 2. 构建查询条件
        PtRoomBo bo = new PtRoomBo();
        bo.setRoomName(queryDto.getRoomName());
        bo.setRoomCode(queryDto.getRoomCode());
        bo.setAreaId(queryDto.getAreaId());
        bo.setRoomType(queryDto.getRoomType());
        
        // 3. 执行查询
        TableDataInfo<PtRoomVo> result = roomService.queryPageList(bo, pageQuery);
        
        // 4. 转换结果
        List<RemoteRoomDto> dtoList = MapstructUtils.convert(result.getRows(), RemoteRoomDto.class);
        
        // 5. 构建返回
        TableDataInfo<RemoteRoomDto> pageData = TableDataInfo.build();
        pageData.setRows(dtoList);
        pageData.setTotal(result.getTotal());
        return pageData;
    }
    
    @Override
    public R<Void> insertRoom(RemoteRoomDto dto) {
        PtRoomBo bo = MapstructUtils.convert(dto, PtRoomBo.class);
        roomService.insertByBo(bo);
        return R.ok();
    }
    
    @Override
    public R<Void> updateRoom(RemoteRoomDto dto) {
        PtRoomBo bo = MapstructUtils.convert(dto, PtRoomBo.class);
        roomService.updateByBo(bo);
        return R.ok();
    }
    
    @Override
    public R<Void> deleteRoomByIds(Long[] ids) {
        roomService.deleteWithValidByIds(List.of(ids), false);
        return R.ok();
    }
    
    /**
     * 手动转换方法(复杂场景使用)
     */
    private RemoteRoomDto convertToDto(PtRoomVo vo) {
        if (vo == null) {
            return null;
        }
        RemoteRoomDto dto = new RemoteRoomDto();
        dto.setRoomId(vo.getRoomId());
        dto.setRoomName(vo.getRoomName());
        dto.setRoomCode(vo.getRoomCode());
        dto.setAreaId(vo.getAreaId());
        dto.setRoomType(vo.getRoomType());
        dto.setCapacity(vo.getCapacity());
        return dto;
    }
}

服务消费者(Consumer)

1. 注入远程服务

package org.dromara.ecs.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.backstage.api.RemotePtRoomService;
import org.dromara.backstage.api.domain.dto.RemoteRoomDto;
import org.dromara.backstage.api.domain.dto.RemoteRoomQueryDto;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 房间信息控制器
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/ecs/room")
public class RoomController extends BaseController {
    
    @DubboReference
    private RemotePtRoomService remotePtRoomService;
    
    /**
     * 查询房间列表
     */
    @SaCheckPermission("ecs:room:list")
    @GetMapping("/list")
    public TableDataInfo<RemoteRoomDto> list(RemoteRoomQueryDto queryDto) {
        return remotePtRoomService.selectRoomPage(queryDto);
    }
    
    /**
     * 获取房间详细信息
     */
    @SaCheckPermission("ecs:room:list")
    @GetMapping("/{roomId}")
    public R<RemoteRoomDto> getInfo(@PathVariable Long roomId) {
        return remotePtRoomService.selectRoomById(roomId);
    }
    
    /**
     * 获取所有房间列表(不分页)
     */
    @SaCheckPermission("ecs:room:list")
    @GetMapping("/all")
    public R<List<RemoteRoomDto>> all() {
        return R.ok(remotePtRoomService.selectRoomList());
    }
}

异步调用

消费端异步调用

@DubboReference(async = true)
private RemotePtRoomService remotePtRoomService;

public void asyncCall() {
    // 发起异步调用
    remotePtRoomService.selectRoomById(1L);
    
    // 获取异步结果
    CompletableFuture<RemoteRoomDto> future = RpcContext.getContext().getCompletableFuture();
    future.whenComplete((result, exception) -> {
        if (exception != null) {
            log.error("调用失败", exception);
        } else {
            log.info("调用成功:{}", result);
        }
    });
}

常见问题

1. 服务注册失败

检查 @DubboService 注解是否正确添加,以及 Nacos 注册中心是否正常运行。

2. 服务调用超时

application.yml 中配置超时时间:

dubbo:
  consumer:
    timeout: 5000  # 5秒

3. 循环依赖问题

避免模块间的循环依赖,通过接口层解耦。

4. 版本兼容性

确保所有服务的 Dubbo 版本一致。

最佳实践

  1. 接口粒度:一个 Dubbo 接口对应一个业务领域
  2. 参数设计:使用 DTO 包装参数,避免过多参数
  3. 返回值:使用 R<T> 包装,统一错误处理
  4. 异常处理:在 Provider 端捕获并转换为业务异常
  5. 版本控制:接口变更时考虑版本兼容性