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