赞
踩
在上一篇 注册服务到nacos我们完成服务的注册,这篇文章我们就完成服务远程调用的整合。
通过本篇文章的操作,我们即可实现像调用本地代码一样让三个服务完成互相调用。
考虑版本兼容性,可以看到笔者的spring boot
、spring cloud alibaba
、spring 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>
由于前面的教程已经引入了spring cloud Alibaba
和spring 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>
由于远程的调用的原因,我们需要对之前的服务架构进行重构,将通过的dto
对象放到cloud-common
模块:
将account
、order
、product
三个模块的分为service
和feign
远程调用模块,这里笔者以account
模块为例讲解一下重构过程。
我们本次会以用户下单为例编写相关接口,所以我们分别会对模块编写对应功能:
首先是account-service
,重构过程很简单,将controller
等代码直接移动从account-biz
移动到account-service
即可:
完成后每个模块引入openfeign
这样依赖
<!--openfeign轻量级http框架-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
我们的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();
}
}
然后我们的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);
}
补充:我们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>
<!– DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性) –>
<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>
然后是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>
同理完成order
和product
模块重构和编写。首先是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();
}
}
然后是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);
}
}
然后我们就可以开始最重要的order
模块的编写了,首先引入order
、product
两个远程调用接口的依赖,然后引入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>
完成后,启动类增加注解去扫描引入的依赖的包,确保将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");
}
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;
}
首先确认服务正常启动,并注册到nacos
中
调用如下接口,查看数据库是否插入并修改成功,若成功则说明远程调用成功,需要补充一句,由于涉及三个服务的调用,这俩面涉及分布式事务问题,笔者后续还会继续补充
http://localhost:9001/order/order/create
参数
{
"serialVersionUID": 1,
"orderNo": "123",
"accountCode": "zsy",
"productCode": "P001",
"count": 1,
"amount": 1,
"price": 1
}
如果某些网络接口远程调用不稳定,读者可以增加下面这段配置延长远程调用时间
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
笔者在测试过程中遇到更新乱码问题,查阅网上得知数据库连接信息有问题,将数据连接字符串增加字符格式配置即可,如下所示
jdbc:mysql://ip:3306/cloud_alibaba?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。