一生懸命勉強すれば、毎日上達します
事前準備
検索マイクロサービスのAPIプロジェクト構築
<dependencies>
<!--goods API依存関係>
<dependency>
<groupId>com.robod</groupId>
<artifactId>changgou-service-goods-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringDataES依存関係>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
検索マイクロサービス構築
検索マイクロサービスとしてchanggou-service の下に新しい changgou-service-search プロジェクトを作成します。検索マイクロサービスの内部では、APIプロジェクトのJavaBeanとFeignインタフェースを使用する必要があるので、依存関係としてsearch-apiとgoods-apiを追加します。
<dependencies>
<!--依存検索api>
<dependency>
<groupId>com.robod</groupId>
<artifactId>changgou-service-search-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.robod</groupId>
<artifactId>changgou-service-goods-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
スタートアップクラスとコンフィギュレーションファイルは必須。
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.robod.goods.feign")
@EnableElasticsearchRepositories(basePackages = "com.robod.mapper")
public class SearchApplication {
public static void main(String[] args) {
//SpringBootのnettyとelasticsearchのnetty関連のjarの競合を解決する
System.setProperty("es.set.netty.runtime.available.processors", "false");
SpringApplication.run(SearchApplication.class,args);
}
}
server:
port: 18085
spring:
application:
name: search
data:
elasticsearch:
cluster-name: my-application # クラスタノードの名前はesの設定ファイルで設定する。
cluster-nodes: 192.0:9300 # ここでは、TCPポートを使用するので、9300である。
eureka:
client:
service-url:
defaultZone: "http://...1:7100"/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#タイムアウトの設定
ribbon:
ReadTimeout: 500000 # Feignデータの読み込み要求タイムアウト時間
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 50000 # feign接続タイムアウト
データインポート ES
MySQLからESへのデータインポートは、大まかに以下のステップに分けられます:
まずJavaBeanを作成し、関連するマッピング・コンフィギュレーション、インデックス、タイプ、フィールドを定義します:
@Data
@Document(indexName = "sku_info", type = "docs")
public class SkuInfo implements Serializable {
@Id
private Long id;//商品IDだけでなく商品番号も
/**
* SKU
* FieldType.Text単語の分割をサポートする
* analyzer 単語分割のインデックスを作成する
* searchAnalyzer 検索に使用するセグメンター
*/
@Field(type = FieldType.Text, analyzer = "ik_smart",searchAnalyzer = "ik_smart")
private String name;
@Field(type = FieldType.Double)
private Long price;//商品価格、単位:元
private Integer num;//在庫数
private String image;//商品イメージ
private String status;//製品ステータス, 1-通常, 2-ダウンロード済み, 3-削除済み
private LocalDateTime createTime;//作成時間
private LocalDateTime updateTime;//更新時間
private String isDefault; //デフォルトの
private Long spuId;//SPU_ID
private Long categoryId;//カテゴリID
@Field(type = FieldType.Keyword)
private String categoryName;//単語分割されていないカテゴリ名
@Field(type = FieldType.Keyword)
private String brandName;//ブランド名、サブ分類されていない
private String spec;//
private Map<String, Object> specMap;//仕様パラメータ
}
SkuInfoで、Indexを "sku_info "に、Tpyeを "docs "に設定し、いくつかのフィールドに区切りを設定します。そして
@FeignClient(name = "goods")
@RequestMapping("/sku")
public interface SkuFeign {
/**
* 全ての SKU データを検索する
* @return
*/
@GetMapping
Result<List<Sku>> findAll();
}
このFeignを使用して、GoodsマイクロサービスのfindAllメソッドを呼び出し、データベース内のすべてのSkuデータを取得します。最後に、データインポート機能を実装します。
//SkuEsController
@GetMapping("/import")
public Result importData(){
skuEsService.importData();
return new Result(true, StatusCode.OK,"データは正常にインポートされた");
}
-----------------------------------------------------------
//SkuEsServiceImpl
@Override
public void importData() {
List<Sku> skuList = skuFeign.findAll().getData();
List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skuList), SkuInfo.class);
//マップに文字列を指定すると、マップのキーが自動的にフィールドを生成する
for (SkuInfo skuInfo : skuInfos) {
Map<String,Object> map = JSON.parseObject(skuInfo.getSpec(),Map.class);
skuInfo.setSpecMap(map);
}
skuEsMapper.saveAll(skuInfos);
}
-------------------------------------------------------------
//ElasticsearchRepository から継承、総称型 SkuInfo、主キー型 Long
public interface SkuEsMapper extends ElasticsearchRepository<SkuInfo,Long> {
}
プログラムを起動し、"http://localhost:18085"/search/importにアクセスしてインポートを開始します。
長い待ち時間の後、90,000以上のデータがESにインポートされました。少し時間がかかり、15分ほどかかりましたが、これは仮想マシンの設定に関係していると思われます。
インデックスに問題があるようです。インデックスを削除し、プロジェクトを開始すれば問題ありません!
機能の実装
キーワード検索
この機能を実装する前に、フロントエンドとバックエンドでパラメータを渡す際のフォーマットを指定する必要があります。ビデオではMapを使っていますが、Mapは可読性が悪すぎて良くないと思います。そこで、search-apiプロジェクトにSearchEntityを追加しました:
@Data
public class SearchEntity {
private long total; //検索結果のレコード総数
private int totalPages; //クエリ結果の総ページ数
private List<SkuInfo> rows; //検索結果のコレクション
public SearchEntity() {
}
public SearchEntity(List<SkuInfo> rows, long total, int totalPages) {
this.rows = rows;
this.total = total;
this.totalPages = totalPages;
}
}
あとは、検索マイクロサービスに適切なコードを書くだけです。
@GetMapping
public Result<SearchEntity> searchByKeywords(@RequestParam(required = false)String keywords) {
SearchEntity searchEntity = skuEsService.searchByKeywords(keywords);
return new Result<>(true,StatusCode.OK,"キーワード検索の成功によると、",searchEntity);
}
---------------------------------------------------------------------------------------------------
@Override
public SearchEntity searchByKeywords(String keywords) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
if (!StringUtils.isEmpty(keywords)) {
nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
}
AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate
.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
List<SkuInfo> content = skuInfos.getContent();
return new SearchEntity(content,skuInfos.getTotalElements(),skuInfos.getTotalPages());
}
"http://localhost:18085"/search?keywords=
その後、プロジェクトを立ち上げてアクセスしたところ、FAILED TO MAPが報告され、エラーメッセージの中に次のようなものがありました:
おそらくこれは、LocalDateTimeに何か問題があったことを意味し、Dateクラスがあまりよくなかったので、LocaDateTimeに変更しました。
/**
* LocalDateTimeが複数のFieldに分かれていないことを実現するために、最後の2つのアノテーションのみ使用できるが、フォーマットが正しくない。
* フォーマットとタイムゾーンを指定するために、最初の2つのアノテーションを追加する必要がある。
**/
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;//作成時間
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;//更新時間
もう一度インポートしてください。
フォーマットがうまくいったので、もう一度テストしてみましょう。
わかりました!
シャオミモールのトップで商品を検索すると、下にカテゴリーが表示され、商品を絞り込むことができます。また、ショッピングモールのテーブルデザインにcategoryNameというフィールドがあります。次のステップは、分類統計のためのデータから検索を実装することです。
MySQLの代わりにElasticsearchを使用することで、図のような効果を得ることができます。
SearchEntity を修正して、categoryList フィールドを追加します:
private List<String> categoryList; //分類コレクション
SkuEsServiceImplのsearchByKeywordsメソッドを修正し、グループ統計用のコードを追加します:
public SearchEntity searchByKeywords(String keywords) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
if (!StringUtils.isEmpty(keywords)) {
nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
//terms: Create a new aggregation with the given name.
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categories_grouping")
.field("categoryName"));
}
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate.queryForPage(nativeSearchQuery, SkuInfo.class);
StringTerms stringTerms = skuInfos.getAggregations().get("categories_grouping");
List<String> categoryList = new ArrayList<>();
for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
categoryList.add(bucket.getKeyAsString());
}
return new SearchEntity(skuInfos.getTotalElements(),skuInfos.getTotalPages(),
categoryList,skuInfos.getContent());
}
もう一度テストしてください:
OK!統計のグループ化が実装されました。
概要
本記事では、主にElasticsearchの環境構築から、データをESにインポートするところまで書きます。最後に、キーワード検索と分類統計の機能を実装します。
コード: