# Dubbo 集成指南 ## 概述 RuoYi-Cloud-Plus 使用 Apache Dubbo 3.X 作为 RPC 远程调用框架,替代传统的 Feign 调用。 ## 核心概念 | 概念 | 说明 | |------|------| | Provider | 服务提供者,暴露服务 | | Consumer | 服务消费者,调用服务 | | Interface | 服务接口定义 | | Registry | 注册中心(Nacos) | ## 服务提供者(Provider) ### 1. 定义接口(ruoyi-api 模块) ```java 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 selectRoomById(Long roomId); /** * 查询房间列表 */ List selectRoomList(); /** * 分页查询房间列表 */ TableDataInfo selectRoomPage(RemoteRoomQueryDto queryDto); /** * 新增房间 */ R insertRoom(RemoteRoomDto dto); /** * 修改房间 */ R updateRoom(RemoteRoomDto dto); /** * 删除房间 */ R deleteRoomByIds(Long[] ids); } ``` ### 2. 定义 DTO(ruoyi-api 模块) ```java 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 模块) ```java 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 模块) ```java 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 selectRoomById(Long roomId) { PtRoomVo vo = roomService.queryById(roomId); if (vo == null) { return R.fail("房间不存在"); } return R.ok(convertToDto(vo)); } @Override public List selectRoomList() { PtRoomBo bo = new PtRoomBo(); List voList = roomService.queryList(bo); return MapstructUtils.convert(voList, RemoteRoomDto.class); } @Override public TableDataInfo 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 result = roomService.queryPageList(bo, pageQuery); // 4. 转换结果 List dtoList = MapstructUtils.convert(result.getRows(), RemoteRoomDto.class); // 5. 构建返回 TableDataInfo pageData = TableDataInfo.build(); pageData.setRows(dtoList); pageData.setTotal(result.getTotal()); return pageData; } @Override public R insertRoom(RemoteRoomDto dto) { PtRoomBo bo = MapstructUtils.convert(dto, PtRoomBo.class); roomService.insertByBo(bo); return R.ok(); } @Override public R updateRoom(RemoteRoomDto dto) { PtRoomBo bo = MapstructUtils.convert(dto, PtRoomBo.class); roomService.updateByBo(bo); return R.ok(); } @Override public R 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. 注入远程服务 ```java 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 list(RemoteRoomQueryDto queryDto) { return remotePtRoomService.selectRoomPage(queryDto); } /** * 获取房间详细信息 */ @SaCheckPermission("ecs:room:list") @GetMapping("/{roomId}") public R getInfo(@PathVariable Long roomId) { return remotePtRoomService.selectRoomById(roomId); } /** * 获取所有房间列表(不分页) */ @SaCheckPermission("ecs:room:list") @GetMapping("/all") public R> all() { return R.ok(remotePtRoomService.selectRoomList()); } } ``` ## 异步调用 ### 消费端异步调用 ```java @DubboReference(async = true) private RemotePtRoomService remotePtRoomService; public void asyncCall() { // 发起异步调用 remotePtRoomService.selectRoomById(1L); // 获取异步结果 CompletableFuture future = RpcContext.getContext().getCompletableFuture(); future.whenComplete((result, exception) -> { if (exception != null) { log.error("调用失败", exception); } else { log.info("调用成功:{}", result); } }); } ``` ## 常见问题 ### 1. 服务注册失败 检查 `@DubboService` 注解是否正确添加,以及 Nacos 注册中心是否正常运行。 ### 2. 服务调用超时 在 `application.yml` 中配置超时时间: ```yaml dubbo: consumer: timeout: 5000 # 5秒 ``` ### 3. 循环依赖问题 避免模块间的循环依赖,通过接口层解耦。 ### 4. 版本兼容性 确保所有服务的 Dubbo 版本一致。 ## 最佳实践 1. **接口粒度**:一个 Dubbo 接口对应一个业务领域 2. **参数设计**:使用 DTO 包装参数,避免过多参数 3. **返回值**:使用 `R` 包装,统一错误处理 4. **异常处理**:在 Provider 端捕获并转换为业务异常 5. **版本控制**:接口变更时考虑版本兼容性