不同微服务之间Feign调用方法(Pigx框架下)
写在前面:近日在写需求时,有个需求需要在ctn端调用cd端的服务去实现,但我们知道为了安全性和效率的考虑,各个微服务之间有认证机制,不能随意调用,那么如何调用其他微服务呢。
Pigx框架下spring cloud中Feign的调用
在使用SpringCloud生态的时候,微服务之间会进行调用,一般我们有两个选择。一是选择使用RestTemplate,二是使用Feign。二者都是基于HTTP的调用,但Feign是直接使用声明式调用,更加符合大家平时写接口的习惯.下面介绍Pigx框架下feign调用方法。
步骤
1. 😐首先要知道自己调用哪个微服务😐
比如我这次要使用的是cd端的微服务,那么我的一系列配置要写在cd端,而且我这里ctn调用cd是内部之间调用,不是外部调用,属于 👌无token请求,服务内部发起情况处理。👌 其他的还有外部调用带token请求先不谈(👌客户端带Token 情况👌)
无token请求(内部请求)
带token请求(外部请求)
2. 😡先写feign服务😡
注意,调用哪个微服务就在那个服务写,位置在api包下的feign包里
service服务命名前缀统一加Remote(远程)最好,下面开始写服务内容
package com.xbm.cd.api.feign;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.xbm.common.constant.XbmServiceNameConstants;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
/**
* @author wujie
* @data 2021.7.2
*/
@FeignClient(contextId = "remoteCdOrderBoxService",value = XbmServiceNameConstants.XBM_CD_SERVICE)
public interface RemoteCdOrderBoxService {
@PostMapping("/order/updateByUuid")
R updateByUuid(@RequestBody List<String> uuid,@RequestHeader(SecurityConstants.FROM) String from);
}
👇注意一点:如果是@GetMapping接口通过路径传递值的话要注意了,要定义value是什么,方法定义要这么写👇
@GetMapping("/eq/select/{name}")
SysEquipment selectOne(@PathVariable(value = "name") String name, @RequestHeader(SecurityConstants.FROM) String from);
//一定要记得写成@PathVariable(value = "name") String name 形式
//不能和平常一样写@PathVariable String name 形式
首先这个@FeignClient注释是必加的,后面括号里面要声明两个属性值,一个contextId声明这个Bean叫什么,最好和service服务名一样,后面value写调哪个服务,这里统一规范Feign写在调用服务下,所以就写当前的服务cd,@PostMapping("/order/updateByUuid")表示这是一个post类型接口,但重点来了,这里不写任何接口要实现的操作,这个接口地址是cd端真实存在的一个接口地址,这里只是调用它。所以这里要求所有Feign服务调用方法都是事先已经存在的(没有就先写在要调用的服务端里面)。
@SysLog("修改一键放箱状态")
@PostMapping("/updateByUuid")
@Transactional
@Inner
public R updateByUuid(@RequestBody List<String> uuid){
orderBoxInfoService.update(Wrappers.<CdOrderBoxInfo>lambdaUpdate()
.set(CdOrderBoxInfo::GetToPackStatus,0).in(CdOrderBoxInfo::getOrderUuid,uuid))
return R.ok();
}
@Inner记得加上(无token内部调用需要)
其次还得严格按照调用方法的传送数据格式来传送数据@RequestBody List uuid 和 调用方法一致
R updateByUuid(@RequestBody List<String> uuid,@RequestHeader(SecurityConstants.FROM) String from);
@RequestHeader(SecurityConstants.FROM) String from————为无token调用所必需的获取请求头中数据的注释。
注意
若@FeignClient注释不能使用,得手动导入相关依赖,在pom.xml中加入即可
<!--feign 工具类-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-feign</artifactId>
</dependency>
3. 😄配置 feignclient 的全类名路径, 避免包名不规范无法扫描问题😄
这个配置要写在api包的resource包里,表明feign服务路径
# 配置 feignclient 的全类名路径, 避免包名不规范无法扫描问题
com.pig4cloud.pigx.common.feign.PigxFeignAutoConfiguration=\
com.xbm.cd.api.feign.RemoteCdOrderBoxService
如果以后有多个feign调用服务,需要像下面这样加逗号和斜杠(最后一个不用)
com.pig4cloud.pigx.admin.api.feign.RemoteRoleService,\
com.pig4cloud.pigx.admin.api.feign.RemoteMenuService,\
com.pig4cloud.pigx.admin.api.feign.RemoteFileService
4. 😁 在其他服务端调用😁
首先声明一下
private final RemoteUserService userService;
final修饰符:只能被赋值一次
- final可以修饰类、方法、变量
a.修饰类的时候此类不能被继承。
b.修饰方法时方法不能被重写。
c.修饰变量时不能被重新赋值。 - 修饰基本类型时,基本类型的值不能发生改变。
修饰引用类型时,引用类型的地址值不能改变,但是该对象堆内存的值可以改变。如final Student s1=new Student( ); 对象s1的地址不可以改变,s1的成员变量可以改变
//查询删除的放箱订单是否有uuid(用于筛选判断是不是由车队端运单一键放箱过来的)
List<CtnOrderInfo> orderInfos = ctnOrderInfoService.list(Wrappers.<CtnOrderInfo>lambdaQuery().in(CtnOrderInfo::getNo, orderNos));
List<String> uuid = orderInfos.stream().map(CtnOrderInfo::getUuid).collect(Collectors.toList());
//将车队运单里orderUuid与放箱端的uuid对比,相同则说明是由运单一键放箱过来,将运单的一键放箱状态改为0
if (uuid !=null){
cdOrderBoxService.updateByUuid(uuid,SecurityConstants.FROM_IN);
}
在ctn端调用时也要严格按照要求发送数据,这里传入的uuid要符合调用方法中 List 类型。
到这里是不是以为结束了,确实,pigx教程里是这样的,但细心的小伙伴应该已经发现了隐藏bug,Pigx框架下是有租户这个概念的,所以在@RequestHeader(SecurityConstants.FROM) String from 请求内容中,如果调用服务端的租户和被调用服务端的租户不一样,那么这个认证就会不通过,该怎么办呢?
5. 😂如何绕过租户😂
-
在service服务层自己写直接调用数据库的方法,不通过已有方法
-
首先在相关service里加方法
int updateByOrderUuid(List<String> uuid);
别忘了把调用的接口里的方法更改为这个自己写的方法
@SysLog("修改一键放箱状态")
@PostMapping("/updateByUuid")
@Transactional
@Inner
public R updateByUuid(@RequestBody List<String> uuid){
orderBoxInfoService.updateByOrderUuid(uuid);
return R.ok();
}
-
在serviceImpl里写具体方法实现
@Override public int updateByOrderUuid(List<String> uuid){ UpdateWrapper<CdOrderBoxInfo> wrapper = new UpdateWrapper<>(); wrapper.lambda().set(CdOrderBoxInfo::getToPackStatus,0).in(CdOrderBoxInfo::getOrderUuid,uuid); return getBaseMapper().updateBySomething(wrapper); }
(这里的updateBySomething在下面会说怎么来的)
写到这里是不是还是一头雾水,为什么要自己写方法呢?
因为接下来我们要在Mapper层写具体的不想被多租户过滤的方法,然后给方法实现类serviceImpl使用,不能随意改变原有的方法的多租户过滤,所以得自己另写不需要过滤的方法,保证安全性。
👇注意:
如果你的系统没有租户概念,或者你跨服务调用数据也只是调用自己租户的数据的话,就不需要自己另外写绕开多租户的方法,数据量大了会影响运行速度,直接用自带的get、list等方法即可,直接将updateBySomething换为自带的update方法即可,然后到这里就结束了,就可以拿去用了
@SqlParser(filter = true)
@Update("update cd_order_box_info set ${ew.sqlSet} ${ew.customSqlSegment}")
int updateBySomething(@Param(Constants.WRAPPER) Wrapper<CdOrderBoxInfo> wrapper);
@SqlParser(filter = true) 固定写法,避开多租户。
@Update(“update cd_order_box_info set ${ew.sqlSet} ${ew.customSqlSegment}”) 写上更新的表的名称,后面两个
${ew.sqlSet} ${ew.customSqlSegment}固定写法
int updateBySomething(@Param(Constants.WRAPPER) Wrapper wrapper);
定义该方法用在serviceImpl里的 return getBaseMapper().updateBySomething(wrapper);