微服务第一轮

课程文档

目录

一、业务流程

1、登录

Controller中的接口: 

 Service中的实现impl:

Service中的实现impl所继承的接口IService(各种方法): 

VO:

 DTO:

2、搜索商品

​Controller中的接口:

3、购物车

​Controller中的接口:

二、拆分商品服务

1. 创建新module - maven模块,并引入依赖

2. 新建包com.hmall.xx(业务名),添加和修改启动类,新建mapper包、domain包 - service包 - controller包

3. 拷贝并修改yaml配置文件到resources中,分别修改 端口号、服务名称、datasource(需创建sql datebase)、swagger接口文档说明与controller扫描包

4.domain,mapper,service,controller包代码 

5. 刷新maven,添加该业务模块启动项到Services中,并把Active profiles 修改为 local

6. 运行,在访问地址后面添加doc.html访问swagger接口文档,进行调试

​三、拆分购物车服务

四、服务调用(RPC)

RestTemplate 

远程直接调用

五、Nacos注册中心

服务注册

 1、添加依赖,配置Nacos

 2、启动服务,访问http://192.168.150.101:8848/nacos/控制台,可以发现服务注册成功:

​服务发现

1、  添加依赖,配置Nacos

 2、根据负载均衡算法发现并调用方法


一、业务流程

1、登录

Controller中的接口: 


@Api(tags = "用户相关接口")
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("用户登录接口")
    @PostMapping("login")
    public UserLoginVO login(@RequestBody @Validated LoginFormDTO loginFormDTO){
        return userService.login(loginFormDTO);
    }
}

 Service中的实现impl:

public interface IUserService extends IService<User> {

    UserLoginVO login(LoginFormDTO loginFormDTO);

    void deductMoney(String pw, Integer totalFee);
}

Service中的实现impl所继承的接口IService(各种方法): 


public interface IService<T> {
    int DEFAULT_BATCH_SIZE = 1000;

    default boolean save(T entity) {
        return SqlHelper.retBool(this.getBaseMapper().insert(entity));
    }

    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean saveBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 1000);
    }

    boolean saveBatch(Collection<T> entityList, int batchSize);

    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return this.saveOrUpdateBatch(entityList, 1000);
    }

    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
}

VO:

@Data
public class UserLoginVO {
    private String token;
    private Long userId;
    private String username;
    private Integer balance;
}

 DTO:

@Data
@ApiModel(description = "登录表单实体")
public class LoginFormDTO {
    @ApiModelProperty(value = "用户名", required = true)
    @NotNull(message = "用户名不能为空")
    private String username;
    @NotNull(message = "密码不能为空")
    @ApiModelProperty(value = "用户名", required = true)
    private String password;
    @ApiModelProperty(value = "是否记住我", required = false)
    private Boolean rememberMe = false;
}

2、搜索商品

在首页搜索框输入关键字手机,点击搜索即可进入搜索列表页面: 

 Controller中的接口:

@Api(tags = "搜索相关接口")
@RestController
@RequestMapping("/search")
@RequiredArgsConstructor
public class SearchController {

    private final IItemService itemService;

    @ApiOperation("搜索商品")
    @GetMapping("/list")
    public PageDTO<ItemDTO> search(ItemPageQuery query) {
        // 分页查询
        Page<Item> result = itemService.lambdaQuery()
                .like(StrUtil.isNotBlank(query.getKey()), Item::getName, query.getKey())
                .eq(StrUtil.isNotBlank(query.getBrand()), Item::getBrand, query.getBrand())
                .eq(StrUtil.isNotBlank(query.getCategory()), Item::getCategory, query.getCategory())
                .eq(Item::getStatus, 1)
                .between(query.getMaxPrice() != null, Item::getPrice, query.getMinPrice(), query.getMaxPrice())
                .page(query.toMpPage("update_time", false));
        // 封装并返回
        return PageDTO.of(result, ItemDTO.class);
    }
}

3、购物车

在搜索到的商品列表中,点击按钮加入购物车,即可将商品加入购物车:

 加入成功后即可进入购物车列表页,查看自己购物车商品列表:

 Controller中的接口:

@Api(tags = "购物车相关接口")
@RestController
@RequestMapping("/carts")
@RequiredArgsConstructor
public class CartController {
    private final ICartService cartService;

    @ApiOperation("添加商品到购物车")
    @PostMapping
    public void addItem2Cart(@Valid @RequestBody CartFormDTO cartFormDTO){
        cartService.addItem2Cart(cartFormDTO);
    }

    @ApiOperation("更新购物车数据")
    @PutMapping
    public void updateCart(@RequestBody Cart cart){
        cartService.updateById(cart);
    }

    @ApiOperation("删除购物车中商品")
    @DeleteMapping("{id}")
    public void deleteCartItem(@Param ("购物车条目id")@PathVariable("id") Long id){
        cartService.removeById(id);
    }

    @ApiOperation("查询购物车列表")
    @GetMapping
    public List<CartVO> queryMyCarts(){
        return cartService.queryMyCarts();
    }
    @ApiOperation("批量删除购物车中商品")
    @ApiImplicitParam(name = "ids", value = "购物车条目id集合")
    @DeleteMapping
    public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids){
        cartService.removeByItemIds(ids);
    }
}

其中,查询购物车列表时,由于要判断商品最新的价格和状态,所以还需要查询商品信息,业务流程如下:

二、拆分商品服务

1. 创建新module - maven模块,并引入依赖

创建新模块 

选择maven模块,并设定JDK版本为11: 

 添加依赖

<?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>
        <groupId>com.heima</groupId>
        <artifactId>hmall</artifactId>
        <version>1.0.0</version>
    </parent>

    <groupId>org.qingshui</groupId>
    <artifactId>item-service</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <version>3.3.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 新建包com.hmall.xx(业务名),添加和修改启动类,新建mapper包、domain包 - service包 - controller包

 新建包com.hmall.item: 

 编写启动类ItemApplication:

@MapperScan("com.hmall.item.mapper")
@SpringBootApplication
public class ItemApplication {
    public static void main(String[] args) {
        SpringApplication.run(ItemApplication.class, args);
    }
}

 新建mapper包、domain包 - service包 - controller包:

3. 拷贝并修改yaml配置文件到resources中,分别修改 端口号、服务名称、datasource(需创建sql datebase)、swagger接口文档说明与controller扫描包

从hm-service中拷贝:

修改application.yaml为:

server:
  port: 8081
spring:
  application:
    name: item-service
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://${hm.db.host}:3306/hm-item?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ${hm.db.pw}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"
knife4j:
  enable: true
  openapi:
    title: 商品服务接口文档
    description: "信息"
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.hmall.item.controller

创建该模块的数据库: 

4.domain,mapper,service,controller包代码 

【1】domain包代码:dto、po、vo、(query)

dto:数据传输对象 

OrderDetailDTO: 

@ApiModel(description = "订单明细条目")
@Data
@Accessors(chain = true)
public class OrderDetailDTO {
    @ApiModelProperty("商品id")
    private Long itemId;
    @ApiModelProperty("商品购买数量")
    private Integer num;
}

 ItemDTO:


@Data
@ApiModel(description = "商品实体")
public class ItemDTO {
    @ApiModelProperty("商品id")
    private Long id;
    @ApiModelProperty("SKU名称")
    private String name;
    @ApiModelProperty("价格(分)")
    private Integer price;
    @ApiModelProperty("库存数量")
    private Integer stock;
    @ApiModelProperty("商品图片")
    private String image;
    @ApiModelProperty("类目名称")
    private String category;
    @ApiModelProperty("品牌名称")
    private String brand;
    @ApiModelProperty("规格")
    private String spec;
    @ApiModelProperty("销量")
    private Integer sold;
    @ApiModelProperty("评论数")
    private Integer commentCount;
    @ApiModelProperty("是否是推广广告,true/false")
    private Boolean isAD;
    @ApiModelProperty("商品状态 1-正常,2-下架,3-删除")
    private Integer status;
}

 po:实体


@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("item")
public class Item implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 商品id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * SKU名称
     */
    private String name;

    /**
     * 价格(分)
     */
    private Integer price;

    /**
     * 库存数量
     */
    private Integer stock;

    /**
     * 商品图片
     */
    private String image;

    /**
     * 类目名称
     */
    private String category;

    /**
     * 品牌名称
     */
    private String brand;

    /**
     * 规格
     */
    private String spec;

    /**
     * 销量
     */
    private Integer sold;

    /**
     * 评论数
     */
    private Integer commentCount;

    /**
     * 是否是推广广告,true/false
     */
    @TableField("isAD")
    private Boolean isAD;

    /**
     * 商品状态 1-正常,2-下架,3-删除
     */
    private Integer status;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;

    /**
     * 创建人
     */
    private Long creater;

    /**
     * 修改人
     */
    private Long updater;


}

vo:视图对象 

在hm-service模块中没有与商品有关的代码。

query:分页查询 

@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "商品分页查询条件")
public class ItemPageQuery extends PageQuery {
    @ApiModelProperty("搜索关键字")
    private String key;
    @ApiModelProperty("商品分类")
    private String category;
    @ApiModelProperty("商品品牌")
    private String brand;
    @ApiModelProperty("价格最小值")
    private Integer minPrice;
    @ApiModelProperty("价格最大值")
    private Integer maxPrice;
}

【2】mapper包代码 :mapper接口 及mapper.xml文件

public interface ItemMapper extends BaseMapper<Item> {

    @Update("UPDATE item SET stock = stock - #{num} WHERE id = #{itemId}")
    void updateStock(OrderDetailDTO orderDetail);
}

【3】 service包:service接口及实现类

service接口: 

public interface IItemService extends IService<Item> {

    void deductStock(List<OrderDetailDTO> items);

    List<ItemDTO> queryItemByIds(Collection<Long> ids);
}

 修改sqlStatement后的实现类:

@Service
public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements IItemService {

    @Override
    public void deductStock(List<OrderDetailDTO> items) {
        String sqlStatement = "com.hmall.mapper.item.ItemMapper.updateStock";
        boolean r = false;
        try {
            r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
        } catch (Exception e) {
            throw new BizIllegalException("更新库存异常,可能是库存不足!", e);
        }
        if (!r) {
            throw new BizIllegalException("库存不足!");
        }
    }

    @Override
    public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
        return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
    }
}

【4】controller包

@Api(tags = "商品管理相关接口")
@RestController
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {

    private final IItemService itemService;

    @ApiOperation("分页查询商品")
    @GetMapping("/page")
    public PageDTO<ItemDTO> queryItemByPage(PageQuery query) {
        // 1.分页查询
        Page<Item> result = itemService.page(query.toMpPage("update_time", false));
        // 2.封装并返回
        return PageDTO.of(result, ItemDTO.class);
    }

    @ApiOperation("根据id批量查询商品")
    @GetMapping
    public List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){
        return itemService.queryItemByIds(ids);
    }

    @ApiOperation("根据id查询商品")
    @GetMapping("{id}")
    public ItemDTO queryItemById(@PathVariable("id") Long id) {
        return BeanUtils.copyBean(itemService.getById(id), ItemDTO.class);
    }

    @ApiOperation("新增商品")
    @PostMapping
    public void saveItem(@RequestBody ItemDTO item) {
        // 新增
        itemService.save(BeanUtils.copyBean(item, Item.class));
    }

    @ApiOperation("更新商品状态")
    @PutMapping("/status/{id}/{status}")
    public void updateItemStatus(@PathVariable("id") Long id, @PathVariable("status") Integer status){
        Item item = new Item();
        item.setId(id);
        item.setStatus(status);
        itemService.updateById(item);
    }

    @ApiOperation("更新商品")
    @PutMapping
    public void updateItem(@RequestBody ItemDTO item) {
        // 不允许修改商品状态,所以强制设置为null,更新时,就会忽略该字段
        item.setStatus(null);
        // 更新
        itemService.updateById(BeanUtils.copyBean(item, Item.class));
    }

    @ApiOperation("根据id删除商品")
    @DeleteMapping("{id}")
    public void deleteItemById(@PathVariable("id") Long id) {
        itemService.removeById(id);
    }

    @ApiOperation("批量扣减库存")
    @PutMapping("/stock/deduct")
    public void deductStock(@RequestBody List<OrderDetailDTO> items){
        itemService.deductStock(items);
    }
}

5. 刷新maven,添加该业务模块启动项到Services中,并把Active profiles 修改为 local

6. 运行,在访问地址后面添加doc.html访问swagger接口文档,进行调试

 三、拆分购物车服务

同上 

四、服务调用(RPC)

背景: 

在拆分的时候,我们发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service服务,导致我们无法查询。(也就是一个模块要进行查询但是查询代码都在另外一个模块) 

那么问题来了:我们该如何跨服务调用,准确的说,如何在cart-service中获取item-service服务中的提供的商品数据呢? 

 因此,现在查询购物车列表的流程变成了这样:

 假如我们在cart-service中能模拟浏览器,发送http请求到item-service,是不是就实现了跨微服务的远程调用了呢(也就是用Java代码发送Http请求)

RestTemplate 

Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。 

其中提供了大量的方法,方便我们发送Http请求,例如: 

我们在cart-service服务中定义一个配置类 

 先将RestTemplate注册为一个Bean:

package com.hmall.cart.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RemoteCallConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

远程直接调用

 在这个过程中,item-service提供了查询接口,cart-service利用Http请求调用该接口。因此item-service可以称为服务的提供者,而cart-service则称为服务的消费者或服务调用者。

五、Nacos注册中心

背景: 

RPC通过Http请求实现了跨微服务的远程调用,这种手动发送Http请求的方式存在一些问题。 

试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署,如图: 

此时,每个item-service的实例其IP或端口不同,问题来了:

  • item-service这么多实例,cart-service如何知道每一个实例的地址?

  • http请求要写url地址,cart-service服务到底该调用哪个实例呢?

  • 如果在运行过程中,某一个item-service实例宕机,cart-service依然在调用该怎么办?

  • 如果并发太高,item-service临时多部署了N台实例,cart-service如何知道新实例的地址?

这时我们需要注册中心 (其实也是建立一个连接池)

流程如下:

  • 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心

  • 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)

  • 调用者自己对实例列表负载均衡,挑选一个实例

  • 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)

  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除

  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表

  • 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表

服务注册

将模块(微服务)注册到Nacos 

 1、添加依赖,配置Nacos

item-servicepom.xml中添加依赖:

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

 在item-serviceapplication.yml中添加nacos地址配置:

将nacos地址修改为自己的虚拟机地址 

spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

 2、启动服务,访问http://192.168.150.101:8848/nacos/控制台,可以发现服务注册成功:

 服务发现

服务的消费者要去nacos订阅服务,这个过程就是服务发现 。

1、  添加依赖,配置Nacos

服务发现除了要引入nacos依赖以外,由于还需要负载均衡,因此要引入SpringCloud提供的LoadBalancer依赖。

我们在cart-service中的pom.xml中添加下面的依赖:

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

 cart-serviceapplication.yml中添加nacos地址配置:

spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848

 2、根据负载均衡算法发现并调用方法

接下来,服务调用者cart-service就可以去订阅item-service服务了。不过item-service有多个实例,而真正发起调用时只需要知道一个实例的地址。

因此,服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:

  • 随机

  • 轮询

  • IP的hash

  • 最近最少访问

  • ...

这里我们可以选择最简单的随机负载均衡。

另外,服务发现需要用到一个工具,DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:

 

 接下来,我们就可以对原来的远程调用做修改了,之前调用时我们需要写死服务提供者的IP和端口:

 但现在不需要了,我们通过DiscoveryClient发现服务实例列表,然后通过负载均衡算法,选择一个实例去调用:

六、OpenFeign 

快速入门

OpenFeign其作用就是基于SpringMVC的常见注解,轻松的实现http请求的发送。 

1、引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer 

  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>

2、通过@EnableFeignClients注解,启用OpenFeign功能

接下来,我们在cart-serviceCartApplication启动类上添加注解,启动OpenFeign功能: 

3、编写OpenFeign客户端 

cart-service中,定义一个新的接口,编写Feign客户端:  

package com.hmall.cart.client;

import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service") //根据服务名称取注册中心
public interface ItemClient {

    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

这里只需要声明接口,无需实现方法。接口中的几个关键信息:

  • @FeignClient("item-service") :声明服务名称

  • @GetMapping :声明请求方式

  • @GetMapping("/items") :声明请求路径

  • @RequestParam("ids") Collection<Long> ids :声明请求参数

  • List<ItemDTO> :返回值类型

有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理为List<ItemDTO>

我们只需要直接调用这个方法,即可实现远程调用了。

连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池

  • Apache HttpClient :支持连接池

  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection(效率不好)。比如,我们使用OK Http.

 1、引入依赖、开启连接池

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

cart-serviceapplication.yml配置文件中开启Feign的连接池功能: 

feign:
  okhttp:
    enabled: true # 开启OKHttp功能

 2、实现

Debug方式启动cart-service,请求一次查询我的购物车方法(发起请求),可以发现这里底层的实现已经改为OkHttpClient

 七、日志输出

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

 定义日志级别

在hm-api模块下新建一个配置类,定义Feign的日志级别: 

 配置

接下来,要让日志级别生效,还需要配置这个类。有两种方式:

  • 局部生效:在某个FeignClient中配置,只对当前FeignClient生效

@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
  • 全局生效:在@EnableFeignClients中配置,针对所有FeignClient生效。

@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

 日志格式:

17:35:32:148 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] ---> GET http://item-service/items?ids=100000006163 HTTP/1.1
17:35:32:148 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] ---> END HTTP (0-byte body)
17:35:32:278 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] <--- HTTP/1.1 200  (127ms)
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] connection: keep-alive
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] content-type: application/json
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] date: Fri, 26 May 2023 09:35:32 GMT
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] keep-alive: timeout=60
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] transfer-encoding: chunked
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] 
17:35:32:280 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] [{"id":100000006163,"name":"巴布豆(BOBDOG)柔薄悦动婴儿拉拉裤XXL码80片(15kg以上)","price":67100,"stock":10000,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t23998/350/2363990466/222391/a6e9581d/5b7cba5bN0c18fb4f.jpg!q70.jpg.webp","category":"拉拉裤","brand":"巴布豆","spec":"{}","sold":11,"commentCount":33343434,"isAD":false,"status":2}]
17:35:32:281 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] <--- END HTTP (369-byte body)

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/693603.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

牛客热题:最长公共子串

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;最长公共子串题目链接方法一&am…

【Redis学习笔记05】Jedis客户端(中)

Jedis客户端 1. 命令 1.1 String类型 1.1.1 常见命令 SET命令 语法&#xff1a;SET key value [EX seconds | PX milliseconds] [NX|XX] 说明&#xff1a;将string类型的value值设置到指定key中&#xff0c;如果之前该key存在&#xff0c;则会覆盖原先的值&#xff0c;原先…

Java--可变参数

1.JDK1.5开始&#xff0c;Java支持同类型的可变参数给一个方法 2.在方法声明之前&#xff0c;在指定参数类型后加一个省略号&#xff08;...&#xff09; 3.一个方法只能指定一个可变参数&#xff0c;它必须是方法的最后一个参数&#xff0c;任何普通的参数必须在它之前声明 …

Java----抽象类和接口

欢迎大家来这次博客-----抽象类和接口。 1.抽象类 1.1 抽象类概念 在Java中我们都是通过类来描述对象&#xff0c;但反过来并不是所有的类都是用来描述对象的。当一个类中没有足够的信息来描述一个具体对象&#xff0c;我们就将该类称为抽象类。 如上图中的Shape类&#xff…

支付宝订单支付和超时收单

下订单成功后&#xff0c;发送mq消息到死信队列&#xff0c;消息过期后根据死信的路由key重新路由到交换机&#xff0c;交换机根据消息的路由key把消息路由到普通队列上&#xff0c;消费者监听队列并消费。 问题&#xff0c;现在提交订单信息调用支付宝的支付页面&#xff0c;…

AI论文速读 | 2024[ICML]FlashST:简单通用的流量预测提示微调框架

题目&#xff1a; FlashST: A Simple and Universal Prompt-Tuning Framework for Traffic Prediction 作者&#xff1a;Zhonghang Li, Lianghao Xia&#xff08;夏良昊&#xff09;, Yong Xu&#xff08;徐勇&#xff09;, Chao Huang 机构&#xff1a;华南理工大学&#xf…

分享不用会员免费听歌的软件,可听付费,支持随听随下!

今天来点特别的&#xff0c;给你们带来几款全网免费听歌的神器&#xff0c;让你们的音乐之旅不再有障碍&#xff01; 现在&#xff0c;找好听的歌越来越像寻宝一样&#xff0c;动不动就得掏腰包。不过别担心&#xff0c;阿星今天就来分享几款好用的免费听歌app&#xff0c;电脑…

SQL(一)基本语法

文章目录 一、Sql 语言基本特点二、数据查询&#xff08;按执行顺序排列&#xff09;1. From & Join2. Where3. Group by4. Having5. Select6. Distinct7. Order by8. Limit/ Offset 三、功能公式1. 字符处理2. 时间处理3. 统计计算 一、Sql 语言基本特点 不区分大小写分号…

数据库(29)——子查询

概念 SQL语句中嵌套SELECT语句&#xff0c;称为嵌套查询&#xff0c;又称子查询。 SELECT * FROM t1 WHERE column1 (SELECT column1 FROM t2); 子查询外部语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个。 标量子查询 子查询返回的结果是单个值&#xff08;数字&#xff…

pdf压缩文件怎么压缩最小,软件工具压缩清晰

PDF格式的文件&#xff0c;当其体积过于庞大时&#xff0c;确实在上传的过程中显得尤为不便。今天给大家分享一个压缩pdf的简单的方法&#xff0c;让大家可以轻松的压缩pdf。 浏览器打开 "轻云处理pdf官网" &#xff0c;上传pdf文件&#xff0c;文件上传完成后网站会…

ChatGPT Prompt技术全攻略-精通篇:Prompt工程技术的高级应用

系列篇章&#x1f4a5; No.文章1ChatGPT Prompt技术全攻略-入门篇&#xff1a;AI提示工程基础2ChatGPT Prompt技术全攻略-进阶篇&#xff1a;深入Prompt工程技术3ChatGPT Prompt技术全攻略-高级篇&#xff1a;掌握高级Prompt工程技术4ChatGPT Prompt技术全攻略-应用篇&#xf…

计算机网络到底是指什么?

计算机网络是信息技术领域中最为核心和复杂的一部分&#xff0c;它涵盖了众多的技术原理和应用。下面&#xff0c;我们将从技术层面深入探讨计算机网络的相关内容。 一、计算机网络的分层模型 计算机网络的分层模型是网络通信的基石&#xff0c;它将网络通信过程划分为不同的层…

万能嗅探:视频号下载神器

万能嗅探是一款比较好用资源嗅探软件&#xff0c;界面干净&#xff0c;可以抓取浏览器的网页&#xff0c;不过想必各位主要用来抓取视频号&#xff0c;下面是使用方法。 使用方法 打开万能嗅探客户端&#xff0c;然后打开浏览器&#xff0c;产生网络请求即可&#xff0c;看看…

【Linux高级IO】select、poll、epoll

【Linux高级IO】select、poll、epoll toc 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2024.6.5 前言&#xff1a;本篇博客将会介绍面试重点考察的select、poll、epoll IO: input && Output read && write 应用层read&&write的时候&#xff0c…

PostgreSQL 17 Beta1 发布,酷克数据再次贡献核心力量

得益于全球的开发者贡献&#xff0c;PostgreSQL已成长为一款拥有众多全球用户和贡献者、成熟稳定的开源数据库。2024年5月23日&#xff0c;PostgreSQL全球开发组宣布&#xff0c;PostgreSQL 17的首个 Beta 版本现已开放下载。本次新版本带来了众多惊喜。值得一提的是&#xff0…

【云原生】基于windows环境搭建Docker

目录 一、Docker Desktop搭建 二、前置准备 2.1开启 Hyper-V 2.2 Hyper-V选项看不到问题解决 2.3 开启或升级wsl 三、安装过程 3.1 下载安装包 3.2 安装 Docker Desktop 3.2.1 Docker 图标一直处于starting状态问题解决 3.3 配置仓库与镜像 3.4 docker功能测试 四、…

NRF52833串口和BLE升级bootloader合并(SDK1710,S113协议栈)

打pca10100_s113_ble_debug工程,将生成的key __ALIGN(4) const uint8_t pk[64] = {0xa3, 0x9a, 0x37, 0xb3, 0x1e, 0x44, 0xb5, 0x77, 0xb3, 0xa4, 0xf3, 0x65, 0xb8, 0xe6, 0xff, 0xa4, 0x33, 0x19, 0x30, 0x0c, 0xd8, 0xaf, 0xc6, 0x5a, 0xdf, 0xd1, 0x8f, 0xf3, 0xf3, 0xd…

TCP/IP协议分析实验:通过一次下载任务抓包分析

TCP/IP协议分析 一、实验简介 本实验主要讲解TCP/IP协议的应用&#xff0c;通过一次下载任务&#xff0c;抓取TCP/IP数据报文&#xff0c;对TCP连接和断开的过程进行分析&#xff0c;查看TCP“三次握手”和“四次挥手”的数据报文&#xff0c;并对其进行简单的分析。 二、实…

手机怎么压缩图片?通过三种压缩操作

手机怎么压缩图片&#xff1f;在智能手机日益普及的今天&#xff0c;拍照分享已成为日常生活的一部分。然而&#xff0c;高质量的照片往往占用较大的存储空间&#xff0c;且在网络上传输时速度较慢。那么&#xff0c;如何在手机上压缩图片呢&#xff1f;本文将介绍三种实用的手…

开源与新质生产力

在这个信息技术迅猛发展的时代&#xff0c;全球范围内的产业都在经历着深刻的变革。在这样的背景下&#xff0c;“新质生产力”的概念引起了广泛的讨论。无论是已经成为或正努力转型成为新质生产力的企业&#xff0c;都在寻求新的增长动力和竞争优势。作为一名长期从事开源领域…