Java Network Shop Project SpringBoot+SpringCloud+Vue Network Shop (SSM front-end and back-end separation project) Thirteen (Feign interface call best implementation)

Posted by ashii on Sun, 19 Dec 2021 23:36:28 +0100

1. Create a search service

(1) Create a module:



(3) Introducing dependency

<?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">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.service</groupId>
    <artifactId>ly-search</artifactId>

    <dependencies>
        <!--eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--elasticsearch-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <version>2.5.3</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.leyou.service</groupId>
            <artifactId>ly-item-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

(4) Profile


server:
  port: 8085
spring:
  application:
    name: search-service
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 165.149.69.135:9300
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    lease-renewal-interval-in-seconds: 5 # Send a heartbeat every 5 seconds
    lease-expiration-duration-in-seconds: 10 # Expire without sending in 10 seconds
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port}

(5) Startup class


package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LySearchApplication {

    public static void main(String[] args) {

        SpringApplication.run(LySearchApplication.class);

    }
}

2. Data Format Analysis of Index Base

Next, we need to import commodity data into the index library for easy user search.

So the question is, we have SPU s and SKU s, how do we save them in the index library?

(1) Result-oriented


As you can see, each search result has at least one item, and when we select the small one below the big one, the item changes.

Therefore, the result of the search is the SPU, a collection of multiple SKU s.

Since the result of the search is SPU, we should also store SPU in the index library, but we need to include SKU information.

(2) What data is needed

Let's see what the data is on the page:

Visual: Picture, Price, Title, Subtitle

Hidden data: spu id, sku ID

In addition, the page has filter conditions:

These filter conditions also need to be stored in the index library, including:

Categories, brands, and other specifications that can be searched for

To sum up, the data formats we need are:

spuId, SkuId, commodity classification id, brand id, picture, price, creation time of commodity, sku information set, searchable specification parameters

(2) Final data structure

We create a class that encapsulates the data to be saved to the index library and sets the mapping properties:

package com.leyou.search.pojo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Data
@Document(indexName = "goods", type = "docs", shards = 1, replicas = 0)
public class Goods {
    @Id
    private Long id; // spuId
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String all; // All the information you need to be searched for, including titles, categories, and even brands

    @Field(type = FieldType.Keyword, index = false)  //type = FieldType.Keyword does not search, index = false does not split words
    private String subTitle;// selling point

    private Long brandId;// Brand id
    private Long cid1;// Level 1 Classification id
    private Long cid2;// Level 2 classification id
    private Long cid3;// Level 3 Classification id

    private Date createTime;// Creation Time
    private List<Long> price;// Price

    @Field(type = FieldType.Keyword, index = false)
    private String skus;// json structure of sku information
    private Map<String, Object> specs;// Searchable specification parameter, key is parameter name, value is parameter value
    //TODO getter setter toString
}

Some special field explanations:

  • all: The field used for full-text retrieval that contains title, commodity classification information

  • Price: A price array, which is a collection of prices for all skUs. Easy to filter based on price

  • skus: sku information for page display, not indexed, not searched. Contains skuId, image, price, title fields

  • specs: A collection of all searchable specification parameters. The key is the parameter name and the value is the parameter value.

    For example, we store memory in specs: 4G,6G, red, json:

    {
        "specs":{
            "Memory":[4G,6G],
            "colour":"gules"
        }
    }
    

    When stored in an index library, elasticsearch handles two fields:

    • specs. Memory: [4G,6G]
    • specs. Color: Red

    In addition, for string types, an additional field is stored that does not split words and is used as an aggregation.

    • specs. Color. keyword: red

3. Commodity micro-service providers interface

Indexed databases contain data from databases, and we cannot directly query commodity databases, because each microservice is independent of each other in real development, including databases. Therefore, we can only invoke interface services provided by commercial microservices.

First think about the data we need:

  • SPU Information

  • SKU Information

  • SPU Details

  • Category Name (Split all Field)

  • Specification parameters

  • brand

Rethink about what services we need:

  • First: Query spu services in batches, which have already been written.
  • Second: Query sku services based on spuld
  • Third: Querying SpuDetail services based on spuld has been written
  • Fourth, according to the commodity classification id, query the commodity classification, not written
  • Fifth: Specification parameters: written
  • Sixth: Brand, not written

Therefore, we need to provide an additional interface to query the names of commodity classifications.

(1) Category name query of Category Controller in ly-item-service

 /*
    Interface for querying commodity classification based on id
     */
    @GetMapping("list/ids")
    public ResponseEntity<List<Category>> queryCategoryByIds(@RequestParam("ids") List<Long> ids){
        return ResponseEntity.ok(categoryService.queryByIds(ids));
    }

(2) Query Brand ById Method in BrandController

    /*
    Query brand by id
     */
    @GetMapping("{id}")
    public ResponseEntity<Brand> queryBrandById(@PathVariable("id")Long id){

        return ResponseEntity.ok(brandService.queryById(id));
    }

(3) Running tests

http://localhost:8083/category/list/ids?ids=1,2,3

http://localhost:8083/brand/1

4. Write a FeignClient (where the method in the interface corresponds to Controller but simply removes ResponseEntity)

(1) Create CategoryClient interface


package com.leyou.search.client;

import com.leyou.item.pojo.Category;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service")
public interface CategoryClient {

    @GetMapping("category/list/ids")
    List<Category> queryCategoryByIds(@RequestParam("ids") List<Long> ids);

}

Create unit tests


Run Successfully

(2) Create GoodsClient


package com.leyou.search.client;

import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.Sku;
import com.leyou.item.pojo.Spu;
import com.leyou.item.pojo.SpuDetail;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service")
public interface GoodsClient {

    @GetMapping("/spu/detail/{id}")
    SpuDetail queryDetailById(@PathVariable("id") Long spuId);
    /*
    Query all its sku via spu
     */
    @GetMapping("sku/list")
    List<Sku> querySkuBySpuId(@RequestParam("id") Long spuId);

    @GetMapping("/spu/page")
    PageResult<Spu> querySpuByPage(
            @RequestParam(value = "page",defaultValue = "1") Integer page,
            @RequestParam(value = "rows",defaultValue = "5") Integer rows,
            @RequestParam(value = "saleable",required = false) Boolean saleable,
            @RequestParam(value = "key",required = false) String key
    );

}

These codes are copied directly from the commercial microservice and are identical. The difference is that there is no concrete implementation of the method. Do you think this is a problem?

The FeignClient code follows the SpringMVC style and is therefore in full agreement with the Controller for commercial microservices. There are problems with this:

  • Code redundancy. Although you don't need to write the implementation, you just write the interface, the service caller writes code that is consistent with the service controller, and several consumers write it several times.
  • Increase development costs. The caller also needs to know the path of the interface before writing the correct FeignClient.

Solution:

Therefore, a more friendly practice is to: (Service providers provide not only entity classes but also api interface declarations)

  • Our service provider provides not only entity classes but also api interface declarations
  • Instead of writing interface method declarations in bytes, the caller directly inherits the Api interface from the provider.

(3) Create in ly-item-interface

1) Introducing dependencies

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.leyou.common</groupId>
            <artifactId>ly-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

2) Create GoodsApi



package com.leyou.item.api;

import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.Sku;
import com.leyou.item.pojo.Spu;
import com.leyou.item.pojo.SpuDetail;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

public interface GoodsApi {
    @GetMapping("/spu/detail/{id}")
    SpuDetail queryDetailById(@PathVariable("id") Long spuId);
    /*
    Query all its sku via spu
     */
    @GetMapping("sku/list")
    List<Sku> querySkuBySpuId(@RequestParam("id") Long spuId);

    @GetMapping("/spu/page")
    PageResult<Spu> querySpuByPage(
            @RequestParam(value = "page",defaultValue = "1") Integer page,
            @RequestParam(value = "rows",defaultValue = "5") Integer rows,
            @RequestParam(value = "saleable",required = false) Boolean saleable,
            @RequestParam(value = "key",required = false) String key
    );
}

(4) GoodsClient inherits GoodsApi

package com.leyou.search.client;

import com.leyou.item.api.GoodsApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("item-service")
public interface GoodsClient extends GoodsApi {


}

(5) Improve CategoryClient

1) Create CategoryApi interface in ly-item-interface


package com.leyou.item.api;

import com.leyou.item.pojo.Category;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

public interface CategoryApi {

    @GetMapping("category/list/ids")
    List<Category> queryCategoryByIds(@RequestParam("ids") List<Long> ids);

}

2) Perfect CategoryClient

package com.leyou.search.client;

import com.leyou.item.api.CategoryApi;
import org.springframework.cloud.openfeign.FeignClient;


@FeignClient("item-service")
public interface CategoryClient extends CategoryApi {


}

(6) Create other related Api s

1)BrandApi


package com.leyou.item.api;

import com.leyou.item.pojo.Brand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

public interface BrandApi {
    /*
       Query brand by id
        */
    @GetMapping("brand/{id}")
    Brand queryBrandById(@PathVariable("id")Long id);

}

2)SpecificationApi

package com.leyou.item.api;

import com.leyou.item.pojo.SpecParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

public interface SpecificationApi {

    @GetMapping("spec/params")
    List<SpecParam> queryParamList(
            @RequestParam(value = "gid",required = false) Long gid,
            @RequestParam(value = "cid",required = false) Long cid, //Can require current parameter settings be or not
            @RequestParam(value = "searching",required = false) Boolean searching //searching Sets whether to Search
    );

}

(7) Create other client s

1)SpecificationClient


package com.leyou.search.client;

import com.leyou.item.api.SpecificationApi;
import org.springframework.cloud.openfeign.FeignClient;


@FeignClient("item-service")
public interface SpecificationClient extends SpecificationApi {
}

2)BrandClient


package com.leyou.search.client;

import com.leyou.item.api.BrandApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}

3) Run the test Run the test method just again


Topics: Java Spring Spring Boot Spring Cloud intellij-idea