您现在的位置是:首页 >技术杂谈 >微服务开发系列 第三篇:OpenFeign网站首页技术杂谈

微服务开发系列 第三篇:OpenFeign

阳光倾洒 2024-06-18 12:01:02
简介微服务开发系列 第三篇: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;
    }
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。