当前位置:   article > 正文

微服务第一轮

微服务第一轮

课程文档

目录

一、业务流程

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中的接口: 

  1. @Api(tags = "用户相关接口")
  2. @RestController
  3. @RequestMapping("/users")
  4. @RequiredArgsConstructor
  5. public class UserController {
  6. private final IUserService userService;
  7. @ApiOperation("用户登录接口")
  8. @PostMapping("login")
  9. public UserLoginVO login(@RequestBody @Validated LoginFormDTO loginFormDTO){
  10. return userService.login(loginFormDTO);
  11. }
  12. }

 Service中的实现impl:

  1. public interface IUserService extends IService<User> {
  2. UserLoginVO login(LoginFormDTO loginFormDTO);
  3. void deductMoney(String pw, Integer totalFee);
  4. }

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

  1. public interface IService<T> {
  2. int DEFAULT_BATCH_SIZE = 1000;
  3. default boolean save(T entity) {
  4. return SqlHelper.retBool(this.getBaseMapper().insert(entity));
  5. }
  6. @Transactional(
  7. rollbackFor = {Exception.class}
  8. )
  9. default boolean saveBatch(Collection<T> entityList) {
  10. return this.saveBatch(entityList, 1000);
  11. }
  12. boolean saveBatch(Collection<T> entityList, int batchSize);
  13. @Transactional(
  14. rollbackFor = {Exception.class}
  15. )
  16. default boolean saveOrUpdateBatch(Collection<T> entityList) {
  17. return this.saveOrUpdateBatch(entityList, 1000);
  18. }
  19. boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
  20. }

VO:

  1. @Data
  2. public class UserLoginVO {
  3. private String token;
  4. private Long userId;
  5. private String username;
  6. private Integer balance;
  7. }

 DTO:

  1. @Data
  2. @ApiModel(description = "登录表单实体")
  3. public class LoginFormDTO {
  4. @ApiModelProperty(value = "用户名", required = true)
  5. @NotNull(message = "用户名不能为空")
  6. private String username;
  7. @NotNull(message = "密码不能为空")
  8. @ApiModelProperty(value = "用户名", required = true)
  9. private String password;
  10. @ApiModelProperty(value = "是否记住我", required = false)
  11. private Boolean rememberMe = false;
  12. }

2、搜索商品

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

 Controller中的接口:

  1. @Api(tags = "搜索相关接口")
  2. @RestController
  3. @RequestMapping("/search")
  4. @RequiredArgsConstructor
  5. public class SearchController {
  6. private final IItemService itemService;
  7. @ApiOperation("搜索商品")
  8. @GetMapping("/list")
  9. public PageDTO<ItemDTO> search(ItemPageQuery query) {
  10. // 分页查询
  11. Page<Item> result = itemService.lambdaQuery()
  12. .like(StrUtil.isNotBlank(query.getKey()), Item::getName, query.getKey())
  13. .eq(StrUtil.isNotBlank(query.getBrand()), Item::getBrand, query.getBrand())
  14. .eq(StrUtil.isNotBlank(query.getCategory()), Item::getCategory, query.getCategory())
  15. .eq(Item::getStatus, 1)
  16. .between(query.getMaxPrice() != null, Item::getPrice, query.getMinPrice(), query.getMaxPrice())
  17. .page(query.toMpPage("update_time", false));
  18. // 封装并返回
  19. return PageDTO.of(result, ItemDTO.class);
  20. }
  21. }

3、购物车

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

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

 Controller中的接口:

  1. @Api(tags = "购物车相关接口")
  2. @RestController
  3. @RequestMapping("/carts")
  4. @RequiredArgsConstructor
  5. public class CartController {
  6. private final ICartService cartService;
  7. @ApiOperation("添加商品到购物车")
  8. @PostMapping
  9. public void addItem2Cart(@Valid @RequestBody CartFormDTO cartFormDTO){
  10. cartService.addItem2Cart(cartFormDTO);
  11. }
  12. @ApiOperation("更新购物车数据")
  13. @PutMapping
  14. public void updateCart(@RequestBody Cart cart){
  15. cartService.updateById(cart);
  16. }
  17. @ApiOperation("删除购物车中商品")
  18. @DeleteMapping("{id}")
  19. public void deleteCartItem(@Param ("购物车条目id")@PathVariable("id") Long id){
  20. cartService.removeById(id);
  21. }
  22. @ApiOperation("查询购物车列表")
  23. @GetMapping
  24. public List<CartVO> queryMyCarts(){
  25. return cartService.queryMyCarts();
  26. }
  27. @ApiOperation("批量删除购物车中商品")
  28. @ApiImplicitParam(name = "ids", value = "购物车条目id集合")
  29. @DeleteMapping
  30. public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids){
  31. cartService.removeByItemIds(ids);
  32. }
  33. }

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

二、拆分商品服务

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

创建新模块 

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

 添加依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>com.heima</groupId>
  8. <artifactId>hmall</artifactId>
  9. <version>1.0.0</version>
  10. </parent>
  11. <groupId>org.qingshui</groupId>
  12. <artifactId>item-service</artifactId>
  13. <properties>
  14. <maven.compiler.source>11</maven.compiler.source>
  15. <maven.compiler.target>11</maven.compiler.target>
  16. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  17. </properties>
  18. <dependencies>
  19. <!--common-->
  20. <dependency>
  21. <groupId>com.heima</groupId>
  22. <artifactId>hm-common</artifactId>
  23. <version>1.0.0</version>
  24. </dependency>
  25. <!--web-->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-web</artifactId>
  29. </dependency>
  30. <!--数据库-->
  31. <dependency>
  32. <groupId>mysql</groupId>
  33. <artifactId>mysql-connector-java</artifactId>
  34. </dependency>
  35. <!--mybatis-->
  36. <dependency>
  37. <groupId>com.baomidou</groupId>
  38. <artifactId>mybatis-plus-boot-starter</artifactId>
  39. </dependency>
  40. <!--单元测试-->
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-test</artifactId>
  44. <version>3.3.0</version>
  45. <scope>test</scope>
  46. </dependency>
  47. <dependency>
  48. <groupId>junit</groupId>
  49. <artifactId>junit</artifactId>
  50. <scope>test</scope>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.junit.jupiter</groupId>
  54. <artifactId>junit-jupiter</artifactId>
  55. <scope>test</scope>
  56. </dependency>
  57. </dependencies>
  58. <build>
  59. <finalName>${project.artifactId}</finalName>
  60. <plugins>
  61. <plugin>
  62. <groupId>org.springframework.boot</groupId>
  63. <artifactId>spring-boot-maven-plugin</artifactId>
  64. </plugin>
  65. </plugins>
  66. </build>
  67. </project>

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

 新建包com.hmall.item: 

 编写启动类ItemApplication:

  1. @MapperScan("com.hmall.item.mapper")
  2. @SpringBootApplication
  3. public class ItemApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ItemApplication.class, args);
  6. }
  7. }

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

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

从hm-service中拷贝:

修改application.yaml为:

  1. server:
  2. port: 8081
  3. spring:
  4. application:
  5. name: item-service
  6. profiles:
  7. active: dev
  8. datasource:
  9. url: jdbc:mysql://${hm.db.host}:3306/hm-item?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
  10. driver-class-name: com.mysql.cj.jdbc.Driver
  11. username: root
  12. password: ${hm.db.pw}
  13. mybatis-plus:
  14. configuration:
  15. default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  16. global-config:
  17. db-config:
  18. update-strategy: not_null
  19. id-type: auto
  20. logging:
  21. level:
  22. com.hmall: debug
  23. pattern:
  24. dateformat: HH:mm:ss:SSS
  25. file:
  26. path: "logs/${spring.application.name}"
  27. knife4j:
  28. enable: true
  29. openapi:
  30. title: 商品服务接口文档
  31. description: "信息"
  32. email: zhanghuyi@itcast.cn
  33. concat: 虎哥
  34. url: https://www.itcast.cn
  35. version: v1.0.0
  36. group:
  37. default:
  38. group-name: default
  39. api-rule: package
  40. api-rule-resources:
  41. - com.hmall.item.controller

创建该模块的数据库: 

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

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

dto:数据传输对象 

OrderDetailDTO: 

  1. @ApiModel(description = "订单明细条目")
  2. @Data
  3. @Accessors(chain = true)
  4. public class OrderDetailDTO {
  5. @ApiModelProperty("商品id")
  6. private Long itemId;
  7. @ApiModelProperty("商品购买数量")
  8. private Integer num;
  9. }

 ItemDTO:

  1. @Data
  2. @ApiModel(description = "商品实体")
  3. public class ItemDTO {
  4. @ApiModelProperty("商品id")
  5. private Long id;
  6. @ApiModelProperty("SKU名称")
  7. private String name;
  8. @ApiModelProperty("价格(分)")
  9. private Integer price;
  10. @ApiModelProperty("库存数量")
  11. private Integer stock;
  12. @ApiModelProperty("商品图片")
  13. private String image;
  14. @ApiModelProperty("类目名称")
  15. private String category;
  16. @ApiModelProperty("品牌名称")
  17. private String brand;
  18. @ApiModelProperty("规格")
  19. private String spec;
  20. @ApiModelProperty("销量")
  21. private Integer sold;
  22. @ApiModelProperty("评论数")
  23. private Integer commentCount;
  24. @ApiModelProperty("是否是推广广告,true/false")
  25. private Boolean isAD;
  26. @ApiModelProperty("商品状态 1-正常,2-下架,3-删除")
  27. private Integer status;
  28. }

 po:实体

  1. @Data
  2. @EqualsAndHashCode(callSuper = false)
  3. @Accessors(chain = true)
  4. @TableName("item")
  5. public class Item implements Serializable {
  6. private static final long serialVersionUID = 1L;
  7. /**
  8. * 商品id
  9. */
  10. @TableId(value = "id", type = IdType.AUTO)
  11. private Long id;
  12. /**
  13. * SKU名称
  14. */
  15. private String name;
  16. /**
  17. * 价格(分)
  18. */
  19. private Integer price;
  20. /**
  21. * 库存数量
  22. */
  23. private Integer stock;
  24. /**
  25. * 商品图片
  26. */
  27. private String image;
  28. /**
  29. * 类目名称
  30. */
  31. private String category;
  32. /**
  33. * 品牌名称
  34. */
  35. private String brand;
  36. /**
  37. * 规格
  38. */
  39. private String spec;
  40. /**
  41. * 销量
  42. */
  43. private Integer sold;
  44. /**
  45. * 评论数
  46. */
  47. private Integer commentCount;
  48. /**
  49. * 是否是推广广告,true/false
  50. */
  51. @TableField("isAD")
  52. private Boolean isAD;
  53. /**
  54. * 商品状态 1-正常,2-下架,3-删除
  55. */
  56. private Integer status;
  57. /**
  58. * 创建时间
  59. */
  60. private LocalDateTime createTime;
  61. /**
  62. * 更新时间
  63. */
  64. private LocalDateTime updateTime;
  65. /**
  66. * 创建人
  67. */
  68. private Long creater;
  69. /**
  70. * 修改人
  71. */
  72. private Long updater;
  73. }

vo:视图对象 

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

query:分页查询 

  1. @EqualsAndHashCode(callSuper = true)
  2. @Data
  3. @ApiModel(description = "商品分页查询条件")
  4. public class ItemPageQuery extends PageQuery {
  5. @ApiModelProperty("搜索关键字")
  6. private String key;
  7. @ApiModelProperty("商品分类")
  8. private String category;
  9. @ApiModelProperty("商品品牌")
  10. private String brand;
  11. @ApiModelProperty("价格最小值")
  12. private Integer minPrice;
  13. @ApiModelProperty("价格最大值")
  14. private Integer maxPrice;
  15. }

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

  1. public interface ItemMapper extends BaseMapper<Item> {
  2. @Update("UPDATE item SET stock = stock - #{num} WHERE id = #{itemId}")
  3. void updateStock(OrderDetailDTO orderDetail);
  4. }

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

service接口: 

  1. public interface IItemService extends IService<Item> {
  2. void deductStock(List<OrderDetailDTO> items);
  3. List<ItemDTO> queryItemByIds(Collection<Long> ids);
  4. }

 修改sqlStatement后的实现类:

  1. @Service
  2. public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements IItemService {
  3. @Override
  4. public void deductStock(List<OrderDetailDTO> items) {
  5. String sqlStatement = "com.hmall.mapper.item.ItemMapper.updateStock";
  6. boolean r = false;
  7. try {
  8. r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
  9. } catch (Exception e) {
  10. throw new BizIllegalException("更新库存异常,可能是库存不足!", e);
  11. }
  12. if (!r) {
  13. throw new BizIllegalException("库存不足!");
  14. }
  15. }
  16. @Override
  17. public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
  18. return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
  19. }
  20. }

【4】controller包

  1. @Api(tags = "商品管理相关接口")
  2. @RestController
  3. @RequestMapping("/items")
  4. @RequiredArgsConstructor
  5. public class ItemController {
  6. private final IItemService itemService;
  7. @ApiOperation("分页查询商品")
  8. @GetMapping("/page")
  9. public PageDTO<ItemDTO> queryItemByPage(PageQuery query) {
  10. // 1.分页查询
  11. Page<Item> result = itemService.page(query.toMpPage("update_time", false));
  12. // 2.封装并返回
  13. return PageDTO.of(result, ItemDTO.class);
  14. }
  15. @ApiOperation("根据id批量查询商品")
  16. @GetMapping
  17. public List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){
  18. return itemService.queryItemByIds(ids);
  19. }
  20. @ApiOperation("根据id查询商品")
  21. @GetMapping("{id}")
  22. public ItemDTO queryItemById(@PathVariable("id") Long id) {
  23. return BeanUtils.copyBean(itemService.getById(id), ItemDTO.class);
  24. }
  25. @ApiOperation("新增商品")
  26. @PostMapping
  27. public void saveItem(@RequestBody ItemDTO item) {
  28. // 新增
  29. itemService.save(BeanUtils.copyBean(item, Item.class));
  30. }
  31. @ApiOperation("更新商品状态")
  32. @PutMapping("/status/{id}/{status}")
  33. public void updateItemStatus(@PathVariable("id") Long id, @PathVariable("status") Integer status){
  34. Item item = new Item();
  35. item.setId(id);
  36. item.setStatus(status);
  37. itemService.updateById(item);
  38. }
  39. @ApiOperation("更新商品")
  40. @PutMapping
  41. public void updateItem(@RequestBody ItemDTO item) {
  42. // 不允许修改商品状态,所以强制设置为null,更新时,就会忽略该字段
  43. item.setStatus(null);
  44. // 更新
  45. itemService.updateById(BeanUtils.copyBean(item, Item.class));
  46. }
  47. @ApiOperation("根据id删除商品")
  48. @DeleteMapping("{id}")
  49. public void deleteItemById(@PathVariable("id") Long id) {
  50. itemService.removeById(id);
  51. }
  52. @ApiOperation("批量扣减库存")
  53. @PutMapping("/stock/deduct")
  54. public void deductStock(@RequestBody List<OrderDetailDTO> items){
  55. itemService.deductStock(items);
  56. }
  57. }

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:

  1. package com.hmall.cart.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.client.RestTemplate;
  5. @Configuration
  6. public class RemoteCallConfig {
  7. @Bean
  8. public RestTemplate restTemplate() {
  9. return new RestTemplate();
  10. }
  11. }

远程直接调用

 在这个过程中,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中添加依赖:

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

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

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

  1. spring:
  2. application:
  3. name: item-service # 服务名称
  4. cloud:
  5. nacos:
  6. 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中添加下面的依赖:

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

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

  1. spring:
  2. cloud:
  3. nacos:
  4. 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 

  1. <!--openFeign-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>
  6. <!--负载均衡器-->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  10. </dependency>

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

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

3、编写OpenFeign客户端 

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

  1. package com.hmall.cart.client;
  2. import com.hmall.cart.domain.dto.ItemDTO;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestParam;
  6. import java.util.List;
  7. @FeignClient("item-service") //根据服务名称取注册中心
  8. public interface ItemClient {
  9. @GetMapping("/items")
  10. List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
  11. }

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

  • @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、引入依赖、开启连接池

  1. <!--OK http 的依赖 -->
  2. <dependency>
  3. <groupId>io.github.openfeign</groupId>
  4. <artifactId>feign-okhttp</artifactId>
  5. </dependency>

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

  1. feign:
  2. okhttp:
  3. 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)

 日志格式:

  1. 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
  2. 17:35:32:148 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] ---> END HTTP (0-byte body)
  3. 17:35:32:278 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] <--- HTTP/1.1 200 (127ms)
  4. 17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] connection: keep-alive
  5. 17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] content-type: application/json
  6. 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
  7. 17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] keep-alive: timeout=60
  8. 17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] transfer-encoding: chunked
  9. 17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds]
  10. 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}]
  11. 17:35:32:281 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient : [ItemClient#queryItemByIds] <--- END HTTP (369-byte body)

 

 

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

闽ICP备14008679号