当前位置:   article > 正文

微服务远程调用openFeign整合_openfeign 微服务合并 单体部署

openfeign 微服务合并 单体部署

前言

在上一篇 注册服务到nacos我们完成服务的注册,这篇文章我们就完成服务远程调用的整合。
通过本篇文章的操作,我们即可实现像调用本地代码一样让三个服务完成互相调用。

操作步骤

引入依赖

考虑版本兼容性,可以看到笔者的spring bootspring cloud alibabaspring cloud版本信息如下:

<spring-boot.version>2.1.9.RELEASE</spring-boot.version>
		<springcloud-alibaba.version>0.9.0.RELEASE</springcloud-alibaba.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
  • 1
  • 2
  • 3

由于前面的教程已经引入了spring cloud Alibabaspring boot的依赖,所以这篇文章引入spring cloud依赖即可

<!--springcloud依赖-->
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${springcloud-alibaba.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

服务模块改造

由于远程的调用的原因,我们需要对之前的服务架构进行重构,将通过的dto对象放到cloud-common模块:

在这里插入图片描述

accountorderproduct三个模块的分为servicefeign远程调用模块,这里笔者以account模块为例讲解一下重构过程。

我们本次会以用户下单为例编写相关接口,所以我们分别会对模块编写对应功能:

  1. order:生成订单。
  2. account:用户金额扣减。
  3. product:商品库存扣减。

首先是account-service,重构过程很简单,将controller等代码直接移动从account-biz移动到account-service即可:

在这里插入图片描述

完成后每个模块引入openfeign这样依赖

  <!--openfeign轻量级http框架-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

我们的accountController编写下面这些方法:

@RestController
@Log4j2
public class AccountController {

    @Autowired
    private IAccountService accountService;

 

    @PostMapping("/account/reduceAccount")
    ResultData<Boolean> reduceAccount(@RequestParam("accountCode") String accountCode, @RequestParam("amount") BigDecimal amount) {
        accountService.reduceAccount(accountCode, amount);
        return ResultData.success();
    }


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后我们的feign模块声明controller中的所有HTTP接口,如下所示,可以看到配置的映射地址和controller是一致的。这样一来项目启动后就会将其 feign接口信息注册到nacos中。

@FeignClient(name = "account-service")
public interface AccountFeign {

    @PostMapping("/account/reduceAccount")
    ResultData<Boolean> reduceAccount(@RequestParam("accountCode") String accountCode, @RequestParam("amount") BigDecimal amount);
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

补充:我们controller中使用了@Log4j2l,所以我们需要在resource目录下配置log4j2.xml以及logback-custom.xml(没有下载lombok插件的注意下载lombok插件)

在这里插入图片描述

首先是log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>

<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->

<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->

<configuration status="WARN" monitorInterval="30">

    <!-- 常量引用 -->
    <properties>
        <property name="LOG_HOME">logs/</property>
        <property name="LOG_NAME">order-service</property>
        <!--<property name="FORMAT">%d{yyyy-MM-dd HH:mm:ss} [%traceId] %-5p %c{1}:%L - %m%n</property>-->
        <property name="FORMAT">%d %-5p %c{1}:%L - %m%n</property>
    </properties>
    <!--先定义所有的appender-->

    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${FORMAT}"/>
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
        <!--<File name="log" fileName="${LOG_HOME}/${LOG_NAME}-test.log" append="false">
            <PatternLayout pattern="${FORMAT}"/>
        </File>-->

        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/${LOG_NAME}-info.log"
                     filePattern="${LOG_HOME}/bak/$${date:yyyy-MM}/${LOG_NAME}-info-%d{yyyy-MM-dd}-%i.log">
            <!--${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log-->
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${FORMAT}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <!-- 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小 -->
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>

        </RollingFile>

        <!-- <RollingFile name="RollingFileWarn" fileName="${LOG_HOME}/${LOG_NAME}-warn.log"
                      filePattern="${LOG_HOME}/bak/$${date:yyyy-MM}/${LOG_NAME}-warn-%d{yyyy-MM-dd}-%i.log">
             <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
             <PatternLayout pattern="${FORMAT}"/>
             <Policies>
                 <TimeBasedTriggeringPolicy/>
                 <SizeBasedTriggeringPolicy size="100 MB"/>
             </Policies>
             &lt;!&ndash; DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性) &ndash;&gt;
             <DefaultRolloverStrategy max="20"/>
         </RollingFile>-->

        <!--错误日志-->
        <RollingFile name="RollingFileError" fileName="${LOG_HOME}/${LOG_NAME}-error.log"
                     filePattern="${LOG_HOME}/bak/$${date:yyyy-MM}/${LOG_NAME}-error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${FORMAT}"/>

            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>

        </RollingFile>

    </appenders>



    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <logger name="org.springframework" level="INFO" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>

        <logger name="com.xxl.job.core" level="INFO" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>


        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <!--
                        <appender-ref ref="RollingFileWarn"/>
            -->
            <appender-ref ref="RollingFileError"/>
        </root>

    </loggers>

</configuration>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

然后是logback-custom.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 -->
    <property name="LOG_HOME" value="./logs" />
    <property name="LOG_NAME" value="order-service" />

    <!--从配置中心加载数据-->
    <springProperty scope="context" name="applicationName" source="spring.application.name"/>
    <springProperty scope="context" name="logstashDestination" source="logstash.destination"/>

    <!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <withJansi>true</withJansi>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件INFO -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_HOME}/${LOG_NAME}_info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/${LOG_NAME}_info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件ERROR -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_HOME}/${LOG_NAME}_error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_HOME}/${LOG_NAME}_error.%d{yyyy-MM-dd}.log</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!--LogStash-->
    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!--logstash的请求地址-->
        <destination>${logstashDestination}</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
            <customFields>{"serverName":"${applicationName}"}</customFields>
        </encoder>
    </appender>



    <!--myibatis log configure -->
    <logger name="com.apache.ibatis" level="TRACE" />
    <logger name="java.sql.Connection" level="DEBUG" />
    <logger name="java.sql.Statement" level="DEBUG" />
    <logger name="java.sql.PreparedStatement" level="DEBUG" />

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="ERROR_FILE" />
        <appender-ref ref="logstash"/>
    </root>

</configuration>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

同理完成orderproduct模块重构和编写。首先是product模块的controller代码

 @RestController
@RequestMapping()
public class ProductController {

    @Autowired
    private IProductService productService;

    @PostMapping("/product/deduct")
    public ResultData deduct(@RequestParam("productCode") String productCode, @RequestParam("deductCount") Integer deductCount) {
        productService.deduct(productCode, deductCount);
        return ResultData.success();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

然后是service模块的代码编写,逻辑很简单,就查看是否存在商品,若商品存在且数量充裕情况下进行相应库存扣减即可。

@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {


    @Autowired
    private ProductMapper productMapper;

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void deduct(String productCode, Integer deductCount) {
        QueryWrapper<Product> wrapper = new QueryWrapper<>();
        wrapper.eq("product_code", productCode);
        Product product = productMapper.selectOne(wrapper);

        if (product == null) {
            log.error("下单失败,产品不存在,产品代码: " + productCode);
            throw new RuntimeException("下单失败,产品不存在");
        }


        int subCount = product.getCount() - deductCount;

        if (subCount < 0) {
            log.error("下单失败,库存不足,产品代码:" + productCode);
            throw new RuntimeException("下单失败,库存不足,产品代码:" + productCode);
        }

        //更新库存
        product.setCount(subCount);
        productMapper.updateById(product);

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

然后我们就可以开始最重要的order模块的编写了,首先引入orderproduct两个远程调用接口的依赖,然后引入openfeign的依赖。

<!--openfeign轻量级http框架-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--account-feign远程调用-->
        <dependency>
            <groupId>com.zsy</groupId>
            <artifactId>account-feign</artifactId>
            <version>${parent.version}</version>
        </dependency>

        <!--product-feign远程调用-->
        <dependency>
            <groupId>com.zsy</groupId>
            <artifactId>product-feign</artifactId>
            <version>${parent.version}</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

完成后,启动类增加注解去扫描引入的依赖的包,确保将feign放到spring容器中@EnableFeignClients(basePackages = "com.zsy.feign.*")

orderController编写调用代码

@PostMapping("/order/create")
    public ResultData<String> create(@RequestBody OrderDto orderDTO) {
        log.info("创建订单开始,订单信息:{}", JSON.toJSONString(orderDTO));
        orderDTO.setOrderNo(String.valueOf(UUID.randomUUID()));
        //设置总金额
        orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount())));
        //创建订单
        orderService.createOrder(orderDTO);
        return ResultData.success("create  order success");

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

service代码,编写下单、扣款、扣库存的调用

@Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void createOrder(OrderDto orderDTO) {
        Order order = new Order();
        BeanUtils.copyProperties(orderDTO, order);
        //创建订单
        save(order);
        //扣减金额
        accountFeign.reduceAccount(orderDTO.getAccountCode(), orderDTO.getPrice());
        //扣减商品
        productFeign.deduct(orderDTO.getProductCode(), orderDTO.getCount());


    }


    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean save(Order entity) {
        return orderMapper.insert(entity) > 0;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

测试

首先确认服务正常启动,并注册到nacos

在这里插入图片描述

调用如下接口,查看数据库是否插入并修改成功,若成功则说明远程调用成功,需要补充一句,由于涉及三个服务的调用,这俩面涉及分布式事务问题,笔者后续还会继续补充

http://localhost:9001/order/order/create
  • 1

参数

{
  "serialVersionUID": 1,
  "orderNo": "123",
  "accountCode": "zsy",
  "productCode": "P001",
  "count": 1,
  "amount": 1,
  "price": 1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

feign超时设置

如果某些网络接口远程调用不稳定,读者可以增加下面这段配置延长远程调用时间

feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

关于更新乱码问题

笔者在测试过程中遇到更新乱码问题,查阅网上得知数据库连接信息有问题,将数据连接字符串增加字符格式配置即可,如下所示

jdbc:mysql://ip:3306/cloud_alibaba?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
  • 1

参考文献

SpringCloud Alibaba微服务实战三 - 服务调用

彻底解决mybatis 插入数据中文后显示问号(?)的问题

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/428845
推荐阅读
  

闽ICP备14008679号