您现在的位置是:首页 >技术杂谈 >微服务开发系列 第三篇:OpenFeign网站首页技术杂谈
微服务开发系列 第三篇:OpenFeign
总概
A、技术栈
- 开发语言:Java 1.8
- 数据库:MySQL、Redis、MongoDB、Elasticsearch
- 微服务框架:Spring Cloud Alibaba
- 微服务网关:Spring Cloud Gateway
- 服务注册和配置中心:Nacos
- 分布式事务:Seata
- 链路追踪框架:Sleuth
- 服务降级与熔断:Sentinel
- ORM框架:MyBatis-Plus
- 分布式任务调度平台:XXL-JOB
- 消息中间件:RocketMQ
- 分布式锁:Redisson
- 权限:OAuth2
- DevOps:Jenkins、Docker、K8S
B、本节实现目标
- 新建mall-feign服务。
- 新建mall-order服务。
- 实现下单接口
一、Spring Cloud OpenFeign简介
Spring Cloud OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。
二、新建mall-feign服务
2.1 新建module
新建module
新建mall-feign
2.2 新建.gitignore
.idea
.mvn
target
mvnw
*.iml
2.3 配置pom.xml
参考mall-member服务配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>mall-pom</artifactId>
<groupId>com.ac</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.ac</groupId>
<artifactId>mall-feign</artifactId>
<version>${mall.version}</version>
<name>mall-feign</name>
<description>OpenFeign服务</description>
<dependencies>
<dependency>
<groupId>com.ac</groupId>
<artifactId>mall-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.4 加OpenFeign依赖
在mall-pom的pom.xml里加入mavn依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
2.5 提供用户查询的Feign接口
package com.ac.feign.member.api;
import com.ac.feign.member.dto.MemberDTO;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("mall-member")
public interface MemberFeignApi {
@ApiOperation(value = "获取用户")
@GetMapping("member/{id}")
MemberDTO findMember(@PathVariable("id") Long id);
}
2.5.1 说明NO.1
@FeignClient("mall-member")
中的"mall-member"是member服务注册在Nacos上的服务名称,
mall-member
2.5.2 说明NO.2
@PathVariable("id") Long id
,@PathVariable括号里的“id”不能少,否则其他依赖mall-feign服务的代码启动会报错,MemberFeignApi注入失败。
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.ac.feign.member.api.MemberFeignApi': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
2.6 mall-feign项目结构截图
mall-feign项目结构截图
2.7 关于OpenFeign位置说明
OpenFeign位置一般有两种,
- 第一种:在各个服务中写需要用的OpenFeign接口。
- 第二种:将项目中所有的OpenFeign接口抽取出来,放到一个公共的feign服务中,如上面创建的mall-feign服务,然后其他各个服务都依赖这个mall-feign。
很明显,我采用的是第二种,理由是,一个OpenFeign接口可能很多服务都需要用,比如,大部分服务都会通过OpenFeign来取用户数据,如果采用方案一,各个服务都要写一遍MemberFeignApi,如果采用第二种方案,则mall-feign里的MemberFeignApi能被各服务共用。
三、新建mall-order服务
参考mall-feign新建mall-order服务,具体操作步骤省略。
3.1 依赖mall-feign服务
mall-order需要依赖mall-feign服务,因此在pom.xml里配置依赖项
<dependency>
<groupId>com.ac</groupId>
<artifactId>mall-feign</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
pom.xml依赖项
3.2 @EnableFeignClients
注解
在mall-order的Application类上,加上注解@EnableFeignClients
注解,@EnableFeignClients
申明该项目是Feign客户端,扫描对应的feign client。
@MapperScan("com.ac.order.mapper")
@ComponentScan("com.ac.*")
@EnableFeignClients(basePackages = {"com.ac.feign.*"})
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
3.2.1 说明NO.1
@EnableFeignClients(basePackages = {"com.ac.feign.*"})
,中配置了basePackages,让IOC容器去扫描mall-feign服务里的接口。
如果不配置,mall-order服务启动时会报错
A component required a bean of type 'com.ac.feign.member.api.MemberFeignApi' that could not be found.
3.3 调用MemberFeignApi测试
@Api(tags = "订单")
@RestController
@RequestMapping("order")
public class OrderController {
@Resource
private MemberFeignApi memberFeignApi;
@ApiOperation(value = "通过OpenFeign取用户数据")
@GetMapping("feign/member/{id}")
public MemberDTO test(@PathVariable Long id) {
return memberFeignApi.findMember(id);
}
}
测试结果
项目结构截图
四、下单接口
下单接口需要通过memberId调用mall-member服务的用户信息,通过productId调用mall-product服务的产品信息,代码如下:
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDaoImpl;
@Resource
private MemberFeignApi memberFeignApi;
@Resource
private OrderItemService orderItemServiceImpl;
@Transactional(rollbackFor = Exception.class)
@Override
public Long createOrder(OrderAddVO addVO) {
Order order = new Order();
order.setOrderNo(RandomUtil.randomNumbers(8));
order.setOrderState(OrderStateEnum.UN_PAY);
order.setOrderTime(LocalDateTime.now());
//通过feign取用户信息
MemberDTO member = memberFeignApi.findMember(addVO.getMemberId());
order.setMemberId(addVO.getMemberId());
order.setMemberName(member.getMemberName());
order.setMobile(member.getMobile());
orderDaoImpl.save(order);
BigDecimal discountAmount = new BigDecimal(0.00);
BigDecimal productAmount = new BigDecimal(0.00);
//存订单项信息
for (OrderItemAddVO orderItemAdd : addVO.getOrderItemList()) {
OrderItem orderItem = orderItemServiceImpl.addOrderItem(order.getId(), orderItemAdd);
productAmount = productAmount.add(orderItem.getBuyPrice().multiply(new BigDecimal(orderItem.getBuyNum())));
}
//更新订单金额信息
order.setDiscountAmount(discountAmount);
order.setProductAmount(productAmount);
BigDecimal payAmount = productAmount.subtract(discountAmount);
order.setPayAmount(payAmount);
orderDaoImpl.updateById(order);
return order.getId();
}
}
@Slf4j
@Service
public class OrderItemServiceImpl implements OrderItemService {
@Resource
private OrderItemDao orderItemDaoImpl;
@Resource
private ProductFeignApi productFeignApi;
@Override
public OrderItem addOrderItem(Long orderId, OrderItemAddVO orderItemAdd) {
//通过feign取产品信息
ProductDTO product = productFeignApi.findProduct(orderItemAdd.getProductId());
OrderItem entity = new OrderItem();
entity.setOrderId(orderId);
entity.setProductId(product.getId());
entity.setProductName(product.getProductName());
entity.setImageUrl(product.getImageUrl());
entity.setBuyNum(orderItemAdd.getBuyNum());
if (product.getDiscountPrice() != null && product.getDiscountPrice().doubleValue() > 0) {
entity.setBuyPrice(product.getDiscountPrice());
} else {
entity.setBuyPrice(product.getSellPrice());
}
orderItemDaoImpl.save(entity);
return entity;
}
}