chao 4 vuotta sitten
vanhempi
commit
6a5846dd4c
35 muutettua tiedostoa jossa 4450 lisäystä ja 52 poistoa
  1. 172 16
      pom.xml
  2. 4 4
      src/main/java/com/caimei365/commodity/CommodityApplication.java
  3. 294 0
      src/main/java/com/caimei365/commodity/components/RedisService.java
  4. 361 0
      src/main/java/com/caimei365/commodity/components/SearchOpenService.java
  5. 1 1
      src/main/java/com/caimei365/commodity/config/LoadBalanceConfiguration.java
  6. 208 0
      src/main/java/com/caimei365/commodity/controller/SearchIndexApi.java
  7. 52 0
      src/main/java/com/caimei365/commodity/controller/SearchProductApi.java
  8. 92 0
      src/main/java/com/caimei365/commodity/controller/SearchQueryApi.java
  9. 4 13
      src/main/java/com/caimei365/commodity/controller/TestWebClientApi.java
  10. 203 0
      src/main/java/com/caimei365/commodity/mapper/SearchMapper.java
  11. 85 0
      src/main/java/com/caimei365/commodity/model/ResponseJson.java
  12. 48 0
      src/main/java/com/caimei365/commodity/model/search/ArticleDO.java
  13. 48 0
      src/main/java/com/caimei365/commodity/model/search/DbArticleDO.java
  14. 15 0
      src/main/java/com/caimei365/commodity/model/search/DocumentDTO.java
  15. 29 0
      src/main/java/com/caimei365/commodity/model/search/EquipmentDO.java
  16. 26 0
      src/main/java/com/caimei365/commodity/model/search/MainDO.java
  17. 33 0
      src/main/java/com/caimei365/commodity/model/search/MallProductDO.java
  18. 75 0
      src/main/java/com/caimei365/commodity/model/search/ProductDO.java
  19. 40 0
      src/main/java/com/caimei365/commodity/model/search/SupplierDO.java
  20. 113 0
      src/main/java/com/caimei365/commodity/model/vo/PageVo.java
  21. 44 0
      src/main/java/com/caimei365/commodity/model/vo/ProductListVo.java
  22. 108 0
      src/main/java/com/caimei365/commodity/service/SearchIndexService.java
  23. 20 0
      src/main/java/com/caimei365/commodity/service/SearchProductService.java
  24. 44 0
      src/main/java/com/caimei365/commodity/service/SearchQueryService.java
  25. 1093 0
      src/main/java/com/caimei365/commodity/service/impl/SearchIndexServiceImpl.java
  26. 159 0
      src/main/java/com/caimei365/commodity/service/impl/SearchProductServiceImpl.java
  27. 290 0
      src/main/java/com/caimei365/commodity/service/impl/SearchQueryServiceImpl.java
  28. 115 0
      src/main/java/com/caimei365/commodity/utils/ImageUtils.java
  29. 51 0
      src/main/java/com/caimei365/commodity/utils/Json2PojoUtil.java
  30. 137 0
      src/main/java/com/caimei365/commodity/utils/PriceUtil.java
  31. 1 1
      src/main/java/com/caimei365/commodity/utils/WebClientUtils.java
  32. 0 16
      src/main/resources/application.yml
  33. 15 0
      src/main/resources/bootstrap.yml
  34. 469 0
      src/main/resources/mapper/SearchMapper.xml
  35. 1 1
      src/test/java/com/caimei365/commodity/ProductApplicationTests.java

+ 172 - 16
pom.xml

@@ -5,17 +5,17 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.4.3</version>
-        <relativePath/> <!-- lookup parent from repository -->
+        <version>2.4.4</version>
+        <relativePath/>
     </parent>
-    <groupId>com.caimei365.product</groupId>
-    <artifactId>caimei365-cloud-product</artifactId>
+    <groupId>com.caimei365.commodity</groupId>
+    <artifactId>caimei365-cloud-commodity</artifactId>
     <version>0.0.1-SNAPSHOT</version>
-    <name>caimei365-cloud-product</name>
+    <name>caimei365-cloud-commodity</name>
     <description>采美365微服务-商品服务</description>
     <properties>
         <java.version>1.8</java.version>
-        <spring-cloud.version>2020.0.1</spring-cloud.version>
+        <spring-cloud.version>2020.0.2</spring-cloud.version>
     </properties>
     <dependencyManagement>
         <dependencies>
@@ -30,33 +30,189 @@
     </dependencyManagement>
 
     <dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-webflux</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.cloud</groupId>
+			<artifactId>spring-cloud-starter</artifactId>
+		</dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter</artifactId>
+            <artifactId>spring-cloud-starter-config</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+		<dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <!-- mysql -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.1.4</version>
+        </dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<optional>true</optional>
+		</dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.2.5</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-spring-boot-starter</artifactId>
+                    <groupId>org.mybatis.spring.boot</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- redis依赖包 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-webflux</artifactId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.lettuce</groupId>
+                    <artifactId>lettuce-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <!--阿里云开放搜索(OpenSearch)sdk -->
+        <dependency>
+            <groupId>com.aliyun.opensearch</groupId>
+            <artifactId>aliyun-sdk-opensearch</artifactId>
+            <version>3.5.1</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>guava</artifactId>
+                    <groupId>com.google.guava</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>commons-lang</artifactId>
+                    <groupId>commons-lang</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>httpclient</artifactId>
+                    <groupId>org.apache.httpcomponents</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- 阿里云短信sdk -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.18</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
+            <version>2.1.0</version>
         </dependency>
+        <!-- jwt -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.14.0</version>
+        </dependency>
+        <!-- aop -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
+            <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
+        <!-- fastjson -->
         <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.75</version>
         </dependency>
+        <!-- knife4j:swagger增强-->
+        <!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html -->
         <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-openfeign</artifactId>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>3.0.2</version>
         </dependency>
         <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>3.0.0</version>
         </dependency>
+
+
+
+
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>io.projectreactor</groupId>
+			<artifactId>reactor-test</artifactId>
+			<scope>test</scope>
+		</dependency>
     </dependencies>
+
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <activatedProperties>dev</activatedProperties>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>beta</id>
+            <properties>
+                <activatedProperties>beta</activatedProperties>
+            </properties>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <activatedProperties>prod</activatedProperties>
+            </properties>
+        </profile>
+    </profiles>
+
     <build>
+        <finalName>${project.artifactId}</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!--可以在此配置过滤文件  -->
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.xml</include>
+                </includes>
+                <!--开启filtering功能  -->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>

+ 4 - 4
src/main/java/com/caimei365/product/ProductApplication.java → src/main/java/com/caimei365/commodity/CommodityApplication.java

@@ -1,4 +1,4 @@
-package com.caimei365.product;
+package com.caimei365.commodity;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -16,11 +16,11 @@ import org.springframework.context.annotation.ComponentScan;
 @SpringBootApplication
 @EnableDiscoveryClient
 //@EnableFeignClients
-@ComponentScan(basePackages = {"com.caimei365.product.*"})
-public class ProductApplication {
+@ComponentScan(basePackages = {"com.caimei365.commodity.*"})
+public class CommodityApplication {
 
     public static void main(String[] args) {
-        SpringApplication.run(ProductApplication.class, args);
+        SpringApplication.run(CommodityApplication.class, args);
     }
 
 }

+ 294 - 0
src/main/java/com/caimei365/commodity/components/RedisService.java

@@ -0,0 +1,294 @@
+package com.caimei365.commodity.components;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis 服务工具类
+ *
+ * @author : Charles
+ * @date : 2021/3/4
+ */
+@Slf4j
+@Service
+public class RedisService {
+
+	@Resource
+	private RedisTemplate<Serializable, Object> redisTemplate;
+
+    /**
+     * 批量删除
+     * @param keys
+     */
+	public void remove(String... keys) {
+		for(String key :keys){
+			remove(key);
+		}
+	}
+
+    /**
+     * 批量删除正则匹配到的
+     * @param pattern
+     */
+	public void removePattern(String pattern) {
+		Set<Serializable> keys = redisTemplate.keys(pattern);
+        assert keys != null;
+        if (keys.size() > 0){
+			redisTemplate.delete(keys);
+		}
+	}
+
+    /**
+     * 删除
+     * @param key
+     */
+	public void remove(String key) {
+		if (exists(key)) {
+			redisTemplate.delete(key);
+		}
+	}
+
+    /**
+     * 判断缓存中是否存在
+     * @param key
+     * @return boolean
+     */
+	public boolean exists(String key) {
+		return StringUtils.isBlank(key) ? false : redisTemplate.hasKey(key);
+	}
+
+    /**
+     * 读取缓存
+     * @param key
+     * @return
+     */
+	public Object get(String key) {
+		Object result = null;
+		ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+		result = operations.get(key);
+		return result;
+	}
+
+    /**
+     * 写入缓存
+     * @param key
+     * @param value
+     * @return
+     */
+	public boolean set(String key, Object value) {
+		boolean result = false;
+		try {
+			ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+			operations.set(key, value);
+			result = true;
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+
+    /**
+     * 写入缓存并加上过期时间(秒)
+     * @param key
+     * @param value
+     * @param expireTimeSeconds
+     * @return
+     */
+	public boolean set(String key, Object value, Long expireTimeSeconds) {
+		boolean result = false;
+		try {
+			ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
+			operations.set(key, value);
+			redisTemplate.expire(key, expireTimeSeconds, TimeUnit.SECONDS);
+			result = true;
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+    /**
+     * 写入过期时间(秒)
+     * @param key
+     * @param expireTimeSeconds
+     * @return
+     */
+	public boolean expire(String key, Long expireTimeSeconds) {
+		boolean result = false;
+		try {
+			redisTemplate.expire(key, expireTimeSeconds, TimeUnit.SECONDS);
+			result = true;
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+    /* **************************** 针对list操作的方法 **************************** */
+    /**
+     * 在key对应list的尾部添加
+     * @param key
+     * @param value
+     * @return
+     */
+	public long rightPushForList(String key, Object value) {
+		return redisTemplate.opsForList().rightPush(key, value);
+	}
+
+    /**
+     * 在key对应list的头部添加
+     * @param key
+     * @param value
+     * @return
+     */
+	public long leftPushForList(String key, Object value) {
+		return redisTemplate.opsForList().leftPush(key, value);
+	}
+
+    /**
+     * key对应list的长度
+     * @param key
+     * @return
+     */
+	public long listSize(String key) {
+		return redisTemplate.opsForList().size(key);
+	}
+
+    /**
+     * 获取list集合
+     * @param Key
+     * @param begin
+     * @param end
+     * @return
+     */
+	public List<?> getList(String Key, int begin, int end) {
+		return redisTemplate.opsForList().range(Key, begin, end);
+	}
+
+    /**
+     * 在key对应list的尾部移除
+     * @param key
+     * @return
+     */
+	public Object rightPopForList(String key) {
+		return redisTemplate.opsForList().rightPop(key);
+	}
+
+    /**
+     * 在key对应list的头部移除
+     * @param key
+     * @return
+     */
+	public Object leftPopForList(String key) {
+		return redisTemplate.opsForList().leftPop(key);
+	}
+
+    /**
+     * 移除list的index位置上的值
+     * @param Key
+     * @param index
+     * @param value
+     */
+	public void removeList(String Key, long index, Object value) {
+		redisTemplate.opsForList().remove(Key, index, value);
+	}
+
+    /**
+     * 重置list的index位置上的值
+     * @param Key
+     * @param index
+     * @param value
+     */
+	public void setList(String Key, long index, Object value) {
+		redisTemplate.opsForList().set(Key, index, value);
+	}
+
+
+    /**
+     * 写入list
+     * @param key
+     * @param list
+     */
+	public void setList(String key, List list) {
+		if(list!=null&&list.size()>0){
+			for (Object object : list) {
+				rightPushForList(key,object);
+			}
+		}
+	}
+
+    /**
+     * 写入map
+     * @param key
+     * @param map
+     */
+	public void setMap(String key, Map<String, Object> map) {
+		redisTemplate.opsForHash().putAll(key, map);
+	}
+
+    /**
+     * 获取map
+     * @param key
+     * @param mapKey
+     * @return
+     */
+	public Object get(String key, String mapKey) {
+		return redisTemplate.opsForHash().get(key, mapKey);
+	}
+
+    /**
+     * 写入map
+     * @param key
+     * @param hashKey
+     * @param hashValue
+     */
+	public void setMapByKV(String key, Object hashKey, Object hashValue) {
+		redisTemplate.opsForHash().put(key, hashKey,hashValue);
+	}
+
+    /**
+     * 删除map中的某个key-value
+     * @param key
+     * @param hashKey
+     */
+	public void removeHash(String key, String hashKey) {
+		redisTemplate.opsForHash().delete(key, hashKey);
+	}
+
+    /**
+     *
+     * @param key
+     * @return
+     */
+	public Map<Object, Object> getEntries(String key) {
+		return redisTemplate.opsForHash().entries(key);
+	}
+
+    /**
+     *
+     * @param key
+     * @param step
+     * @return
+     */
+	public long increase(String key, long step) {
+		return redisTemplate.opsForValue().increment(key, step);
+	}
+
+    /**
+     * 获取失效时间
+     * @param key
+     * @return
+     */
+	public long getExpireTime(String key) {
+		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+	}
+
+}

+ 361 - 0
src/main/java/com/caimei365/commodity/components/SearchOpenService.java

@@ -0,0 +1,361 @@
+package com.caimei365.commodity.components;
+
+import com.aliyun.opensearch.DocumentClient;
+import com.aliyun.opensearch.OpenSearchClient;
+import com.aliyun.opensearch.SearcherClient;
+import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Lists;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONException;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
+import com.aliyun.opensearch.sdk.generated.OpenSearch;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchResult;
+import com.aliyun.opensearch.sdk.generated.search.*;
+import com.aliyun.opensearch.sdk.generated.search.general.SearchResult;
+import com.aliyun.opensearch.search.SearchResultDebug;
+import com.caimei365.commodity.model.ResponseJson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * OpenSearch 服务工具类
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+@Slf4j
+@Service
+public class SearchOpenService {
+
+    @Value("${aliyunConfig.searchName}")
+    private String appName;
+    @Value("${aliyunConfig.accessKey}")
+    private String accesskey;
+    @Value("${aliyunConfig.secret}")
+    private String secret;
+    @Value("${aliyunConfig.searchHost}")
+    private String host;
+
+    public String getAppName(){
+        return this.appName;
+    }
+    /**
+     * 推送操作
+     *
+     * @param document doc
+     * @return result
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    public String pushDocument(String document, String tableName) throws OpenSearchClientException, OpenSearchException {
+        // 创建并构造OpenSearch对象
+        OpenSearch openSearch = new OpenSearch(accesskey, secret, host);
+        // 创建OpenSearchClient对象,并以OpenSearch对象作为构造参数
+        OpenSearchClient serviceClient = new OpenSearchClient(openSearch);
+        // 创建DocumentClient对象,并以OpenSearchClient对象作为构造参数
+        DocumentClient documentClient = new DocumentClient(serviceClient);
+        // 推送 到阿里云开放搜索
+        OpenSearchResult osr = documentClient.push(document, appName, tableName);
+
+        return osr.getResult();
+    }
+
+    /**
+     * 查询操作
+     *
+     * @param searchParams 搜索参数
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException       exp
+     */
+    public String pushQueryDocument(SearchParams searchParams) throws OpenSearchClientException, OpenSearchException {
+        // 创建并构造OpenSearch对象
+        OpenSearch openSearch = new OpenSearch(accesskey, secret, host);
+        // 创建OpenSearchClient对象,并以OpenSearch对象作为构造参数
+        OpenSearchClient serviceClient = new OpenSearchClient(openSearch);
+        // 创建SearcherClient对象,并以OpenSearchClient对象作为构造参数
+        SearcherClient searcherClient = new SearcherClient(serviceClient);
+        // 执行返回查询结果。用户需按code和message,进行异常情况判断。code对应的错误信息查看——错误码文档。
+        SearchResult searchResult = searcherClient.execute(searchParams);
+        String result = searchResult.getResult();
+        if (appName.contains("test")) {
+            // debug请求地址信息
+            SearchResultDebug searched = searcherClient.executeDebug(searchParams);
+            // 输出查询请求串信息
+            log.info(">>>>>>queryUrl: " + searched.getRequestUrl());
+        }
+        return result;
+    }
+
+    /**
+     * 获取默认查询参数
+     *
+     * @param queryStr      查询类型及参数
+     * @param fetchFields   返回字段
+     * @param summaryFields 摘要字段
+     * @param num           页码
+     * @param size          每页数量
+     * @return searchParams
+     */
+    public SearchParams getSearchParams(String queryStr, ArrayList<String> fetchFields, List<Map<String, String>> summaryFields, String rankName, int num, int size, String sortField, Integer sortType) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(appName));
+        config.setStart(num-1);
+        config.setHits(size);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(fetchFields);
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        // 指定搜索的关键词,这里要指定在哪个索引上搜索
+        // 如果不指定的话默认在使用“default”索引(索引字段名称是您在您的数据结构中的“索引字段列表”中对应字段。)
+        // 若需多个索引组合查询,需要在setQuery处合并,否则若设置多个setQuery子句,则后面的子句会替换前面子句
+        searchParams.setQuery(queryStr);
+
+        // 创建sort对象,并设置二维排序
+        Sort sorter = new Sort();
+        if (StringUtils.isNotEmpty(sortField)) {
+            Order order = (1 == sortType) ? Order.DECREASE : Order.INCREASE;
+            // 设置排序字段
+            sorter.addToSortFields(new SortField(sortField, order));
+        }
+        // 以RANK相关性算分降序
+        sorter.addToSortFields(new SortField("RANK", Order.DECREASE));
+        //添加Sort对象参数
+        searchParams.setSort(sorter);
+        if (queryStr.contains(rankName + ":")) {
+            // 设置粗精排表达式
+            Rank rank = new Rank();
+            rank.setFirstRankName("default");
+            rank.setSecondRankName(rankName);
+            searchParams.setRank(rank);
+        }
+        if (null != summaryFields && !summaryFields.isEmpty()) {
+            for (Map<String, String> summaryField : summaryFields) {
+                String field = summaryField.get("field");
+                Integer length = Integer.valueOf(summaryField.get("length"));
+                // 设置搜索结果摘要信息
+                Summary summary = new Summary();
+                // 指定的生效的字段。此字段必需为可分词的text类型的字段。
+                summary.setSummary_field(field);
+                // 片段长度
+                summary.setSummary_len(length.toString());
+                // 飘红标签
+                summary.setSummary_element("em");
+                // 片段链接符
+                summary.setSummary_ellipsis("...");
+                // 片段数量
+                summary.setSummary_snippet("1");
+                // 添加Summary对象参数
+                searchParams.addToSummaries(summary);
+            }
+        }
+        return searchParams;
+    }
+
+    /**
+     * 获取默认滚动查询参数
+     *
+     * @param queryStr      查询类型及参数
+     * @param fetchFields   返回字段
+     * @param size          每页数量
+     * @return searchParams
+     */
+    public SearchParams getScrollParams(String queryStr, ArrayList<String> fetchFields, int size, String sortField, Integer sortType) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(appName));
+        // config.setStart(0) scroll该参数不起作用,默认为0
+        config.setHits(size);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(fetchFields);
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        // 设置查询子句,若需多个索引组合查询,需要setQuery处合并,否则若设置多个setQuery后面的会替换前面查询
+        searchParams.setQuery(queryStr);
+        if (StringUtils.isNotEmpty(sortField)) {
+            // 创建sort对象,并设置二维排序
+            Sort sorter = new Sort();
+            Order order = (1 == sortType) ? Order.DECREASE : Order.INCREASE;
+            // 设置排序字段
+            sorter.addToSortFields(new SortField(sortField, order));
+            //添加Sort对象参数
+            searchParams.setSort(sorter);
+        }
+        return searchParams;
+    }
+
+    /**
+     * 获取查询商品的参数
+     *
+     * @param queryStr  查询类型及参数
+     * @param size      每页数量
+     * @param sortField 排序字段
+     * @param sortType  升降序
+     * @return searchParams
+     */
+    public SearchParams getProductParams(String queryStr, String filter, Integer identity, int num, int size, String sortField, Integer sortType) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(appName));
+        config.setStart(num-1);
+        config.setHits(size);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id", "p_id", "p_name", "p_image", "p_brand_name", "p_unit", "p_code", "p_price_flag", "p_price_grade", "p_supplier_id", "p_keyword", "p_act_flag"));
+        // 注意:config子句中的rerank_size参数,在Rank类对象中设置
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        // 指定搜索的关键词,这里要指定在哪个索引上搜索
+        // 如果不指定的话默认在使用“default”索引(索引字段名称是您在您的数据结构中的“索引字段列表”中对应字段。)
+        // 若需多个索引组合查询,需要在setQuery处合并,否则若设置多个setQuery子句,则后面的子句会替换前面子句
+        searchParams.setQuery(queryStr);
+
+        String thisFilter = StringUtils.isNotEmpty(filter) ? (filter + " AND ") : "";
+        // identity: 0个人,1协销,2会员机构,3供应商,4普通机构
+        // p_valid:0逻辑删除 1待审核 2已上架 3已下架 8审核未通过 9已隐身 10已冻结
+        // p_visibility:3:所有人可见,2:普通机构可见,1:会员机构可见
+        if (identity == 1) {
+            // 协销 | 综合供应商
+            thisFilter += "(p_valid=2 OR p_valid=3 OR p_valid=9) AND p_type=1";
+        } else if (identity == 2) {
+            // 会员机构
+            thisFilter += "p_valid=2 AND p_type=1";
+        } else if (identity == 4) {
+            // 普通机构
+            thisFilter += "(p_visibility=2 OR p_visibility=3) AND p_valid=2 AND p_type=1";
+        } else {
+            // 游客|所有人
+            thisFilter += "p_visibility=3 AND p_valid=2 AND p_type=1";
+        }
+        searchParams.setFilter(thisFilter);
+
+        // 设置聚合打散子句
+        Distinct dist = new Distinct();
+        // 设置dist_key
+        dist.setKey("p_id");
+        // 设置dist_count
+        dist.setDistCount(1);
+        // 设置dist_times
+        dist.setDistTimes(1);
+        // 设置reserved
+        dist.setReserved(false);
+        // 设置update_total_hit
+        dist.setUpdateTotalHit(false);
+        // 设置过滤条件
+        dist.setDistFilter("p_id>0");
+        // 设置grade
+        dist.setGrade("1.2");
+        searchParams.addToDistincts(dist);
+
+        if (queryStr.contains("product:")) {
+            // 设置粗精排表达式
+            Rank rank = new Rank();
+            rank.setFirstRankName("default");
+            rank.setSecondRankName("product");
+            searchParams.setRank(rank);
+        }
+        // 创建sort对象,并设置二维排序
+        Sort sorter = new Sort();
+        // 价格,销量,人气 排序
+        String[] sortFields = {"price", "sales", "favorite"};
+        if (StringUtils.isNotEmpty(sortField) && Arrays.asList(sortFields).contains(sortField)) {
+            Order order = (1 == sortType) ? Order.DECREASE : Order.INCREASE;
+            // 设置排序字段
+            sorter.addToSortFields(new SortField("p_"+sortField, order));
+        }
+        // 以RANK相关性算分降序
+        sorter.addToSortFields(new SortField("RANK", Order.DECREASE));
+        // 默认商品ID倒序
+        sorter.addToSortFields(new SortField("p_id", Order.DECREASE));
+        //添加Sort对象参数
+        searchParams.setSort(sorter);
+        // 设置搜索结果摘要信息
+        Summary summary = new Summary();
+        // 指定的生效的字段。此字段必需为可分词的text类型的字段。
+        summary.setSummary_field("p_name");
+        // 片段长度
+        summary.setSummary_len("120");
+        // 飘红标签
+        summary.setSummary_element("em");
+        // 片段链接符
+        summary.setSummary_ellipsis("...");
+        // 片段数量
+        summary.setSummary_snippet("1");
+        // 添加Summary对象参数
+        searchParams.addToSummaries(summary);
+
+        return searchParams;
+    }
+
+    /**
+     * 获取滚动查询商品的参数
+     *
+     * @param queryStr  查询类型及参数
+     * @param size      每页数量
+     * @param sortField 排序字段
+     * @param sortType  升降序
+     * @return searchParams
+     */
+    public SearchParams getScrollProductParams(String queryStr, Integer identity, int size, String sortField, Integer sortType) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(appName));
+        // config.setStart(0) scroll该参数不起作用,默认为0
+        config.setHits(size);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id", "p_id", "p_name", "p_image", "p_brand_name", "p_unit", "p_code", "p_price_flag", "p_price_grade", "p_supplier_id", "p_keyword", "p_act_flag"));
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        // 设置查询子句,若需多个索引组合查询,需要setQuery处合并,否则若设置多个setQuery后面的会替换前面查询
+        searchParams.setQuery(queryStr);
+
+        // identity: 0个人,1协销,2会员机构,3供应商,4普通机构
+        // p_valid:0逻辑删除 1待审核 2已上架 3已下架 8审核未通过 9已隐身 10已冻结
+        // p_visibility:3:所有人可见,2:普通机构可见,1:会员机构可见
+        String thisFilter = "";
+        if (identity == 1) {
+            // 协销 | 综合供应商
+            thisFilter += "(p_valid=2 OR p_valid=3 OR p_valid=9) AND p_type=1";
+        } else if (identity == 2) {
+            // 会员机构
+            thisFilter += "p_valid=2 AND p_type=1";
+        } else if (identity == 4) {
+            // 普通机构
+            thisFilter += "(p_visibility=2 OR p_visibility=3) AND p_valid=2 AND p_type=1";
+        } else {
+            // 游客|所有人
+            thisFilter += "p_visibility=3 AND p_valid=2 AND p_type=1";
+        }
+        searchParams.setFilter(thisFilter);
+
+        // 创建sort对象,并设置二维排序
+        Sort sorter = new Sort();
+        // 价格,销量,人气 排序
+        String[] sortFields = {"p_price", "p_sales", "p_favorite"};
+        if (StringUtils.isNotEmpty(sortField) && Arrays.asList(sortFields).contains(sortField)) {
+            Order order = (1 == sortType) ? Order.DECREASE : Order.INCREASE;
+            // 设置排序字段
+            sorter.addToSortFields(new SortField(sortField, order));
+        }else{
+            // 默认商品ID倒序
+            sorter.addToSortFields(new SortField("p_id", Order.DECREASE));
+        }
+        //添加Sort对象参数
+        searchParams.setSort(sorter);
+
+        return searchParams;
+    }
+
+}

+ 1 - 1
src/main/java/com/caimei365/product/config/LoadBalanceConfiguration.java → src/main/java/com/caimei365/commodity/config/LoadBalanceConfiguration.java

@@ -1,4 +1,4 @@
-package com.caimei365.product.config;
+package com.caimei365.commodity.config;
 
 import org.springframework.cloud.client.loadbalancer.LoadBalanced;
 import org.springframework.context.annotation.Bean;

+ 208 - 0
src/main/java/com/caimei365/commodity/controller/SearchIndexApi.java

@@ -0,0 +1,208 @@
+package com.caimei365.commodity.controller;
+
+import com.caimei365.commodity.model.ResponseJson;
+import com.caimei365.commodity.service.SearchIndexService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 更新搜索索引(阿里云搜索)
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+@Api(tags="更新搜索索引API")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/commodity/search/index")
+public class SearchIndexApi {
+
+    private final SearchIndexService searchIndexService;
+
+    /**
+     * 初始化(添加)所有索引
+     *
+     * spi旧接口:/search/manage/init/all
+     * @return totalRecord
+     */
+    @ApiOperation("初始化所有索引")
+    @PostMapping("/init")
+    public ResponseJson<Integer> initAllIndexes(){
+        return searchIndexService.initAllIndexes();
+    }
+
+    /**
+     * 更新所有商品
+     *
+     * spi旧接口:/search/manage/update/product/all
+     * @return productRecord
+     */
+    @ApiOperation("更新所有商品")
+    @PostMapping("/product/all")
+    public ResponseJson<Integer> updateAllProductIndexes(){
+        return searchIndexService.updateAllProductIndexes();
+    }
+
+
+    /**
+     * 更新所有供应商
+     *
+     * spi旧接口:/search/manage/update/supplier/all
+     * @return shopRecord
+     */
+    @ApiOperation("更新所有供应商")
+    @PostMapping("/shop/all")
+    public ResponseJson<Integer> updateAllSupplierIndexes(){
+        return searchIndexService.updateAllSupplierIndexes();
+    }
+
+    /**
+     * 更新所有项目仪器
+     *
+     * spi旧接口:/search/manage/update/equipment/all
+     * @return equipmentRecord
+     */
+    @ApiOperation("更新所有项目仪器")
+    @PostMapping("/equipment/all")
+    public ResponseJson<Integer> updateAllEquipmentIndexes(){
+        return searchIndexService.updateAllEquipmentIndexes();
+    }
+
+    /**
+     * 更新所有文章
+     *
+     * spi旧接口:/search/manage/update/article/all
+     * @return articleRecord
+     */
+    @ApiOperation("更新所有文章")
+    @PostMapping("/article/all")
+    public ResponseJson<Integer> updateAllArticleIndexes(){
+        return searchIndexService.updateAllArticleIndexes();
+    }
+
+    /**
+     * 根据商品Id更新
+     *
+     * spi旧接口:/search/manage/update/product
+     * @param productId 商品Id
+     * @return int
+     */
+    @ApiOperation("根据商品Id更新")
+    @ApiImplicitParam(required = true, name = "productId", value = "商品Id(旧:pid)")
+    @PostMapping("/update/product")
+    public ResponseJson<Integer> updateProductIndex(Integer productId){
+        return searchIndexService.updateProductIndexById(productId);
+    }
+
+    /**
+     * 根据供应商Id更新
+     *
+     * spi旧接口:/search/manage/update/supplier
+     * @param shopId 供应商Id
+     * @return int
+     */
+    @ApiOperation("根据供应商Id更新")
+    @ApiImplicitParam(required = true, name = "shopId", value = "供应商Id(旧:sid)")
+    @PostMapping("/update/shop")
+    public ResponseJson<Integer> updateSupplierIndex(Integer shopId){
+        return searchIndexService.updateSupplierIndexById(shopId);
+    }
+
+    /**
+     * 根据项目仪器Id更新
+     *
+     * spi旧接口:/search/manage/update/equipment
+     * @param equipmentId 仪器Id
+     * @return int
+     */
+    @ApiOperation("根据仪器Id更新")
+    @ApiImplicitParam(required = true, name = "equipmentId", value = "仪器Id(旧:eid)")
+    @PostMapping("/update/equipment")
+    public ResponseJson<Integer> updateEquipmentIndex(Integer equipmentId){
+        return searchIndexService.updateEquipmentIndexById(equipmentId);
+    }
+
+    /**
+     * 根据文章Id更新
+     *
+     * spi旧接口:/search/manage/update/article
+     * @param articleId 文章Id
+     * @return int
+     */
+    @ApiOperation("根据文章Id更新")
+    @ApiImplicitParam(required = true, name = "articleId", value = "文章Id(旧:aid)")
+    @PostMapping("/update/article")
+    public ResponseJson<Integer> updateArticleIndex(Integer articleId){
+        return searchIndexService.updateArticleIndexById(articleId);
+    }
+
+    /**
+     * 根据商品Id删除
+     *
+     * spi旧接口:/search/manage/delete/product
+     * @param productId 商品Id
+     */
+    @ApiOperation("根据商品Id删除")
+    @ApiImplicitParam(required = true, name = "productId", value = "商品Id(旧:pid)")
+    @PostMapping("/delete/product")
+    public ResponseJson<Integer> deleteProductIndex(Integer productId){
+        return searchIndexService.deleteProductIndexById(productId);
+    }
+
+    /**
+     * 根据供应商Id删除
+     *
+     * spi旧接口:/search/manage/delete/supplier
+     * @param shopId 供应商Id
+     */
+    @ApiOperation("根据供应商Id删除")
+    @ApiImplicitParam(required = true, name = "shopId", value = "供应商Id(旧:sid)")
+    @PostMapping("/delete/shop")
+    public ResponseJson<Integer> deleteSupplierIndex(Integer shopId){
+        return searchIndexService.deleteSupplierIndexById(shopId);
+    }
+
+    /**
+     * 根据项目仪器Id删除
+     *
+     * spi旧接口:/search/manage/delete/equipment
+     * @param equipmentId 仪器Id
+     */
+    @ApiOperation("根据项目仪器Id删除")
+    @ApiImplicitParam(required = true, name = "equipmentId", value = "仪器Id(旧:eid)")
+    @PostMapping("/delete/equipment")
+    public ResponseJson<Integer> deleteEquipmentIndex(Integer equipmentId){
+        return searchIndexService.deleteEquipmentIndexById(equipmentId);
+    }
+
+    /**
+     * 根据文章Id删除
+     *
+     * spi旧接口:/search/manage/delete/article
+     * @param articleId 文章Id
+     */
+    @ApiOperation("根据文章Id删除")
+    @ApiImplicitParam(required = true, name = "articleId", value = "文章Id(旧:aid)")
+    @PostMapping("/delete/article")
+    public ResponseJson<Integer> deleteArticleIndex(Integer articleId){
+        return searchIndexService.deleteArticleIndexById(articleId);
+    }
+
+    /**
+     * 根据主文档Id删除
+     *
+     * spi旧接口:/search/manage/delete/main
+     * @param id 文档Id
+     */
+    @ApiOperation("根据主文档Id删除")
+    @ApiImplicitParam(required = true, name = "id", value = "文档Id")
+    @PostMapping("/delete/main")
+    public ResponseJson<Integer> deleteMainIndex(Integer id){
+        return searchIndexService.deleteMainIndexById(id);
+    }
+}

+ 52 - 0
src/main/java/com/caimei365/commodity/controller/SearchProductApi.java

@@ -0,0 +1,52 @@
+package com.caimei365.commodity.controller;
+
+import com.caimei365.commodity.model.ResponseJson;
+import com.caimei365.commodity.service.SearchProductService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 搜索商品(阿里云搜索)
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+@Api(tags="搜索查询API")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/commodity/search/query")
+public class SearchProductApi {
+
+    private final SearchProductService searchProductService;
+
+    /**
+     * 根据关键词搜索商品
+     *
+     * @param keyword 搜索关键字
+     * @return JsonStr(list)
+     */
+    @ApiOperation("根据keyword搜索商品")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = false, name = "keyword", value = "搜索关键字"),
+        @ApiImplicitParam(required = false, name = "identity", value = "用户身份: 0个人,1协销,2会员机构,3供应商,4普通机构"),
+        @ApiImplicitParam(required = false, name = "pageNum", value = "页码"),
+        @ApiImplicitParam(required = false, name = "pageSize", value = "每页数量"),
+        @ApiImplicitParam(required = false, name = "sortField", value = "排序字段:价格price,销量sales,人气favorite"),
+        @ApiImplicitParam(required = false, name = "sortType", value = "排序规则:1降序,其他升序")
+    })
+    @GetMapping("product")
+    public ResponseJson<String> queryProductByKeyword(String keyword,
+                                                      @RequestParam(value = "identity", defaultValue = "0") Integer identity,
+                                                      @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+                                                      @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
+                                                      String sortField, Integer sortType) {
+        return searchProductService.queryProductByKeyword(keyword, identity, pageNum, pageSize, sortField, sortType);
+    }
+}

+ 92 - 0
src/main/java/com/caimei365/commodity/controller/SearchQueryApi.java

@@ -0,0 +1,92 @@
+package com.caimei365.commodity.controller;
+
+import com.caimei365.commodity.service.SearchQueryService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 搜索查询(阿里云搜索)
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+@Api(tags="搜索查询API")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/commodity/search/query")
+public class SearchQueryApi {
+
+    private final SearchQueryService searchQueryService;
+
+    /**
+     * 根据文档Id获取索引Id
+     *
+     * spi旧接口:/search/query/(id,pid,sid,eid,aid)
+     * @return int
+     */
+    @ApiOperation("根据文档Id获取索引Id")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = false, name = "docType", value = "文档类型(product,shop,equipment,article)"),
+        @ApiImplicitParam(required = false, name = "docId", value = "文档Id")
+    })
+    @GetMapping("id")
+    public Integer getIdByDocId(String docType, Integer docId) {
+        return searchQueryService.getIdByDocId(docType, docId);
+    }
+
+    /**
+     * 根据文档类型获取索引Id集合
+     *
+     * spi旧接口:/search/query/(product,supplier,equipment,article)/ids
+     * @return map
+     */
+    @ApiOperation("根据文档类型获取索引Id集合")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = true, name = "docType", value = "文档类型(product,shop,equipment,article)"),
+        @ApiImplicitParam(required = false, name = "pageNum", value = "页码"),
+        @ApiImplicitParam(required = false, name = "pageSize", value = "每页数量")
+    })
+    @GetMapping("ids")
+    public Map<Integer, Integer> getMainIdsByDocType(String docType,
+                                                     @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+                                                     @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
+        return searchQueryService.getMainIdsByDocType(docType, pageNum, pageSize);
+    }
+
+    /**
+     * 根据文档类型获取数量
+     *
+     * spi旧接口:/search/query/record
+     * @return int
+     */
+    @ApiOperation("根据文档类型获取数量")
+    @ApiImplicitParam(required = true, name = "docType", value = "文档类型(product,shop,equipment,article)")
+    @GetMapping("record")
+    public Integer getRecordByDocType(String docType) {
+        return searchQueryService.getRecordByDocType(docType);
+    }
+
+    /**
+     * 根据文档类型获取数量(去重用)
+     *
+     * spi旧接口:/search/query/acount
+     * @return int
+     */
+    @ApiOperation("根据文档Id获取数量(去重用)")
+    @ApiImplicitParams({
+        @ApiImplicitParam(required = false, name = "docType", value = "文档类型(product,shop,equipment,article)"),
+        @ApiImplicitParam(required = false, name = "docId", value = "文档Id")
+    })
+    @GetMapping("count")
+    public Integer getCountByDocId(String docType, Integer docId) {
+        return searchQueryService.getCountByDocId(docType, docId);
+    }
+
+
+}

+ 4 - 13
src/main/java/com/caimei365/product/controller/TestApi.java → src/main/java/com/caimei365/commodity/controller/TestWebClientApi.java

@@ -1,4 +1,4 @@
-package com.caimei365.product.controller;
+package com.caimei365.commodity.controller;
 
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -15,30 +15,21 @@ import javax.annotation.Resource;
  * @author : Charles
  * @date : 2021/02/20
  */
-@RequestMapping("/product")
+@RequestMapping("/commodity")
 @RestController
-public class TestApi {
+public class TestWebClientApi {
 
     @GetMapping("/test")
     public String getTestString() {
         return "test product";
     }
 
-
-    /*@Resource
-    UserFeign userFeign;
-    @GetMapping("/user")
-    public String getUserFeign() {
-        return "user openfeign:" + userFeign.sayHi();
-    }*/
-
-
     @Resource
     private WebClient.Builder clientBuilder;
     private static final String BASE_URL = "http://caimei365-cloud-user";
     @GetMapping("/user")
     public Mono<String> getServerString() {
-        return clientBuilder.baseUrl(BASE_URL).build().get().uri("/user/test").retrieve().bodyToMono(String.class);
+        return clientBuilder.baseUrl(BASE_URL).build().get().uri("/user/").retrieve().bodyToMono(String.class);
     }
 
 }

+ 203 - 0
src/main/java/com/caimei365/commodity/mapper/SearchMapper.java

@@ -0,0 +1,203 @@
+package com.caimei365.commodity.mapper;
+
+import com.caimei365.commodity.model.search.*;
+import com.caimei365.commodity.model.vo.ProductListVo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Mapper
+public interface SearchMapper {
+    /**
+     * 根据商品id查找文档数据
+     * @param productId
+     */
+    ProductDO searchProductById(Integer productId);
+    /**
+     * 获取商品数量
+     */
+    Integer findProductCount();
+    /**
+     * 获取商品列表
+     */
+    List<ProductDO> searchProductList();
+    /**
+     * 根据商品id查找 星范商品数量
+     */
+    Integer countMallProduct(Integer productId);
+    /**
+     * 根据商品id查找 星范商品
+     */
+    MallProductDO searchMallProductByProductId(Integer productId);
+    /**
+     * 根据商品id查找 星范阶梯价标志
+     */
+    Integer getMallLadderPriceFlag(Integer productId);
+    /**
+     * 根据商品id查找 星范最小阶梯价
+     */
+    Double getMallLowerLadderPrice(Integer productId);
+    /**
+     * 获取供应商数量
+     */
+    Integer findSupplierCount();
+    /**
+     * 获取供应商列表
+     */
+    List<SupplierDO> searchSupplierList();
+    /**
+     * 根据供应商Id查找文档数据
+     */
+    SupplierDO searchSupplierById(Integer supplierId);
+
+    /**
+     * 获取项目仪器数量
+     */
+    Integer findEquipmentCount();
+    /**
+     * 获取项目仪器列表
+     */
+    List<EquipmentDO> searchEquipmentList();
+    /**
+     * 根据项目仪器Id查找文档数据
+     */
+    EquipmentDO searchEquipmentById(Integer equipmentId);
+
+    /**
+     * 获取文章数量
+     */
+    Integer findArticleCount();
+    /**
+     * 获取文章列表
+     */
+    List<ArticleDO> searchArticleList();
+    /**
+     * 根据文章Id查找文档数据
+     */
+    ArticleDO searchArticleById(Integer articleId);
+    /**
+     * 根据文章标签查找标签Id
+     */
+    List<Integer> findLabelIdsByName(@Param("labelTexts") String[] labelTexts);
+    /**
+     * 获取所有失效商品数量
+     */
+    Integer findProductInvalidCount();
+    /**
+     * 获取失效商品IDs
+     */
+    List<Integer> findProductInvalidIds();
+    /**
+     * 根据商品ID获取星范ID
+     */
+    Integer findMallIdByProductId(Integer productId);
+    /**
+     * 根据商品IDs获取星范IDs
+     */
+    List<Integer> findMallInvalidIdsByProductIds(@Param("invalidIds") List<Integer> invalidIds);
+    /**
+     * 获取所有失效供应商数量
+     */
+    Integer findSupplierInvalidCount();
+    /**
+     * 获取失效供应商IDs
+     */
+    List<Integer> findSupplierInvalidIds();
+    /**
+     * 获取所有失效项目仪器数量
+     */
+    Integer findEquipmentInvalidCount();
+    /**
+     * 获取失效项目仪器IDs
+     */
+    List<Integer> findEquipmentInvalidIds();
+    /**
+     * 获取所有失效文章数量
+     */
+    Integer findArticleInvalidCount();
+    /**
+     * 获取失效文章IDs
+     */
+    List<Integer> findArticleInvalidIds();
+
+
+    /**
+     * 获取数据库商品
+     * @param identity  用户身份
+     * @param keyword   关键词
+     * @param shopId    供应商id
+     *
+     * @param typeId    分类id
+     * @param idType    分类类型(1一级分类,2二级分类,3三级分类)
+     *
+     * @param sortField 排序类型
+     * @param sortType  排序顺序
+     * @return
+     */
+    List<ProductListVo> queryProductFromDb(@Param("identity") Integer identity,
+                                           @Param("keyword") String keyword,
+                                           @Param("shopId") Integer shopId,
+                                           @Param("bigTypeId") Integer bigTypeId,
+                                           @Param("smallTypeId") Integer smallTypeId,
+                                           @Param("tinyTypeId") Integer tinyTypeId,
+                                           @Param("sortField") String sortField,
+                                           @Param("sortType") Integer sortType);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    /**
+     * 根据关键词获取数据库供应商
+     * @param keyword
+     * @return
+     */
+    List<SupplierDO> searchDbSupplierByKeyword(String keyword);
+
+    /**
+     * 根据关键词获取数据库项目仪器
+     * @param keyword
+     * @return
+     */
+    List<EquipmentDO> searchDbEquipmentByKeyword(String keyword);
+
+    /**
+     * 获取数据库商品
+     * @param shopID    供应商id
+     * @param keyword   关键词
+     * @param typeId    分类id
+     * @param idType    分类类型(1一级分类,2二级分类,3三级分类)
+     * @param identity  用户身份
+     * @param sortField 排序类型
+     * @param sortType  排序顺序
+     * @return
+     */
+    List<ProductDO> searchDbProduct(@Param("shopID") Integer shopID, @Param("keyword") String keyword, @Param("typeId") Integer typeId, @Param("idType") Integer idType, @Param("identity") Integer identity, @Param("sortField") String sortField, @Param("sortType") Integer sortType);
+
+    /**
+     * 获取数据库文章
+     * @return
+     */
+    List<DbArticleDO> searchDbArticle(@Param("articleId") Integer id, @Param("keyword") String keyword, @Param("typeId") Integer typeId, @Param("labelId") Integer labelId);
+
+
+}

+ 85 - 0
src/main/java/com/caimei365/commodity/model/ResponseJson.java

@@ -0,0 +1,85 @@
+package com.caimei365.commodity.model;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 全局API返回值
+ *
+ * @author : Charles
+ * @date : 2021/3/4
+ */
+@Data
+public class ResponseJson<T> implements Serializable {
+    /** 状态码 */
+    @ApiModelProperty("状态码")
+    private int code;
+    /** 提示信息 */
+    @ApiModelProperty("提示信息")
+    private String msg;
+    /** 返回的数据 */
+    @ApiModelProperty("响应数据")
+    private T data;
+
+    private ResponseJson() {}
+
+    private ResponseJson(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    private ResponseJson(int code, String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public static ResponseJson success() {
+        return new ResponseJson<>(0, "操作成功");
+    }
+
+    public static<T> ResponseJson<T> success(T data) {
+        return new ResponseJson<>(0, "操作成功", data);
+    }
+
+    public static<T> ResponseJson<T> success(String msg, T data) {
+        return new ResponseJson<>(0, msg, data);
+    }
+
+    public static<T> ResponseJson<T> success(int code, String msg, T data) {
+        return new ResponseJson<>(code, msg, data);
+    }
+
+    public static ResponseJson error() {
+        return new ResponseJson<>(-1, "操作失败");
+    }
+
+    public static ResponseJson error(String msg) {
+        return new ResponseJson<>(-1, msg);
+    }
+
+    public static ResponseJson error(int code, String msg) {
+        return new ResponseJson<>(code, msg);
+    }
+
+    public static<T> ResponseJson<T> error(T data) {
+        return new ResponseJson<>(-1, "操作失败", data);
+    }
+
+    public static<T> ResponseJson<T> error(String msg, T data) {
+        return new ResponseJson<>(-1, msg, data);
+    }
+
+    public static<T> ResponseJson<T> error(int code, String msg, T data) {
+        return new ResponseJson<>(code, msg, data);
+    }
+
+    @Override
+    public String toString() {
+        return "ResponseJson{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}';
+    }
+
+    private static final long serialVersionUID = 1L;
+}

+ 48 - 0
src/main/java/com/caimei365/commodity/model/search/ArticleDO.java

@@ -0,0 +1,48 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+/**
+ * 文章搜索
+ *
+ * @author : Charles
+ * @date : 2020/5/21
+ */
+@Data
+public class ArticleDO implements Serializable {
+    /** 文章ID */
+    private Integer a_id;
+    /** 查询全部标志 */
+    private Integer a_all;
+    /** 文章标题 */
+    private String a_title;
+    /** 文章主图 */
+    private String a_image;
+    /** 文章作者 */
+    private String a_publisher;
+    /** 发布日期 */
+    private String a_publish_date;
+    /** 文章摘要 */
+    private String a_intro;
+    /** 文章内容 */
+    private String a_content;
+    /** 浏览量 */
+    private Integer a_pv;
+    /** 信息分类Id */
+    private Integer a_type_id;
+    /** 信息分类文本 */
+    private String a_type_text;
+    /** 优先级 */
+    private Integer a_sort;
+    /** 标签 */
+    private String a_label;
+    /** 标签 id数组 */
+    private Integer[] a_label_ids;
+    /** 标签 文本数组 */
+    private String[] a_label_text;
+
+    private static final long serialVersionUID = 1L;
+}

+ 48 - 0
src/main/java/com/caimei365/commodity/model/search/DbArticleDO.java

@@ -0,0 +1,48 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+/**
+ * 文章容错搜索(数据库)
+ *
+ * @author : Asle
+ * @date : 2021/3/17
+ */
+@Data
+public class DbArticleDO implements Serializable {
+    /** 文章ID */
+    private Integer a_id;
+    /** 查询全部标志 */
+    private Integer a_all;
+    /** 文章标题 */
+    private String a_title;
+    /** 文章主图 */
+    private String a_image;
+    /** 文章作者 */
+    private String a_publisher;
+    /** 发布日期 */
+    private String a_publish_date;
+    /** 文章摘要 */
+    private String a_intro;
+    /** 文章内容 */
+    private String a_content;
+    /** 浏览量 */
+    private Integer a_pv;
+    /** 信息分类Id */
+    private Integer a_type_id;
+    /** 信息分类文本 */
+    private String a_type_text;
+    /** 优先级 */
+    private Integer a_sort;
+    /** 标签 */
+    private String a_label;
+    /** 标签 id数组 */
+    private String a_label_ids;
+    /** 标签 文本数组 */
+    private String a_label_text;
+
+    private static final long serialVersionUID = 1L;
+}

+ 15 - 0
src/main/java/com/caimei365/commodity/model/search/DocumentDTO.java

@@ -0,0 +1,15 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+/**
+ * 文档推送结构
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Data
+public class DocumentDTO<T> {
+    private String cmd;
+    private T fields;
+}

+ 29 - 0
src/main/java/com/caimei365/commodity/model/search/EquipmentDO.java

@@ -0,0 +1,29 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 项目仪器搜索
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Data
+public class EquipmentDO implements Serializable {
+    /** 网页ID */
+    private Integer e_id;
+    /** 查询全部标志 */
+    private Integer e_all;
+    /** 网页标题 */
+    private String e_name;
+    /** 关键词 */
+    private String e_keyword;
+    /** 主图 */
+    private String e_image;
+    /** 默认权重 */
+    private Integer e_sort;
+
+    private static final long serialVersionUID = 1L;
+}

+ 26 - 0
src/main/java/com/caimei365/commodity/model/search/MainDO.java

@@ -0,0 +1,26 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 主文档搜索
+ *
+ * @author : Charles
+ * @date : 2020/5/19
+ */
+@Data
+public class MainDO implements Serializable {
+    private Integer id;
+    /** 商品ID(阿里云表外键) */
+    private Integer p_id;
+    /** 供应商ID(阿里云表外键) */
+    private Integer s_id;
+    /** 项目仪器ID(阿里云表外键) */
+    private Integer e_id;
+    /** 文章ID(阿里云表外键) */
+    private Integer a_id;
+
+    private static final long serialVersionUID = 1L;
+}

+ 33 - 0
src/main/java/com/caimei365/commodity/model/search/MallProductDO.java

@@ -0,0 +1,33 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 商品价格
+ *
+ * @author : Charles
+ * @date : 2020/5/20
+ */
+@Data
+public class MallProductDO implements Serializable {
+    /** 星范ID */
+    private Integer m_id;
+    /** 查询全部标志 */
+    private Integer m_all;
+    /** 组织ID */
+    private Integer m_organize_id;
+    /** 商品ID */
+    private Integer m_product_id;
+    /** 星范价格(计算后) */
+    private Double m_price;
+    /** 星范小程序商品分类Id */
+    private Integer m_classify_id;
+    private String m_classify_name;
+    /** 0 有效 其它无效 */
+    private Integer m_valid;
+
+    private static final long serialVersionUID = 1L;
+}
+

+ 75 - 0
src/main/java/com/caimei365/commodity/model/search/ProductDO.java

@@ -0,0 +1,75 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 阿里云商品表(search_product)
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Data
+public class ProductDO implements Serializable {
+    /** 商品productID */
+    private Integer p_id;
+    /** 查询全部标志 */
+    private Integer p_all;
+    /** 名称name */
+    private String p_name;
+    /** 搜索关键词searchKey */
+    private String p_keyword;
+    /** 标签 */
+    private String p_tags;
+    /** 主图mainImage */
+    private String p_image;
+    /** 机构价(阿里云排序必须用int) */
+    private Integer p_price;
+    /** 是否公开机构价 0公开价格 1不公开价格 */
+    private Integer p_price_flag;
+    /** 计算后价格等级 */
+    private Integer p_price_grade;
+    /** 商品货号 */
+    private String p_code;
+    /** 排序值 */
+    private Integer p_sort;
+    /** 包装规格 */
+    private String p_unit;
+    /** 销量 */
+    private Integer p_sales;
+    /** 收藏量(人气) */
+    private Integer p_favorite;
+    /** 品牌Id */
+    private Integer p_brand_id;
+    private String p_brand_name;
+    /** 所属供应商Id,关联供应商表 */
+    private Integer p_supplier_id;
+    private String p_supplier_name;
+    /** 一级级分类 */
+    private Integer p_category1_id;
+    private String p_category1_name;
+    /** 二级分类 */
+    private Integer p_category2_id;
+    private String p_category2_name;
+    /** 三级分类 */
+    private Integer p_category3_id;
+    private String p_category3_name;
+    /** 小程序商品分类Id */
+    private Integer p_classify_id;
+    private String p_classify_name;
+    /** 常用商品001,精品推荐010,热门推荐100,三者同时存在111 */
+    private Integer p_preferred;
+    /** productCategory # 商品的类别:1正常商品(默认),2二手商品' */
+    private Integer p_type;
+    /* 商品可见度: 3:所有人可见,2:普通机构可见,1:会员机构可见 */
+    private Integer p_visibility;
+    /** 商品状态,0逻辑删除 1待审核 2已上架 3已下架 8审核未通过 9已隐身 10已冻结 */
+    private Integer p_valid;
+    /** 星范Id(阿里云表外键) */
+    private Integer m_id;
+    /** 美博会商品活动状态 */
+    private Integer p_act_flag;
+
+    private static final long serialVersionUID = 1L;
+}

+ 40 - 0
src/main/java/com/caimei365/commodity/model/search/SupplierDO.java

@@ -0,0 +1,40 @@
+package com.caimei365.commodity.model.search;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 供应商搜索
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Data
+public class SupplierDO implements Serializable {
+    /** 商品shopID */
+    private Integer s_id;
+    /** 查询全部标志 */
+    private Integer s_all;
+    /** 名称name */
+    private String s_name;
+    /** 供应商logo */
+    private String s_logo;
+    /** 供应商牌照 */
+    private String s_license;
+    /** 经营品项 */
+    private String s_business;
+    /** 所在地区ID */
+    private Integer s_town_id;
+    /** 所在省市地址 */
+    private String s_address;
+    /** 默认排序 */
+    private Integer s_sort;
+    /** 供应商状态:90上线 or 9未缴纳保证金*/
+    private Integer s_valid;
+    /** 供应商下商品*/
+    private List<ProductDO> products;
+
+    private static final long serialVersionUID = 1L;
+}

+ 113 - 0
src/main/java/com/caimei365/commodity/model/vo/PageVo.java

@@ -0,0 +1,113 @@
+package com.caimei365.commodity.model.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+@Data
+public class PageVo<T> implements Serializable {
+    /** 当前页码 */
+    @Builder.Default
+    private int pageNum = 1;
+    /** 每页大小 */
+    @Builder.Default
+    private int pageSize = 10;
+    /** 总记录数 */
+    private int totalRecord;
+    /** 总页数 */
+    private int totalPage;
+    /** 是否有后一页 */
+    private boolean hasNextPage;
+    /** 是否有前一页 */
+    private boolean hasPreviousPage;
+    /** 查询结果 */
+    List<T> results;
+
+    public PageVo(List<T> list) {
+        if (list instanceof com.github.pagehelper.Page) {
+            com.github.pagehelper.Page<T> page = (com.github.pagehelper.Page<T>) list;
+            this.pageNum = page.getPageNum();
+            this.pageSize = page.getPageSize();
+            this.totalRecord = (int) page.getTotal();
+            this.totalPage = page.getPages();
+            setHasPreviousAndNext();
+            this.results = page;
+        } else if (list != null) {
+            this.pageSize = list.size();
+            this.totalRecord = list.size();
+            this.totalPage = 1;
+            this.results = list;
+        }
+    }
+
+    public PageVo(int pageNum, int pageSize, int totalRecord, List<T> results) {
+        super();
+        this.pageNum = pageNum;
+        this.pageSize = pageSize;
+        this.totalRecord = totalRecord;
+        this.totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
+        this.results = results;
+        if (this.pageNum < 2) {
+            hasPreviousPage = false;
+        } else {
+            hasPreviousPage = true;
+        }
+        if (this.pageNum < totalPage) {
+            hasNextPage = true;
+        } else {
+            hasNextPage = false;
+        }
+    }
+
+    public PageVo(int pageNum, int maxSize, int totalRecord, int totalPage,
+                  List<T> results) {
+        super();
+        this.pageNum = pageNum;
+        this.pageSize = maxSize;
+        this.totalRecord = totalRecord;
+        this.totalPage = totalPage;
+        this.results = results;
+        if (this.pageNum < 2) {
+            hasPreviousPage = false;
+        } else {
+            hasPreviousPage = true;
+        }
+        if (this.pageNum < totalPage) {
+            hasNextPage = true;
+        } else {
+            hasNextPage = false;
+        }
+    }
+
+    public PageVo() {super();}
+
+    public void setHasPreviousAndNext() {
+        if (this.pageNum < 2) {
+            hasPreviousPage = false;
+        } else {
+            hasPreviousPage = true;
+        }
+        if (this.pageNum < totalPage) {
+            hasNextPage = true;
+        } else {
+            hasNextPage = false;
+        }
+    }
+
+    public int countTotalPage() {
+        int total = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
+        this.setTotalPage(total);
+        return total;
+    }
+
+    private static final long serialVersionUID = 1L;
+}
+

+ 44 - 0
src/main/java/com/caimei365/commodity/model/vo/ProductListVo.java

@@ -0,0 +1,44 @@
+package com.caimei365.commodity.model.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 阿里云商品表(search_product)
+ *
+ * @author : Charles
+ * @date : 2020/5/13
+ */
+@Data
+public class ProductListVo implements Serializable {
+    /** 索引Id */
+    private Integer id;
+    /** 商品productID */
+    private Integer productId;
+    /** 名称name */
+    private String name;
+    /** 主图mainImage */
+    private String image;
+    /** 品牌 */
+    private String brandName;
+    /** 包装规格 */
+    private String unit;
+    /** 商品货号 */
+    private String code;
+    /** 机构价 */
+    private Double price;
+    /** 是否公开机构价 0公开价格 1不公开价格 */
+    private Integer priceFlag;
+    /** 计算后价格等级 */
+    private Integer priceGrade;
+    /** 所属供应商Id,关联供应商表 */
+    private Integer shopId;
+    /** 搜索关键词searchKey */
+    private String keyword;
+    /** 美博会商品活动状态 */
+    private Integer actFlag;
+
+    private static final long serialVersionUID = 1L;
+
+}

+ 108 - 0
src/main/java/com/caimei365/commodity/service/SearchIndexService.java

@@ -0,0 +1,108 @@
+package com.caimei365.commodity.service;
+
+import com.caimei365.commodity.model.ResponseJson;
+import org.springframework.web.bind.annotation.PostMapping;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+public interface SearchIndexService {
+    /**
+     * 初始化(添加)所有索引
+     * @return totalRecord
+     */
+    ResponseJson<Integer> initAllIndexes();
+
+    /**
+     * 更新所有商品
+     * @return
+     */
+    ResponseJson<Integer> updateAllProductIndexes();
+
+    /**
+     * 更新所有供应商
+     * @return
+     */
+    ResponseJson<Integer> updateAllSupplierIndexes();
+
+    /**
+     * 更新所有项目仪器
+     * @return
+     */
+    ResponseJson<Integer> updateAllEquipmentIndexes();
+
+    /**
+     * 更新所有文章
+     * @return
+     */
+    ResponseJson<Integer> updateAllArticleIndexes();
+
+
+    /**
+     * 根据商品Id更新
+     * @param productId
+     * @return
+     */
+    ResponseJson<Integer> updateProductIndexById(Integer productId);
+
+    /**
+     * 根据供应商Id更新
+     * @param supplierId
+     * @return
+     */
+    ResponseJson<Integer> updateSupplierIndexById(Integer supplierId);
+
+    /**
+     * 根据项目仪器Id更新
+     * @param equipmentId
+     * @return
+     */
+    ResponseJson<Integer> updateEquipmentIndexById(Integer equipmentId);
+
+    /**
+     * 根据文章Id更新
+     * @param articleId
+     * @return
+     */
+    ResponseJson<Integer> updateArticleIndexById(Integer articleId);
+
+
+    /**
+     * 根据商品Id删除
+     * @param productId
+     * @return
+     */
+    ResponseJson<Integer> deleteProductIndexById(Integer productId);
+
+    /**
+     * 根据供应商Id删除
+     * @param supplierId
+     * @return
+     */
+    ResponseJson<Integer> deleteSupplierIndexById(Integer supplierId);
+
+    /**
+     * 根据项目仪器Id删除
+     * @param equipmentId
+     * @return
+     */
+    ResponseJson<Integer> deleteEquipmentIndexById(Integer equipmentId);
+
+    /**
+     * 根据文章Id删除
+     * @param articleId
+     * @return
+     */
+    ResponseJson<Integer> deleteArticleIndexById(Integer articleId);
+
+    /**
+     * 根据主文档Id删除
+     * @param id
+     * @return
+     */
+    ResponseJson<Integer> deleteMainIndexById(Integer id);
+
+}

+ 20 - 0
src/main/java/com/caimei365/commodity/service/SearchProductService.java

@@ -0,0 +1,20 @@
+package com.caimei365.commodity.service;
+
+import com.caimei365.commodity.model.ResponseJson;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+public interface SearchProductService {
+    /**
+     * 根据关键词搜索商品
+     *
+     * @param keyword 关键字
+     * @return JsonStr(list)
+     */
+    ResponseJson<String> queryProductByKeyword(String keyword, Integer identity, int pageNum, int pageSize, String sortField, Integer sortType);
+
+}

+ 44 - 0
src/main/java/com/caimei365/commodity/service/SearchQueryService.java

@@ -0,0 +1,44 @@
+package com.caimei365.commodity.service;
+
+
+import java.util.Map;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+public interface SearchQueryService {
+    /**
+     * 获取索引Id
+     *
+     * @param docType 文档类型
+     * @param docId   文档ID
+     * @return int
+     */
+    Integer getIdByDocId(String docType, Integer docId);
+    /**
+     * 根据文档类型获取索引Id集合
+     * @param docType 文档类型
+     * @return map
+     */
+    Map<Integer, Integer> getMainIdsByDocType(String docType, int pageNum, int pageSize);
+    /**
+     * 根据文档类型获取数量
+     *
+     * @param docType 文档类型
+     * @return int
+     */
+    Integer getRecordByDocType(String docType);
+
+    /**
+     * 根据文档类型获取数量(去重用)
+     *
+     * @param docType 文档类型
+     * @param docId   文档ID
+     * @return int
+     */
+    Integer getCountByDocId(String docType, Integer docId);
+
+}

+ 1093 - 0
src/main/java/com/caimei365/commodity/service/impl/SearchIndexServiceImpl.java

@@ -0,0 +1,1093 @@
+package com.caimei365.commodity.service.impl;
+
+import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Maps;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONException;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
+import com.aliyun.opensearch.sdk.generated.document.Command;
+import com.aliyun.opensearch.sdk.generated.document.DocumentConstants;
+import com.caimei365.commodity.components.SearchOpenService;
+import com.caimei365.commodity.mapper.SearchMapper;
+import com.caimei365.commodity.model.ResponseJson;
+import com.caimei365.commodity.model.search.*;
+import com.caimei365.commodity.service.SearchIndexService;
+import com.caimei365.commodity.service.SearchQueryService;
+import com.caimei365.commodity.utils.ImageUtils;
+import com.caimei365.commodity.utils.PriceUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.util.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/6
+ */
+@Slf4j
+@Service
+public class SearchIndexServiceImpl implements SearchIndexService {
+    @Value("${caimei.wwwDomain}")
+    private String domain;
+    @Resource
+    private SearchMapper searchMapper;
+    @Resource
+    private SearchOpenService searchOpenService;
+    @Resource
+    private SearchQueryService searchQueryService;
+    /**
+     * 初始化(添加)所有索引
+     *
+     * @return totalRecord
+     */
+    @Override
+    public ResponseJson<Integer> initAllIndexes() {
+        // 初始化主键ID,及索引记录数量
+        Integer mainId = 0;
+        // 从数据库获取商品数量
+        Integer productCount = searchMapper.findProductCount();
+        Integer productRecord = setAllProductData(mainId, productCount, true);
+        if (productCount > productRecord) {
+            log.warn("【初始化】批量添加商品文档异常,部分商品未添加。");
+        }
+        log.info(">>>>>>【商品】应加:"+productCount+",实加:"+productRecord);
+        mainId += productRecord;
+        // 从数据库获取供应商数量
+        Integer supplierCount = searchMapper.findSupplierCount();
+        Integer supplierRecord = setAllSupplierData(mainId, supplierCount, true);
+        if (supplierCount > supplierRecord) {
+            log.warn("【初始化】批量添加供应商文档异常,部分供应商未添加。");
+        }
+        log.info(">>>>>>【供应商】应加:"+supplierCount+",实加:"+supplierRecord);
+        mainId += supplierRecord;
+        // 从数据库获取项目仪器数量
+        Integer equipmentCount = searchMapper.findEquipmentCount();
+        Integer equipmentRecord = setAllEquipmentData(mainId, equipmentCount, true);
+        if (equipmentCount > equipmentRecord) {
+            log.warn("批量添加项目仪器文档异常,部分项目仪器未添加。");
+        }
+        log.info(">>>>>>【项目仪器】应加:"+equipmentCount+",实加:"+equipmentRecord);
+        mainId += equipmentRecord;
+        // 从数据库获取文章数量
+        Integer articleCount = searchMapper.findArticleCount();
+        Integer articleRecord = setAllArticleData(mainId, articleCount, true);
+        if (articleCount > articleRecord) {
+            log.warn("批量添加文章文档异常,部分文章未添加。");
+        }
+        log.info(">>>>>>【文章】应加:"+articleCount+",实加:"+articleRecord);
+        mainId += articleRecord;
+
+        log.info(">>>>>>>>>>>>添加总记录:" + mainId);
+        return ResponseJson.success(mainId);
+    }
+
+    /**
+     * 更新所有商品
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateAllProductIndexes() {
+        // 获取失效商品数量
+        Integer invalidCount = searchMapper.findProductInvalidCount();
+        // 批量删除失效商品
+        batchDeleteProductDoc(invalidCount);
+        // 获取商品数量
+        Integer productCount = searchMapper.findProductCount();
+        // 获取最大文档ID
+        Integer mainId = searchQueryService.getIdByDocId("", null);
+        mainId = mainId == -1 ? 0 : mainId;
+        // 获取推送记录数
+        Integer productRecord = setAllProductData(mainId, productCount, false);
+        if (productCount > productRecord) {
+            log.warn("批量添加商品文档异常,部分商品未添加。");
+        }
+        log.info(">>>>>>【商品】应加:"+productCount+",实加:"+productRecord);
+        return ResponseJson.success(productRecord);
+    }
+
+    /**
+     * 更新所有供应商
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateAllSupplierIndexes() {
+        // 获取失效数量
+        Integer invalidCount = searchMapper.findSupplierInvalidCount();
+        // 批量删除失效
+        batchDeleteSupplierDoc(invalidCount);
+        // 获取供应商数量
+        Integer supplierCount = searchMapper.findSupplierCount();
+        // 获取最大文档ID
+        Integer mainId = searchQueryService.getIdByDocId("", null);
+        mainId = mainId == -1 ? 0 : mainId;
+        // 获取推送记录数
+        Integer supplierRecord = setAllSupplierData(mainId, supplierCount, false);
+        if (supplierCount > supplierRecord) {
+            log.warn("批量添加供应商文档异常,部分供应商未添加。");
+        }
+        return ResponseJson.success(supplierRecord);
+    }
+
+    /**
+     * 更新所有项目仪器
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateAllEquipmentIndexes() {
+        // 获取失效数量
+        Integer invalidCount = searchMapper.findEquipmentInvalidCount();
+        // 批量删除失效
+        batchDeleteEquipmentDoc(invalidCount);
+        // 获取项目仪器数量
+        Integer equipmentCount = searchMapper.findEquipmentCount();
+        // 获取最大文档ID
+        Integer mainId = searchQueryService.getIdByDocId("", null);
+        mainId = mainId == -1 ? 0 : mainId;
+        // 获取推送记录数
+        Integer equipmentRecord = setAllEquipmentData(mainId, equipmentCount, false);
+        if (equipmentCount > equipmentRecord) {
+            log.warn("批量添加项目仪器文档异常,部分项目仪器未添加。");
+        }
+        return ResponseJson.success(equipmentRecord);
+
+    }
+
+    /**
+     * 更新所有文章
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateAllArticleIndexes() {
+        // 获取失效数量
+        Integer invalidCount = searchMapper.findArticleInvalidCount();
+        // 批量删除失效
+        batchDeleteArticleDoc(invalidCount);
+        // 获取文章数量
+        Integer articleCount = searchMapper.findArticleCount();
+        // 获取最大文档ID
+        Integer mainId = searchQueryService.getIdByDocId("", null);
+        mainId = mainId == -1 ? 0 : mainId;
+        // 获取推送记录数
+        Integer articleRecord = setAllArticleData(mainId, articleCount, false);
+        if (articleCount > articleRecord) {
+            log.warn("批量添加文章文档异常,部分文章未添加。");
+        }
+        return ResponseJson.success(articleRecord);
+    }
+
+    /**
+     * 根据商品Id更新
+     * @param productId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateProductIndexById(Integer productId){
+        ProductDO product = searchMapper.searchProductById(productId);
+        if (null != product) {
+            // 定义星范商品数据
+            List<DocumentDTO<MallProductDO>> mallProductList = new ArrayList<>();
+            // 星范商品设值,【必须先于商品文档设值】
+            Integer mallId = setMallProductDocument(mallProductList, productId);
+            // 定义商品文档数据
+            List<DocumentDTO<ProductDO>> productList = new ArrayList<>();
+            // 商品文档设值
+            setProductDocument(productList, product, mallId);
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+            // 主文档设值
+            Integer mainId = searchQueryService.getIdByDocId("product", productId);
+            if (mainId == -1) {
+                mainId = searchQueryService.getIdByDocId("", null);
+                mainId = mainId == -1 ? 0 : mainId+1;
+            }
+            // 商品文档 type=1
+            setMainDocument(1, mainList, mainId, productId);
+            try {
+                // 推送到阿里云
+                pushProductDocument(mainList, productList, mallProductList);
+                // 返回结果
+                return ResponseJson.success(mainList.size());
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("添加商品文档异常:" + e);
+                return ResponseJson.error("添加商品文档异常", null);
+            }
+        } else {
+            return deleteProductIndexById(productId);
+        }
+    }
+
+    /**
+     * 根据供应商Id更新
+     *
+     * @param supplierId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateSupplierIndexById(Integer supplierId) {
+        SupplierDO supplier = searchMapper.searchSupplierById(supplierId);
+        if(null != supplier){
+             // 定义供应商文档数据
+            List<DocumentDTO<SupplierDO>> supplierList = new ArrayList<>();
+            // 供应商文档设值
+            setSupplierDocument(supplierList, supplier);
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+            // 主文档设值
+            Integer mainId = searchQueryService.getIdByDocId("shop", supplierId);
+            if (mainId == -1) {
+                mainId = searchQueryService.getIdByDocId("", null);
+                mainId = mainId == -1 ? 0 : mainId+1;
+            }
+            // 供应商文档 type=2
+            setMainDocument(2, mainList, mainId, supplierId);
+            try {
+                // 将文档列表转换成Json串,并推送到阿里云
+                pushSupplierDocument(mainList, supplierList);
+                // 返回结果
+                return ResponseJson.success(mainList.size());
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("添加供应商文档异常:" + e);
+                return ResponseJson.error("添加供应商文档异常", null);
+            }
+        }else{
+            return deleteSupplierIndexById(supplierId);
+        }
+    }
+
+    /**
+     * 根据项目仪器Id更新
+     *
+     * @param equipmentId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateEquipmentIndexById(Integer equipmentId) {
+        EquipmentDO equipment = searchMapper.searchEquipmentById(equipmentId);
+        if(null != equipment){
+            // 定义项目仪器文档数据
+            List<DocumentDTO<EquipmentDO>> equipmentList = new ArrayList<>();
+            // 项目仪器文档设值
+            setEquipmentDocument(equipmentList, equipment);
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+            // 主文档设值
+            Integer mainId = searchQueryService.getIdByDocId("equipment", equipmentId);
+            if (mainId == -1) {
+                mainId = searchQueryService.getIdByDocId("", null);
+                mainId = mainId == -1 ? 0 : mainId+1;
+            }
+            // 项目仪器文档 type=3
+            setMainDocument(3, mainList, mainId, equipmentId);
+            try {
+                // 将文档列表转换成Json串,并推送到阿里云
+                pushEquipmentDocument(mainList, equipmentList);
+                // 返回结果
+                return ResponseJson.success(mainList.size());
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("添加项目仪器文档异常:" + e);
+                return ResponseJson.error("添加项目仪器文档异常", null);
+            }
+        }else{
+            return deleteEquipmentIndexById(equipmentId);
+        }
+    }
+
+    /**
+     * 根据文章Id更新
+     *
+     * @param articleId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> updateArticleIndexById(Integer articleId) {
+        ArticleDO article = searchMapper.searchArticleById(articleId);
+        if(null != article){
+            // 定义文章文档数据
+            List<DocumentDTO<ArticleDO>> articleList = new ArrayList<>();
+            // 文章文档设值
+            setArticleDocument(articleList, article);
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+            // 主文档设值
+            Integer mainId = searchQueryService.getIdByDocId("article", articleId);
+            if (mainId == -1) {
+                mainId = searchQueryService.getIdByDocId("", null);
+                mainId = mainId == -1 ? 0 : mainId+1;
+            }
+            // 文章文档 type=4
+            setMainDocument(4, mainList, mainId, articleId);
+            try {
+                // 将主文档列表转换成Json串,并推送到阿里云
+                pushArticleDocument(mainList, articleList);
+                // 查询文章数量是否唯一,不唯一则删除后重新更新索引
+                Integer count = searchQueryService.getCountByDocId("article", articleId);
+                if (count > 1) {
+                    log.info("文章重复,重复文章id>>>>>>>>>>>" + articleId);
+                    deleteArticleIndexById(articleId);
+                    updateArticleIndexById(articleId);
+                }
+                // 返回结果
+                return ResponseJson.success(mainList.size());
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("添加文章文档异常:" + e);
+                return ResponseJson.error("添加文章文档异常", null);
+            }
+        }else{
+            return deleteArticleIndexById(articleId);
+        }
+    }
+
+
+    /**
+     * 根据商品Id删除
+     *
+     * @param productId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> deleteProductIndexById(Integer productId) {
+        // 根据商品ID获取星范ID
+        Integer mallId = searchMapper.findMallIdByProductId(productId);
+        try {
+            if (null != mallId){
+                deleteDocByTypeId("m_id", mallId, "search_product_mall");
+            }
+            deleteDocByTypeId("p_id", productId, "search_product");
+            // 返回结果
+            return ResponseJson.success(1);
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("删除商品文档异常:" + e);
+            return ResponseJson.error("删除商品文档异常", null);
+        }
+    }
+
+    /**
+     * 根据供应商Id删除
+     *
+     * @param supplierId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> deleteSupplierIndexById(Integer supplierId) {
+        try {
+            deleteDocByTypeId("s_id", supplierId, "search_supplier");
+            // 返回结果
+            return ResponseJson.success(1);
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("删除供应商文档异常:" + e);
+            return ResponseJson.error("删除供应商文档异常", null);
+        }
+    }
+
+    /**
+     * 根据项目仪器Id删除
+     *
+     * @param equipmentId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> deleteEquipmentIndexById(Integer equipmentId) {
+        try {
+            deleteDocByTypeId("e_id", equipmentId, "search_equipment");
+            // 返回结果
+            return ResponseJson.success(1);
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("删除项目仪器文档异常:" + e);
+            return ResponseJson.error("删除项目仪器文档异常", null);
+        }
+    }
+
+    /**
+     * 根据文章Id删除
+     *
+     * @param articleId int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> deleteArticleIndexById(Integer articleId) {
+        try {
+            deleteDocByTypeId("a_id", articleId, "search_article");
+            // 返回结果
+            return ResponseJson.success(1);
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("删除文章文档异常:" + e);
+            return ResponseJson.error("删除文章文档异常", null);
+        }
+    }
+
+    /**
+     * 根据主文档Id删除
+     *
+     * @param id int
+     * @return json
+     */
+    @Override
+    public ResponseJson<Integer> deleteMainIndexById(Integer id) {
+        try {
+            deleteDocByTypeId("id", id, "search_main");
+            // 返回结果
+            return ResponseJson.success(1);
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("删除主文档异常:" + e);
+            return ResponseJson.error("删除主文档异常", null);
+        }
+    }
+
+    /**
+     * 根据文档类型key与类型Id删除
+     * @param type 类型key
+     * @param id 类型Id
+     * @param table 表名
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    private void deleteDocByTypeId(String type, Integer id, String table) throws OpenSearchClientException, OpenSearchException {
+        // 删除文档只需要设置需删除文档主键值即可
+        Map<String, Object> deleteDoc = Maps.newLinkedHashMap();
+        deleteDoc.put(type, id);
+        // 根据ID(主键)删除文档
+        String deleteJsonStr = setDeleteDocument(deleteDoc);
+        // 推送到阿里云
+        String deleteResult = searchOpenService.pushDocument(deleteJsonStr, table);
+        log.info(">>>>>>>>>>>>>>>delete document: 【"+ table +"("+ type +":" + id + "):" + deleteResult + "】");
+    }
+
+
+
+
+    /**
+     * 批量删除失效商品
+     * @param invalidCount int
+     */
+    private void batchDeleteProductDoc(Integer invalidCount) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(invalidCount.doubleValue() / 100);
+        // 定义返回结果
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取失效商品IDs
+            List<Integer> invalidIds = searchMapper.findProductInvalidIds();
+            // 根据商品IDs获取星范IDs
+            List<Integer> mallInvalidIds = searchMapper.findMallInvalidIdsByProductIds(invalidIds);
+            // 删除文档只需要设置需删除文档主键值即可
+            Map<String, Object> deleteDoc = Maps.newLinkedHashMap();
+            if (null != mallInvalidIds) {
+                for (Integer mallId : mallInvalidIds) {
+                    // 根据ID(主键)删除文档
+                    deleteDoc.put("m_id", mallId);
+                }
+            }
+            for (Integer productId : invalidIds) {
+                // 根据ID(主键)删除文档
+                deleteDoc.put("p_id", productId);
+            }
+            try {
+                String deleteJsonStr = setDeleteDocument(deleteDoc);
+                // 推送到阿里云
+                String deleteResult = searchOpenService.pushDocument(deleteJsonStr, "search_product");
+                log.info("删除文档:" + deleteResult);
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.warn("删除文档异常:" + e);
+            }
+        }
+    }
+
+    /**
+     * 设置所有商品数据
+     * @param mainId int
+     * @param productCount int
+     * @param initFlag int
+     * @return int
+     */
+    private Integer setAllProductData(Integer mainId, Integer productCount, boolean initFlag) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(productCount.doubleValue() / 100);
+        Map<Integer, Integer> idsMap = Maps.newLinkedHashMap();
+        if (!initFlag) {
+            Integer record = searchQueryService.getRecordByDocType("product");
+            int l = (int)Math.ceil(record.doubleValue() / 500);
+            for (int i = 1; i <= l; i++) {
+                Map<Integer, Integer> idsTemp = searchQueryService.getMainIdsByDocType("product", i, 500);
+                idsMap.putAll(idsTemp);
+            }
+        }
+        // 定义返回结果
+        int productRecord = 0;
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取数据库商品列表的分页数据
+            List<ProductDO> dbList = searchMapper.searchProductList();
+            // 定义星范商品数据
+            List<DocumentDTO<MallProductDO>> mallProductList = new ArrayList<>();
+            // 定义商品文档数据
+            List<DocumentDTO<ProductDO>> productList = new ArrayList<>();
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+
+            int productMainId;
+            for (ProductDO product : dbList) {
+                // 星范商品设值,【必须先于商品文档设值】
+                Integer mallId = setMallProductDocument(mallProductList, product.getP_id());
+                // 商品文档设值
+                setProductDocument(productList, product, mallId);
+                // 主文档设值
+                Integer productId = product.getP_id();
+                if(initFlag){
+                    productMainId = mainId+1;
+                    mainId+=1;
+                }else{
+                    Integer tempId = idsMap.get(productId);
+                    if (null==tempId || tempId<=0){
+                        tempId = searchQueryService.getIdByDocId("product", productId);
+                    }
+                    if (null!=tempId && tempId>0) {
+                        productMainId = tempId;
+                    }else{
+                        productMainId = mainId+1;
+                        mainId+=1;
+                    }
+                }
+                // 商品文档 type=1
+                setMainDocument(1, mainList, productMainId, productId);
+            }
+            try {
+                // 推送到阿里云
+                pushProductDocument(mainList, productList, mallProductList);
+                // 添加到返回结果
+                productRecord += mainList.size();
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.warn("批量添加商品文档异常:" + e);
+                productRecord += 0;
+            }
+        }
+        return productRecord;
+    }
+
+    /**
+     * 推送所有商品
+     * @param mainList list
+     * @param productList list
+     * @param mallProductList list
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    private void pushProductDocument(List<DocumentDTO<MainDO>> mainList, List<DocumentDTO<ProductDO>> productList, List<DocumentDTO<MallProductDO>> mallProductList) throws OpenSearchClientException, OpenSearchException {
+        // 将主文档列表转换成Json串,并推送到阿里云
+        String mainDocStr = new JSONArray(mainList).toString();
+        String mainResult = searchOpenService.pushDocument(mainDocStr, "search_main");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_main(" + mainList.size() + "):" + mainResult + "】" + mainList.toString());
+        // 将商品文档推送到阿里云
+        String productDocStr = new JSONArray(productList).toString();
+        String productResult = searchOpenService.pushDocument(productDocStr, "search_product");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_product(" + productList.size() + "):" + productResult + "】");
+        // 将星范商品推送到阿里云
+        if (mallProductList.size()>0) {
+            String mallProductStr = new JSONArray(mallProductList).toString();
+            String mallProductResult = searchOpenService.pushDocument(mallProductStr, "search_product_mall");
+            log.info(">>>>>>>>>>>>>>>add document: 【search_product_mall(" + mallProductList.size() + "):" + mallProductResult + "】");
+        }
+    }
+
+    /**
+     * 批量删除失效供应商
+     * @param invalidCount int
+     */
+    private void batchDeleteSupplierDoc(Integer invalidCount) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(invalidCount.doubleValue() / 100);
+        // 定义返回结果
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取失效商品IDs
+            List<Integer> invalidIds = searchMapper.findSupplierInvalidIds();
+            // 删除文档只需要设置需删除文档主键值即可
+            Map<String, Object> deleteDoc = Maps.newLinkedHashMap();
+            for (Integer productId : invalidIds) {
+                // 根据ID(主键)删除文档
+                deleteDoc.put("s_id", productId);
+            }
+            try {
+                String deleteJsonStr = setDeleteDocument(deleteDoc);
+                // 推送到阿里云
+                String deleteResult = searchOpenService.pushDocument(deleteJsonStr, "search_supplier");
+                log.info("删除文档:" + deleteResult);
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.warn("删除文档异常:" + e);
+            }
+        }
+    }
+
+    /**
+     * 设置所有供应商数据
+     * @param mainId int
+     * @param supplierCount int
+     * @param initFlag int
+     * @return int
+     */
+    private Integer setAllSupplierData(Integer mainId, Integer supplierCount, boolean initFlag) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(supplierCount.doubleValue() / 100);
+        Map<Integer, Integer> idsMap = Maps.newLinkedHashMap();
+        if (!initFlag) {
+            Integer record = searchQueryService.getRecordByDocType("shop");
+            int l = (int)Math.ceil(record.doubleValue() / 500);
+            for (int i = 1; i <= l; i++) {
+                Map<Integer, Integer> idsTemp = searchQueryService.getMainIdsByDocType("shop", i, 500);
+                idsMap.putAll(idsTemp);
+            }
+        }
+        // 定义返回结果
+        int supplierRecord = 0;
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取数据库供应商列表的分页数据
+            List<SupplierDO> dbList = searchMapper.searchSupplierList();
+            // 定义供应商文档数据
+            List<DocumentDTO<SupplierDO>> supplierList = new ArrayList<>();
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+
+            int supplierMainId;
+            for (SupplierDO supplier: dbList){
+                // 供应商文档设值
+                setSupplierDocument(supplierList, supplier);
+                // 主文档设值
+                Integer supplierId = supplier.getS_id();
+                if(initFlag){
+                    supplierMainId = mainId+1;
+                    mainId+=1;
+                }else{
+                    Integer tempId = idsMap.get(supplierId);
+                    if (null==tempId || tempId<=0){
+                        tempId = searchQueryService.getIdByDocId("shop", supplierId);
+                    }
+                    if (null!=tempId && tempId>0) {
+                        supplierMainId = tempId;
+                    }else{
+                        supplierMainId = mainId+1;
+                        mainId+=1;
+                    }
+                }
+                // 供应商文档 type=2
+                setMainDocument(2, mainList, supplierMainId, supplierId);
+            }
+            try {
+                // 推送到阿里云
+                pushSupplierDocument(mainList, supplierList);
+                // 添加到返回结果
+                supplierRecord += mainList.size();
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("批量添加供应商文档异常:" + e);
+                supplierRecord += 0;
+            }
+        }
+        return supplierRecord;
+    }
+
+    /**
+     * 推送所有供应商
+     * @param mainList list
+     * @param supplierList list
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    private void pushSupplierDocument(List<DocumentDTO<MainDO>> mainList, List<DocumentDTO<SupplierDO>> supplierList) throws OpenSearchClientException, OpenSearchException {
+        // 将主文档列表转换成Json串,并推送到阿里云
+        String mainDocStr = new JSONArray(mainList).toString();
+        String mainResult = searchOpenService.pushDocument(mainDocStr, "search_main");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_main(" + mainList.size() + "):" + mainResult + "】" + mainList.toString());
+        // 将商品文档推送到阿里云
+        String supplierDocStr = new JSONArray(supplierList).toString();
+        String supplierResult = searchOpenService.pushDocument(supplierDocStr, "search_supplier");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_supplier(" + supplierList.size() + "):" + supplierResult + "】");
+    }
+
+    /**
+     * 批量删除失效项目仪器
+     * @param invalidCount int
+     */
+    private void batchDeleteEquipmentDoc(Integer invalidCount) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(invalidCount.doubleValue() / 100);
+        // 定义返回结果
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取失效商品IDs
+            List<Integer> invalidIds = searchMapper.findEquipmentInvalidIds();
+            // 删除文档只需要设置需删除文档主键值即可
+            Map<String, Object> deleteDoc = Maps.newLinkedHashMap();
+            for (Integer productId : invalidIds) {
+                // 根据ID(主键)删除文档
+                deleteDoc.put("e_id", productId);
+            }
+            try {
+                String deleteJsonStr = setDeleteDocument(deleteDoc);
+                // 推送到阿里云
+                String deleteResult = searchOpenService.pushDocument(deleteJsonStr, "search_equipment");
+                log.info("删除文档:" + deleteResult);
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.warn("删除文档异常:" + e);
+            }
+        }
+    }
+
+    /**
+     * 设置所有项目仪器数据
+     * @param mainId int
+     * @param equipmentCount int
+     * @param initFlag int
+     * @return int
+     */
+    private Integer setAllEquipmentData(Integer mainId, Integer equipmentCount, boolean initFlag) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(equipmentCount.doubleValue() / 100);
+        Map<Integer, Integer> idsMap = Maps.newLinkedHashMap();
+        if (!initFlag) {
+            Integer record = searchQueryService.getRecordByDocType("equipment");
+            int l = (int)Math.ceil(record.doubleValue() / 500);
+            for (int i = 1; i <= l; i++) {
+                Map<Integer, Integer> idsTemp = searchQueryService.getMainIdsByDocType("equipment", i, 500);
+                idsMap.putAll(idsTemp);
+            }
+        }
+        // 定义返回结果
+        int equipmentRecord = 0;
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取数据库项目仪器列表的分页数据
+            List<EquipmentDO> dbList = searchMapper.searchEquipmentList();
+            // 定义项目仪器文档数据
+            List<DocumentDTO<EquipmentDO>> equipmentList = new ArrayList<>();
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+
+            int equipmentMainId;
+            for (EquipmentDO equipment: dbList){
+                // 项目仪器文档设值
+                setEquipmentDocument(equipmentList, equipment);
+                // 主文档设值
+                Integer equipmentId = equipment.getE_id();
+                if(initFlag){
+                    equipmentMainId = mainId+1;
+                    mainId+=1;
+                }else{
+                    Integer tempId = idsMap.get(equipmentId);
+                    if (null==tempId || tempId<=0){
+                        tempId = searchQueryService.getIdByDocId("equipment", equipmentId);
+                    }
+                    if (null!=tempId && tempId>0) {
+                        equipmentMainId = tempId;
+                    }else{
+                        equipmentMainId = mainId+1;
+                        mainId+=1;
+                    }
+                }
+                // 项目仪器文档 type=3
+                setMainDocument(3, mainList, equipmentMainId, equipmentId);
+            }
+            try {
+                // 将文档列表转换成Json串,并推送到阿里云
+                pushEquipmentDocument(mainList, equipmentList);
+                // 添加到返回结果
+                equipmentRecord += mainList.size();
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("批量添加项目仪器文档异常:" + e);
+                equipmentRecord += 0;
+            }
+        }
+        return equipmentRecord;
+    }
+
+    /**
+     * 推送所有项目仪器
+     * @param mainList list
+     * @param equipmentList list
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    private void pushEquipmentDocument(List<DocumentDTO<MainDO>> mainList, List<DocumentDTO<EquipmentDO>> equipmentList) throws OpenSearchClientException, OpenSearchException {
+        // 将主文档列表转换成Json串,并推送到阿里云
+        String mainDocStr = new JSONArray(mainList).toString();
+        String mainResult = searchOpenService.pushDocument(mainDocStr, "search_main");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_main(" + mainList.size() + "):" + mainResult + "】" + mainList.toString());
+        // 将商品文档推送到阿里云
+        String equipmentDocStr = new JSONArray(equipmentList).toString();
+        String equipmentResult = searchOpenService.pushDocument(equipmentDocStr, "search_equipment");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_equipment(" + equipmentList.size() + "):" + equipmentResult + "】");
+    }
+
+    /**
+     * 批量删除失效文章
+     * @param invalidCount int
+     */
+    private void batchDeleteArticleDoc(Integer invalidCount) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(invalidCount.doubleValue() / 100);
+        // 定义返回结果
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取失效商品IDs
+            List<Integer> invalidIds = searchMapper.findArticleInvalidIds();
+            // 删除文档只需要设置需删除文档主键值即可
+            Map<String, Object> deleteDoc = Maps.newLinkedHashMap();
+            for (Integer productId : invalidIds) {
+                // 根据ID(主键)删除文档
+                deleteDoc.put("a_id", productId);
+            }
+            try {
+                String deleteJsonStr = setDeleteDocument(deleteDoc);
+                // 推送到阿里云
+                String deleteResult = searchOpenService.pushDocument(deleteJsonStr, "search_article");
+                log.info("删除文档:" + deleteResult);
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.warn("删除文档异常:" + e);
+            }
+        }
+    }
+
+    /**
+     * 设置所有文章数据
+     * @param mainId int
+     * @param articleCount int
+     * @param initFlag int
+     * @return int
+     */
+    private Integer setAllArticleData(Integer mainId, Integer articleCount, boolean initFlag) {
+        // 批量推送,每100条推送一次
+        int loop = (int)Math.ceil(articleCount.doubleValue() / 100);
+        Map<Integer, Integer> idsMap = Maps.newLinkedHashMap();
+        if (!initFlag) {
+            Integer record = searchQueryService.getRecordByDocType("article");
+            int l = (int)Math.ceil(record.doubleValue() / 500);
+            for (int i = 1; i <= l; i++) {
+                Map<Integer, Integer> idsTemp = searchQueryService.getMainIdsByDocType("article", i, 500);
+                idsMap.putAll(idsTemp);
+            }
+        }
+        // 定义返回结果
+        int articleRecord = 0;
+        for (int i = 1; i <= loop; i++) {
+            PageHelper.startPage(i, 100);
+            // 获取数据库文章列表的分页数据
+            List<ArticleDO> dbList = searchMapper.searchArticleList();
+            // 定义文章文档数据
+            List<DocumentDTO<ArticleDO>> articleList = new ArrayList<>();
+            // 定义主文档入口数据
+            List<DocumentDTO<MainDO>> mainList = new ArrayList<>();
+
+            int articleMainId;
+            for (ArticleDO article: dbList){
+                // 文章文档设值
+                setArticleDocument(articleList, article);
+                // 主文档设值
+                Integer articleId = article.getA_id();
+                if(initFlag){
+                    articleMainId = mainId+1;
+                    mainId+=1;
+                }else{
+                    Integer tempId = idsMap.get(articleId);
+                    if (null==tempId || tempId<=0){
+                        tempId = searchQueryService.getIdByDocId("article", articleId);
+                    }
+                    if (null!=tempId && tempId>0) {
+                        articleMainId = tempId;
+                    }else{
+                        articleMainId = mainId+1;
+                        mainId+=1;
+                    }
+                }
+                // 文章文档 type=4
+                setMainDocument(4, mainList, articleMainId, articleId);
+            }
+            try {
+                // 将文档列表转换成Json串,并推送到阿里云
+                pushArticleDocument(mainList, articleList);
+                // 添加到返回结果
+                articleRecord += mainList.size();
+            } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+                log.error("批量添加文章文档异常:" + e);
+                articleRecord += 0;
+            }
+        }
+        return articleRecord;
+    }
+
+    /**
+     * 推送所有文章数据
+     * @param mainList list
+     * @param articleList list
+     * @throws OpenSearchClientException exp
+     * @throws OpenSearchException exp
+     */
+    private void pushArticleDocument(List<DocumentDTO<MainDO>> mainList, List<DocumentDTO<ArticleDO>> articleList) throws OpenSearchClientException, OpenSearchException {
+        // 将主文档列表转换成Json串,并推送到阿里云
+        String mainDocStr = new JSONArray(mainList).toString();
+        String mainResult = searchOpenService.pushDocument(mainDocStr, "search_main");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_main(" + mainList.size() + "):" + mainResult + "】" + mainList.toString());
+        // 将商品文档推送到阿里云
+        String articleDocStr = new JSONArray(articleList).toString();
+        String articleResult = searchOpenService.pushDocument(articleDocStr, "search_article");
+        log.info(">>>>>>>>>>>>>>>add document: 【search_article(" + articleList.size() + "):" + articleResult + "】");
+    }
+
+    /**
+     * 删除文档设值
+     * @return docsJson
+     */
+    private String setDeleteDocument(Map<String, Object> deleteDoc) {
+        //此处设置删除文档处理
+        JSONObject deleteJson = new JSONObject();
+        deleteJson.put(DocumentConstants.DOC_KEY_CMD, Command.DELETE.toString());
+        deleteJson.put(DocumentConstants.DOC_KEY_FIELDS, deleteDoc);
+        JSONArray docsJsonArr = new JSONArray();
+        docsJsonArr.put(deleteJson);
+        return docsJsonArr.toString();
+    }
+
+    /**
+     * 商品星范商品设值
+     *
+     * @param mallProductList list
+     * @param productId int
+     */
+    private Integer setMallProductDocument(List<DocumentDTO<MallProductDO>> mallProductList, Integer productId) {
+        // 是否是星范商品
+        int mallCount = searchMapper.countMallProduct(productId);
+        if (mallCount >= 1) {
+            MallProductDO mallProduct = searchMapper.searchMallProductByProductId(productId);
+            // 是否有阶梯价
+            Integer mallLadderFlag = searchMapper.getMallLadderPriceFlag(productId);
+            if (mallLadderFlag == 1) {
+                // 阶梯价
+                Double mallLadderPrice = searchMapper.getMallLowerLadderPrice(productId);
+                mallProduct.setM_price(mallLadderPrice);
+            }
+            mallProduct.setM_all(1);
+            // 搜索星范商品数据
+            DocumentDTO<MallProductDO> mallProductDoc = new DocumentDTO<>();
+            mallProductDoc.setCmd("add");
+            mallProductDoc.setFields(mallProduct);
+            mallProductList.add(mallProductDoc);
+            return mallProduct.getM_id();
+        }
+        return 0;
+    }
+
+    /**
+     * 商品文档设值
+     *  @param productList list
+     * @param product ProductDO
+     * @param mallId int
+     */
+    private void setProductDocument(List<DocumentDTO<ProductDO>> productList, ProductDO product, Integer mallId) {
+        String imagePath = ImageUtils.getImageURL("product", product.getP_image(), 0, domain);
+        // 搜索商品文档数据
+        product.setP_image(imagePath);
+        product.setM_id(mallId);
+        product.setP_all(1);
+        // 价格等级
+        product.setP_price_grade(PriceUtil.getpriceGrade(product.getP_price().doubleValue()));
+        DocumentDTO<ProductDO> productDoc = new DocumentDTO<>();
+        productDoc.setCmd("add");
+        productDoc.setFields(product);
+        productList.add(productDoc);
+    }
+
+    /**
+     * 供应商文档设值
+     *
+     * @param supplierList list
+     * @param supplier supplierDO
+     */
+    private void setSupplierDocument(List<DocumentDTO<SupplierDO>> supplierList, SupplierDO supplier) {
+        String imagePath = ImageUtils.getImageURL("supplierLogo", supplier.getS_logo(), 0, domain);
+        // 搜索供应商文档数据
+        supplier.setS_logo(imagePath);
+        supplier.setS_all(1);
+        DocumentDTO<SupplierDO> supplierDoc = new DocumentDTO<>();
+        supplierDoc.setCmd("add");
+        supplierDoc.setFields(supplier);
+        supplierList.add(supplierDoc);
+    }
+
+    /**
+     * 项目仪器文档设值
+     *
+     * @param equipmentList list
+     * @param equipment EquipmentDO
+     */
+    private void setEquipmentDocument(List<DocumentDTO<EquipmentDO>> equipmentList, EquipmentDO equipment) {
+        // 搜索供应商文档数据
+        equipment.setE_all(1);
+        DocumentDTO<EquipmentDO> equipmentDoc = new DocumentDTO<>();
+        equipmentDoc.setCmd("add");
+        equipmentDoc.setFields(equipment);
+        equipmentList.add(equipmentDoc);
+    }
+
+    /**
+     * 文章文档设值
+     *
+     * @param articleList list
+     * @param article ArticleDO
+     */
+    private void setArticleDocument(List<DocumentDTO<ArticleDO>> articleList, ArticleDO article) {
+        String imagePath = ImageUtils.getImageURL("", article.getA_image(), 0, domain);
+        // 搜索供应商文档数据
+        String[] labelTexts = article.getA_label().split(",");
+        if(labelTexts.length > 0){
+            boolean notEmpty = StringUtil.isNotEmpty(labelTexts[0]);
+            if(notEmpty){
+                List<Integer> labelIdList = searchMapper.findLabelIdsByName(labelTexts);
+                Integer[] labelIds = labelIdList.toArray(new Integer[labelTexts.length]);
+                article.setA_label_text(labelTexts);
+                article.setA_label_ids(labelIds);
+            }
+        }
+        article.setA_image(imagePath);
+        article.setA_all(1);
+        DocumentDTO<ArticleDO> articleDoc = new DocumentDTO<>();
+        articleDoc.setCmd("add");
+        articleDoc.setFields(article);
+        articleList.add(articleDoc);
+    }
+
+    /**
+     * 商品主文档设值
+     *
+     * @param mainList list
+     * @param mainId int
+     * @param docId int
+     */
+    private void setMainDocument(Integer type, List<DocumentDTO<MainDO>> mainList, Integer mainId, Integer docId) {
+        // 搜索主文档数据
+        DocumentDTO<MainDO> mainDoc = new DocumentDTO<>();
+        MainDO mainData = new MainDO();
+        mainData.setId(mainId);
+        if (type == 1) {
+            // 商品文档
+            mainData.setP_id(docId);
+        } else if (type == 2) {
+            // 供应商文档
+            mainData.setS_id(docId);
+        } else if (type == 3) {
+            // 项目仪器文档
+            mainData.setE_id(docId);
+        } else if (type == 4) {
+            // 文章文档
+            mainData.setA_id(docId);
+        }
+        mainDoc.setCmd("add");
+        mainDoc.setFields(mainData);
+        mainList.add(mainDoc);
+    }
+}

+ 159 - 0
src/main/java/com/caimei365/commodity/service/impl/SearchProductServiceImpl.java

@@ -0,0 +1,159 @@
+package com.caimei365.commodity.service.impl;
+
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONException;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
+import com.aliyun.opensearch.sdk.generated.search.SearchParams;
+import com.caimei365.commodity.components.SearchOpenService;
+import com.caimei365.commodity.mapper.SearchMapper;
+import com.caimei365.commodity.model.ResponseJson;
+import com.caimei365.commodity.model.search.ProductDO;
+import com.caimei365.commodity.model.vo.PageVo;
+import com.caimei365.commodity.model.vo.ProductListVo;
+import com.caimei365.commodity.service.SearchProductService;
+import com.caimei365.commodity.utils.Json2PojoUtil;
+import com.caimei365.commodity.utils.PriceUtil;
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+@Slf4j
+@Service
+public class SearchProductServiceImpl implements SearchProductService {
+
+    private static final Integer SEARCH_MAX_NUM = 500;
+    @Value("${caimei.wwwDomain}")
+    private String domain;
+    @Resource
+    private SearchMapper searchMapper;
+    @Resource
+    private SearchOpenService searchOpenService;
+
+    /**
+     * 根据关键词搜索商品
+     *
+     * @param keyword   搜索关键字
+     * @param pageNum   页码
+     * @param pageSize  每页数量
+     * @param sortField 排序字段
+     * @param sortType  升降序0/1
+     * @return JsonStr(list)
+     */
+    @Override
+    public ResponseJson<String> queryProductByKeyword(String keyword, Integer identity, int pageNum, int pageSize, String sortField, Integer sortType) {
+        String queryStr = "product:'" + keyword + "'";
+        if (StringUtils.isEmpty(keyword)) {
+            queryStr = "p_all:'1'";
+        }
+        // 阿里云搜索
+        ResponseJson<String> result = queryProduct(queryStr, "", identity, pageNum, pageSize, sortField, sortType);
+        if (0 == result.getCode()) {
+            return result;
+        } else {
+            // 阿里云搜索失败,再次从数据库搜索
+            PageHelper.startPage(pageNum, pageSize);
+            List<ProductListVo> productList = searchMapper.queryProductFromDb(identity, keyword, null, null, null, null, sortField, sortType);
+            productList.forEach(product -> {
+                product.setPriceGrade(PriceUtil.getpriceGrade(product.getPrice()));
+                product.setPrice(0d);
+            });
+            PageVo<ProductListVo> pageData = new PageVo<>(productList);
+            int total = pageData.getTotalRecord();
+            JSONObject pageObj = new JSONObject();
+            pageObj.put("total", total);
+            pageObj.put("items", productList);
+            log.info(">>>>>>数据库容错查询("+queryStr+"): pageSize(" + pageNum +"),pageNum("+pageSize+"),total("+total+")");
+            if (productList.size()>0) {
+                return ResponseJson.success(pageObj.toString());
+            } else {
+                return ResponseJson.error("未查询到文档记录(数据库)", null);
+            }
+        }
+    }
+
+    /**
+     * 查询商品
+     *
+     * @param queryStr  查询类型及参数
+     * @param num       页码
+     * @param size      每页数量
+     * @param sortField 排序字段
+     * @param sortType  升降序
+     * @return JsonStr(list)
+     */
+    public ResponseJson<String> queryProduct(String queryStr, String filter, Integer identity, int num, int size, String sortField, Integer sortType) {
+        SearchParams searchParams;
+        int requestSize = num*size;
+        if (requestSize > SEARCH_MAX_NUM){
+            searchParams = searchOpenService.getProductParams(queryStr, filter, identity,num, size, sortField, sortType);
+        } else {
+            // 由于阿里云搜索机制问题(分页数据重复),搜索500条以内数据手动分页。
+            searchParams = searchOpenService.getProductParams(queryStr, filter, identity,1, requestSize, sortField, sortType);
+        }
+        // 查询推送
+        try {
+            String jsonStr = searchOpenService.pushQueryDocument(searchParams);
+            // 处理查询结果
+            JSONObject jsonObj = new JSONObject(jsonStr);
+            String status = jsonObj.getString("status");
+            String requestId = jsonObj.getString("request_id");
+            String tracer = jsonObj.getString("tracer");
+            JSONObject result = jsonObj.getJSONObject("result");
+            JSONArray errors = jsonObj.getJSONArray("errors");
+            log.info(">>>>>>query("+queryStr+"): " + status + ",request_id=" +requestId+ ",errors=" + errors.toString() + ",tracer="+tracer);
+            if ("OK".equals(status)) {
+                int total = result.getInt("total");
+                int totalPage = (int) Math.ceil( (double)total/size);
+                JSONArray resultArr = result.getJSONArray("items");
+                JSONArray pageData = new JSONArray();
+                if (requestSize > SEARCH_MAX_NUM) {
+                    pageData = resultArr;
+                } else {
+                    if (resultArr.length()>0){
+                        int baseNum = (num-1)*size;
+                        for (int i=0; i<size; i++) {
+                            int index = baseNum + i;
+                            int currentTotal = requestSize;
+                            if (totalPage == num){
+                                currentTotal = total;
+                            }
+                            if (index<currentTotal){
+                                JSONObject item = resultArr.getJSONObject(index);
+                                pageData.put(i, item);
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                }
+                // 阿里云查询出json转换成商品pojo
+                List<ProductListVo> productList = Json2PojoUtil.toProductList(pageData);
+                JSONObject pageObj = new JSONObject();
+                pageObj.put("total", total);
+                pageObj.put("items", productList);
+                log.info(">>>>>>pageResult("+queryStr+"): pageSize(" + size +"),pageNum("+num+"),total("+total+")");
+                return ResponseJson.success(pageObj.toString());
+            } else {
+                return ResponseJson.error("未查询到文档记录", null);
+            }
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("查询文档异常:" + e.getMessage());
+            return ResponseJson.error("查询文档异常:" + e.getMessage(), null);
+        }
+    }
+
+}

+ 290 - 0
src/main/java/com/caimei365/commodity/service/impl/SearchQueryServiceImpl.java

@@ -0,0 +1,290 @@
+package com.caimei365.commodity.service.impl;
+
+import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Lists;
+import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Maps;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONException;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
+import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
+import com.aliyun.opensearch.sdk.generated.search.*;
+import com.caimei365.commodity.components.SearchOpenService;
+import com.caimei365.commodity.mapper.SearchMapper;
+import com.caimei365.commodity.service.SearchQueryService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+@Slf4j
+@Service
+public class SearchQueryServiceImpl implements SearchQueryService {
+
+    private static final Integer SEARCH_MAX_NUM = 500;
+    @Value("${caimei.wwwDomain}")
+    private String domain;
+    @Resource
+    private SearchMapper searchMapper;
+    @Resource
+    private SearchOpenService searchOpenService;
+
+    /**
+     * 获取索引Id
+     *
+     * @param docType 文档类型
+     * @param docId   文档ID
+     * @return int
+     */
+    @Override
+    public  Integer getIdByDocId(String docType, Integer docId) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(searchOpenService.getAppName()));
+        config.setStart(0);
+        config.setHits(1);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id"));
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        if (null != docId) {
+            if("product".equals(docType)) {
+                // 根据商品Id获取索引Id
+                searchParams.setQuery("p_id:'" + docId + "'");
+            } else if("shop".equals(docType)) {
+                // 根据供应商Id获取索引Id
+                searchParams.setQuery("s_id:'" + docId + "'");
+            } else if("equipment".equals(docType)) {
+                // 根据项目仪器Id获取索引Id
+                searchParams.setQuery("e_id:'" + docId + "'");
+            } else if("article".equals(docType)) {
+                // 根据文章Id获取索引Id
+                searchParams.setQuery("a_id:'" + docId + "'");
+            }
+        } else {
+            // 创建sort对象,并设置二维排序
+            Sort sorter = new Sort();
+            // 设置id字段降序
+            sorter.addToSortFields(new SortField("id", Order.DECREASE));
+            // 若id相同则以RANK相关性算分升序
+            sorter.addToSortFields(new SortField("RANK", Order.INCREASE));
+            //添加Sort对象参数
+            searchParams.setSort(sorter);
+        }
+        try {
+            // 推送查询操作
+            String jsonStr = searchOpenService.pushQueryDocument(searchParams);
+            // 处理查询结果
+            JSONObject jsonObj = new JSONObject(jsonStr);
+            String status = jsonObj.getString("status");
+            String requestId = jsonObj.getString("request_id");
+            String tracer = jsonObj.getString("tracer");
+            JSONObject result = jsonObj.getJSONObject("result");
+            JSONArray errors = jsonObj.getJSONArray("errors");
+            log.info(">>>>>>query id("+docType+"): " + status + ",request_id=" +requestId+ ",errors=" + errors.toString() + ",tracer="+tracer);
+            if ("OK".equals(status) && result.getJSONArray("items").length()>0) {
+                return result.getJSONArray("items").getJSONObject(0).getInt("id");
+            } else {
+                return -1;
+            }
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("查询文档索引Id异常:" + e);
+            return -1;
+        }
+    }
+
+    /**
+     * 根据文档类型获取索引Id集合
+     *
+     * @param docType  文档类型
+     * @param pageNum   页码
+     * @param pageSize  每页数量
+     * @return map
+     */
+    @Override
+    public Map<Integer, Integer> getMainIdsByDocType(String docType, int pageNum, int pageSize) {
+        String queryStr;
+        String idName;
+        Map<Integer, Integer> idMap = Maps.newLinkedHashMap();
+        if("product".equals(docType)) {
+            queryStr = "p_all:'1'";
+            idName = "p_id";
+        } else if("shop".equals(docType)) {
+            queryStr = "s_all:'1'";
+            idName = "s_id";
+        } else if("equipment".equals(docType)) {
+            queryStr = "e_all:'1'";
+            idName = "e_id";
+        } else if("article".equals(docType)) {
+            queryStr = "a_all:'1'";
+            idName = "a_id";
+        } else {
+            return idMap;
+        }
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(searchOpenService.getAppName()));
+        config.setStart(pageNum-1);
+        config.setHits(pageSize);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id", idName));
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        searchParams.setQuery(queryStr);
+        try {
+            // 推送查询操作
+            String jsonStr = searchOpenService.pushQueryDocument(searchParams);
+            // 处理查询结果
+            JSONObject jsonObj = new JSONObject(jsonStr);
+            String status = jsonObj.getString("status");
+            String requestId = jsonObj.getString("request_id");
+            String tracer = jsonObj.getString("tracer");
+            JSONObject result = jsonObj.getJSONObject("result");
+            JSONArray errors = jsonObj.getJSONArray("errors");
+            log.info(">>>>>>query idMap("+idName+"): " + status + ",request_id=" +requestId+ ",errors=" + errors.toString() + ",tracer="+tracer);
+            if ("OK".equals(status)) {
+                JSONArray jsonArray = result.getJSONArray("items");
+                for (int i = 0; i < jsonArray.length(); i++) {
+                    idMap.put(jsonArray.getJSONObject(i).getInt(idName), jsonArray.getJSONObject(i).getInt("id"));
+                }
+            }
+            return idMap;
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("查询文档索引Id异常:" + e);
+            return idMap;
+        }
+    }
+
+    /**
+     * 根据文档类型获取数量
+     *
+     * @param docType 文档类型
+     * @return int
+     */
+    @Override
+    public Integer getRecordByDocType(String docType) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(searchOpenService.getAppName()));
+        config.setStart(0);
+        config.setHits(1);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id"));
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        if("product".equals(docType)) {
+            searchParams.setQuery("p_all:'1'");
+        } else if("shop".equals(docType)) {
+            searchParams.setQuery("s_all:'1'");
+        } else if("equipment".equals(docType)) {
+            searchParams.setQuery("e_all:'1'");
+        } else if("article".equals(docType)) {
+            searchParams.setQuery("a_all:'1'");
+        } else {
+            return 0;
+        }
+        //设置统计子句
+        Aggregate agg = new Aggregate();
+        agg.setGroupKey(docType);
+        agg.setAggFun("count()");
+        agg.setAggFilter(docType + "=1");
+        //添加Aggregate对象参数
+        searchParams.addToAggregates(agg);
+        try {
+            // 推送查询操作
+            String jsonStr = searchOpenService.pushQueryDocument(searchParams);
+            // 处理查询结果
+            JSONObject jsonObj = new JSONObject(jsonStr);
+            String status = jsonObj.getString("status");
+            String requestId = jsonObj.getString("request_id");
+            String tracer = jsonObj.getString("tracer");
+            JSONObject result = jsonObj.getJSONObject("result");
+            JSONArray errors = jsonObj.getJSONArray("errors");
+            log.info(">>>>>>query  count("+docType+"): " + status + ",request_id=" +requestId+ ",errors=" + errors.toString() + ",tracer="+tracer);
+            if ("OK".equals(status)) {
+                return result.getJSONArray("facet").getJSONObject(0)
+                             .getJSONArray("items").getJSONObject(0).getInt("count");
+            } else {
+                return -1;
+            }
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("查询文档索引Id异常:" + e);
+            return -1;
+        }
+    }
+
+    /**
+     * 根据文档类型获取数量(去重用)
+     *
+     * @param docType 文档类型
+     * @param docId   文档ID
+     * @return int
+     */
+    @Override
+    public Integer getCountByDocId(String docType, Integer docId) {
+        // 定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
+        Config config = new Config(Lists.newArrayList(searchOpenService.getAppName()));
+        config.setStart(0);
+        config.setHits(1);
+        // 设置搜索结果返回应用中哪些字段
+        config.setFetchFields(Lists.newArrayList("id"));
+        // 设置返回格式为json格式
+        config.setSearchFormat(SearchFormat.JSON);
+        // 创建参数对象
+        SearchParams searchParams = new SearchParams(config);
+        if (null != docId) {
+            if("product".equals(docType)) {
+                searchParams.setQuery("p_id:'" + docId + "'");
+            } else if("shop".equals(docType)) {
+                searchParams.setQuery("s_id:'" + docId + "'");
+            } else if("equipment".equals(docType)) {
+                searchParams.setQuery("e_id:'" + docId + "'");
+            } else if("article".equals(docType)) {
+                searchParams.setQuery("a_id:'" + docId + "'");
+            } else {
+                return 0;
+            }
+        } else {
+            // 创建sort对象,并设置二维排序
+            Sort sorter = new Sort();
+            // 设置id字段降序
+            sorter.addToSortFields(new SortField("id", Order.DECREASE));
+            // 若id相同则以RANK相关性算分升序
+            sorter.addToSortFields(new SortField("RANK", Order.INCREASE));
+            //添加Sort对象参数
+            searchParams.setSort(sorter);
+        }
+        try {
+            // 推送查询操作
+            String jsonStr = searchOpenService.pushQueryDocument(searchParams);
+            // 处理查询结果
+            JSONObject jsonObj = new JSONObject(jsonStr);
+            String status = jsonObj.getString("status");
+            String requestId = jsonObj.getString("request_id");
+            String tracer = jsonObj.getString("tracer");
+            JSONObject result = jsonObj.getJSONObject("result");
+            JSONArray errors = jsonObj.getJSONArray("errors");
+            log.info(">>>>>>query count("+docType+"): " + status + ",request_id=" +requestId+ ",errors=" + errors.toString() + ",tracer="+tracer);
+            if ("OK".equals(status) && result.getJSONArray("items").length()>=0) {
+                return result.getJSONArray("items").length();
+            } else {
+                return -1;
+            }
+        } catch (OpenSearchClientException | OpenSearchException | JSONException e) {
+            log.error("查询文档索引数量异常:" + e);
+            return -1;
+        }
+    }
+
+
+}

+ 115 - 0
src/main/java/com/caimei365/commodity/utils/ImageUtils.java

@@ -0,0 +1,115 @@
+package com.caimei365.commodity.utils;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2020/3/16
+ */
+public class ImageUtils {
+
+	public static String getImageURL(String dirName, String src, String domain) {
+		return getImageURL(dirName, src, 0,domain);
+	}
+
+	/***
+	 * 获取图片地址
+	 *
+	 * @param src
+	 *            保存在数据库中的图片文件名
+	 * @param type
+	 *            图片的前缀 (如 type = 200 那么则获取的图片是 200_XXX的图片)
+	 * @param dirName
+	 *            图片保存的文件夹名 如 (league)
+	 * @param domain
+	 * 			   加上域名拼成完整路径
+	 * @return
+	 */
+	public static String getImageURL(String dirName, String src, int type, String domain) {
+
+
+		//正式环境 域名 http --- https处理
+		if (domain != null && !"".equals(domain) && domain.startsWith("http:") && domain.toLowerCase().lastIndexOf("config/beta")== -1 && domain.toLowerCase().lastIndexOf("localhost")== -1) {
+			domain = domain.replace("http:", "https:");
+		}
+
+		//正式环境 图片地址 http --- https处理
+		if(src != null && src.startsWith("https:")){
+			//非正式环境 使用http
+			if (domain !=null && !"".equals(domain) && domain.toLowerCase().lastIndexOf("config/beta")>-1 || domain.toLowerCase().lastIndexOf("localhost")>-1){
+				src = src.replace("https:","http:");
+			}
+			return src;
+		}
+
+		//正式环境 图片地址  http --- https处理
+		if(src != null && src.startsWith("http:")){
+			//非正式环境 使用http
+			if (domain !=null && !"".equals(domain) && domain.toLowerCase().lastIndexOf("config/beta")==-1 && domain.toLowerCase().lastIndexOf("localhost")==-1){
+				src = src.replace("http:","https:");
+			}
+			return src;
+		}
+		type = 0 ;
+		dirName = dirName.trim();
+		if (dirName == null) {dirName = "";}
+		if(src == null || src.equalsIgnoreCase("null")) {src = "";}
+		if (src.indexOf(",") > 0) {
+			String tmp = src;
+			src = tmp.substring(0, tmp.indexOf(","));
+		}
+		String image = "/img/default/none.jpg";
+		if (dirName.equals("user")) {
+			image = "/img/default/HeaderImg.png";
+		} else if (dirName.equals("club")) {
+			image = "/img/default/default_club.jpg";
+		} else if (dirName.equals("shopLogo")) {
+			image = "/img/default/suppliver.jpg";
+		}else if (dirName.equals("caiMeiImage")) {
+			image = "/img/default/caiMeiImage.jpg";
+		}else {
+			image = "/img/default/none.jpg";
+		}
+		if (src != null && !src.equals("")) {
+			if (type != 0 || dirName.equals("product")) {
+				src = src.replace("\\", "/");
+				String srcActual = src;
+				String subDirName = "";
+				int index = src.lastIndexOf("/");
+
+				if (index != -1) {
+					subDirName = src.substring(0, index + 1);
+					srcActual = src.substring(index + 1);
+				}
+				boolean b = src.startsWith("/uploadFile");
+				if(b){
+					image = src;
+				}else{
+					image = "/uploadFile/" + dirName + "/" + subDirName
+							+ (type == 0 ? "" : type + "_") + srcActual;
+				}
+			} else {
+				boolean b = src.startsWith("/uploadFile");
+				if(b){
+					image = src;
+				}else{
+					image = "/uploadFile/" + dirName + "/" + src;
+				}
+			}
+		}else {
+			if(StringUtils.isEmpty(image)) {
+				image = "/img/default/none.jpg";
+			}
+		}
+		return domain + image;
+	}
+
+	public static String getProductImageURL(String image, int type, String domain) {
+		if (image == null) {
+			return getImageURL("product", "", type, domain);
+		}
+		return getImageURL("product", image, type, domain);
+	}
+}

+ 51 - 0
src/main/java/com/caimei365/commodity/utils/Json2PojoUtil.java

@@ -0,0 +1,51 @@
+package com.caimei365.commodity.utils;
+
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray;
+import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
+import com.caimei365.commodity.model.vo.ProductListVo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2021/4/7
+ */
+public class Json2PojoUtil {
+    public static List<ProductListVo> toProductList(JSONArray jsonArray) {
+        List<ProductListVo> productList = new ArrayList<>();
+        ProductListVo product = new ProductListVo();
+        for (int i=0; i<jsonArray.length(); i++) {
+            JSONObject json = jsonArray.getJSONObject(i);
+
+            product.setId(json.getInt("id"));
+            /* 商品Id */
+            product.setProductId(json.getInt("p_id"));
+            /* 名称name */
+            product.setName(json.getString("p_name"));
+            /* 搜索关键词searchKey */
+            product.setKeyword(json.getString("p_keyword"));
+            /* 主图mainImage */
+            product.setImage(json.getString("p_image"));
+            /* 是否公开机构价 0公开价格 1不公开价格 */
+            product.setPriceFlag(json.getInt("p_price_flag"));
+            /* 计算后价格等级 */
+            product.setPriceGrade(json.getInt("p_price_grade"));
+            /* 商品货号 */
+            product.setCode(json.getString("p_code"));
+            /* 包装规格 */
+            product.setUnit(json.getString("p_unit"));
+            /* 品牌 */
+            product.setBrandName(json.getString("p_brand_name"));
+            /* 所属供应商Id,关联供应商表 */
+            product.setShopId(json.getInt("p_supplier_id"));
+            /* 美博会商品活动状态 */
+            product.setActFlag(json.getInt("p_act_flag"));
+
+            productList.add(product);
+        }
+        return productList;
+    }
+}

+ 137 - 0
src/main/java/com/caimei365/commodity/utils/PriceUtil.java

@@ -0,0 +1,137 @@
+package com.caimei365.commodity.utils;
+
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+
+/**
+ * Description
+ *
+ * @author : Charles
+ * @date : 2020/6/22
+ */
+public class PriceUtil {
+    /** 默认小数位 */
+	private static final int DEFAULT_SCALE = 4;
+
+	/**
+	 * 两个实数比较
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @return int [1:v1>v2, 0:v1=v2, 1:v1<v2]
+	 */
+	public static int compare(Object v1, Object v2) {
+		BigDecimal b1 = convert(v1);
+		BigDecimal b2 = convert(v2);
+		return b1.compareTo(b2);
+	}
+	/**
+	 * 两个实数相加
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @return BigDecimal
+	 */
+	public static BigDecimal add(Object v1, Object v2) {
+		BigDecimal b1 = convert(v1);
+		BigDecimal b2 = convert(v2);
+		return b1.add(b2);
+	}
+	/**
+	 * 两个实数相减
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @return BigDecimal
+	 */
+	public static BigDecimal sub(Object v1, Object v2) {
+		BigDecimal b1 = convert(v1);
+		BigDecimal b2 = convert(v2);
+		return b1.subtract(b2);
+	}
+	/**
+	 * 两个实数相乘
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @return BigDecimal
+	 */
+	public static BigDecimal mul(Object v1, Object v2) {
+		BigDecimal b1 = convert(v1);
+		BigDecimal b2 = convert(v2);
+		return b1.multiply(b2);
+
+	}
+	/**
+	 * 两个实数相除,四舍五入到默认位数
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @return BigDecimal
+	 */
+	public static BigDecimal div(Object v1, Object v2) {
+		return div(v1, v2, DEFAULT_SCALE);
+	}
+	/**
+	 * 两个实数相除,默认四舍五入到scale位
+	 *
+	 * @param v1 BigDecimal
+	 * @param v2 BigDecimal
+	 * @param scale 小数位
+	 * @return BigDecimal
+	 */
+	public static BigDecimal div(Object v1, Object v2, int scale) {
+		if (scale < 0) {
+			throw new IllegalArgumentException("四舍五入的位数不能为负数");
+		}
+		BigDecimal b1 = convert(v1);
+		BigDecimal b2 = convert(v2);
+		if (equal0(b2)) {
+			throw new IllegalArgumentException("除数不能为0");
+		}
+		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP);
+	}
+	/**
+	 * 类型转换函数
+	 *
+	 * @param o <T>
+	 * @return BigDecimal
+	 */
+	private static BigDecimal convert(Object o) {
+		if (o == null) {
+			return BigDecimal.ZERO;
+		} else if (o instanceof BigDecimal) {
+			return (BigDecimal) o;
+		}
+		String str = o.toString();
+		if (!StringUtils.isEmpty(str)) {
+			return new BigDecimal(str);
+		} else {
+			return BigDecimal.ZERO;
+		}
+	}
+	private static boolean equal0(Object v1) {
+		BigDecimal b1 = convert(v1);
+		return b1.compareTo(BigDecimal.ZERO) == 0;
+	}
+
+    /**
+     * 根据价格返回价格等级
+     * @param price 价格
+     * @return priceGrade 价格等级
+     */
+    public static Integer getpriceGrade(Double price){
+        int grade = 1;
+        if (price > 50 * 10000) {
+            grade = 5;
+        } else if (price > 25 * 10000) {
+            grade = 4;
+        } else if (price > 10 * 10000) {
+            grade = 3;
+        } else if (price > 2.5 * 10000) {
+            grade = 2;
+        }
+        return grade;
+    }
+}

+ 1 - 1
src/main/java/com/caimei365/product/utils/WebClientUtils.java → src/main/java/com/caimei365/commodity/utils/WebClientUtils.java

@@ -1,4 +1,4 @@
-package com.caimei365.product.utils;
+package com.caimei365.commodity.utils;
 
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

+ 0 - 16
src/main/resources/application.yml

@@ -1,16 +0,0 @@
-server:
-  port: 18002
-
-# 指定当前服务的名称,这个名称会注册到注册中心
-spring:
-  application:
-    name: @artifactId@
-
-# 指定服务注册中心的地址
-eureka:
-  instance:
-    prefer-ip-address: true       # 是否使用 ip 地址注册
-    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
-  client:
-    service-url:                  # 设置服务注册中心地址
-      defaultZone: http://localhost:18000/eureka/

+ 15 - 0
src/main/resources/bootstrap.yml

@@ -0,0 +1,15 @@
+server:
+  port: 18012
+
+# 指定当前服务的名称,这个名称会注册到注册中心
+spring:
+  application:
+    name: @artifactId@
+  cloud:
+    config:                             # Config客户端配置
+      profile: @activatedProperties@    # 启用配置后缀名称
+      label: master                     # 分支名称
+      uri: http://localhost:18001
+      # uri: http://47.119.112.46:18001          # 配置中心地址
+      # uri: http://120.79.162.1:18001          # 配置中心地址(正式环境)
+      name: commodity                   # 配置文件名称

+ 469 - 0
src/main/resources/mapper/SearchMapper.xml

@@ -0,0 +1,469 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.caimei365.commodity.mapper.SearchMapper">
+    <sql id="Search_Product_List">
+        p.productID as p_id,
+        p.`name` as p_name,
+        p.searchKey as p_keyword,
+        p.tags as p_tags,
+        p.mainImage as p_image,
+        p.price1 as p_price,
+        p.price1TextFlag as p_price_flag,
+        p.productCode as p_code,
+        p.sortIndex as p_sort,
+        p.unit as p_unit,
+        p.sellNumber as p_sales,
+        p.favoriteTimes as p_favorite,
+        p.brandID as p_brand_id,
+        br.name as p_brand_name,
+        p.shopID as p_supplier_id,
+        sh.name as p_supplier_name,
+        p.bigTypeID as p_category1_id,
+        b.name as p_category1_name,
+        p.smallTypeID as p_category2_id,
+        s.name as p_category2_name,
+        p.tinyTypeID as p_category3_id,
+        t.name as p_category3_name,
+        p.classifyId as p_classify_id,
+        c.classifyName as p_classify_name,
+        p.preferredFlag as p_preferred,
+        p.productCategory as p_type,
+        p.validFlag as p_valid,
+        IFNULL(p.visibility,3) as p_visibility,
+        p.price8Text as p_act_flag
+    </sql>
+    <sql id="Product_Joins">
+        left join tinytype as t on p.tinyTypeID = t.tinyTypeID
+        left join smalltype as s on p.smallTypeID = s.smallTypeID
+        left join bigtype as b on p.bigTypeID = b.bigTypeID
+        left join cm_products_classify as c on p.classifyId = c.id
+        left join cm_brand as br on p.brandID = br.id
+        left join shop as sh on p.shopID = sh.shopID
+    </sql>
+    <select id="searchProductById" resultType="com.caimei365.commodity.model.search.ProductDO">
+        select <include refid="Search_Product_List"/>, p.productCategory
+        from product as p
+        <include refid="Product_Joins"/>
+        where p.validFlag in (2,3,9) and p.productCategory = 1
+        and p.productID = #{productId}
+    </select>
+    <select id="findProductCount" resultType="java.lang.Integer">
+        select count(*)
+        from product
+        where validFlag in (2,3,9) and productCategory = 1
+    </select>
+    <select id="findProductInvalidCount" resultType="java.lang.Integer">
+        select count(*)
+        from product
+        where validFlag not in (2,3,9) or productCategory != 1
+    </select>
+    <select id="findProductInvalidIds" resultType="java.lang.Integer">
+        select productID
+        from product
+        where validFlag not in (2,3,9) or productCategory != 1
+    </select>
+    <select id="searchProductList" resultType="com.caimei365.commodity.model.search.ProductDO">
+        select <include refid="Search_Product_List"/>
+        from product as p
+        <include refid="Product_Joins"/>
+        where p.validFlag in (2,3,9) and productCategory = 1
+        order by productID desc
+    </select>
+    <select id="countMallProduct" resultType="java.lang.Integer">
+        select COUNT(*) from cm_mall_organize_products
+        where productID=#{productId} and organizeID=1
+    </select>
+    <select id="searchMallProductByProductId" resultType="com.caimei365.commodity.model.search.MallProductDO">
+        select
+        m.id as m_id,
+        m.organizeID as m_organize_id,
+        m.productID as m_product_id,
+        m.retailPrice as m_price,
+        m.classifyID as m_classify_id,
+        m.delFlag as m_valid,
+        c.classifyName as m_classify_name
+        from cm_mall_organize_products as m
+        left join cm_mall_products_classify as c on m.classifyId = c.id
+        where m.productID = #{productId} and m.organizeID = 1
+        limit 1
+    </select>
+    <select id="getMallLadderPriceFlag" resultType="java.lang.Integer">
+        select ladderPriceFlag
+        from cm_mall_organize_products
+        where productID = #{productId} and organizeID=1
+        limit 1
+    </select>
+    <select id="getMallLowerLadderPrice" resultType="java.lang.Double">
+        select MIN(buyPrice)
+        from cm_mall_product_ladder_price
+        where delFlag = '0'
+        and productId = #{productId}
+    </select>
+    <select id="findMallIdByProductId" resultType="java.lang.Integer">
+        select id
+        from cm_mall_organize_products
+        where productID = #{productId}
+        limit 1
+    </select>
+    <select id="findMallInvalidIdsByProductIds" resultType="java.lang.Integer">
+        select id
+        from cm_mall_organize_products
+        where productID IN
+          <foreach collection="invalidIds" open="(" separator="," close=")" item="productId">
+              #{productId}
+          </foreach>
+    </select>
+
+    <!-- 供应商 -->
+    <sql id="Search_Supplier_List">
+        s.shopID as s_id,
+        s.name as s_name,
+        s.logo as s_logo,
+        s.authorizationCertificateImage as s_license,
+        s.businessScope as s_business,
+        s.townID as s_town_id,
+        CONCAT(p.name, c.name) as s_address,
+        s.sortIndex as s_sort,
+        s.status as s_valid
+    </sql>
+    <sql id="Supplier_Joins">
+        left join town as t on s.townId = t.townID
+        left join city as c on t.cityID = c.cityID
+        left join province as p on c.provinceID = p.provinceID
+    </sql>
+    <select id="findSupplierCount" resultType="java.lang.Integer">
+        select count(*)
+        from shop
+        where (status = 90 or status = 9)
+        and shopID != 1252
+    </select>
+    <select id="findSupplierInvalidCount" resultType="java.lang.Integer">
+        select count(*)
+        from shop
+        where status not in (90,9) or shopID = 1252
+    </select>
+    <select id="findSupplierInvalidIds" resultType="java.lang.Integer">
+        select shopID
+        from shop
+        where status not in (90,9) or shopID = 1252
+    </select>
+    <select id="searchSupplierList" resultType="com.caimei365.commodity.model.search.SupplierDO">
+        select <include refid="Search_Supplier_List"/>
+        from shop as s
+        <include refid="Supplier_Joins"/>
+        where (status = 90 or status = 9)
+        and shopID != 1252
+        order by s.sortIndex
+    </select>
+    <select id="searchSupplierById" resultType="com.caimei365.commodity.model.search.SupplierDO">
+        select <include refid="Search_Supplier_List"/>
+        from shop as s
+        <include refid="Supplier_Joins"/>
+        where (status = 90 or status = 9)
+        and shopID != 1252
+        and s.shopID = #{supplierId}
+    </select>
+
+    <!-- 项目仪器 -->
+    <sql id="Search_Equipment_List">
+        e.id as e_id,
+        e.title as e_name,
+        e.precisehKey as e_keyword,
+        e.headImage as e_image,
+        e.docBoost as e_sort
+    </sql>
+    <select id="findEquipmentCount" resultType="java.lang.Integer">
+        select count(*)
+        from cm_page
+        where type = 2 and enabledStatus = 1
+    </select>
+    <select id="findEquipmentInvalidCount" resultType="java.lang.Integer">
+        select count(*)
+        from cm_page
+        where type != 2 or enabledStatus != 1
+    </select>
+    <select id="findEquipmentInvalidIds" resultType="java.lang.Integer">
+        select id
+        from cm_page
+        where type != 2 or enabledStatus != 1
+    </select>
+    <select id="searchEquipmentList" resultType="com.caimei365.commodity.model.search.EquipmentDO">
+        select <include refid="Search_Equipment_List"/>
+        from cm_page as e
+        where e.type = 2 and e.enabledStatus = 1
+        order by e.docBoost desc
+    </select>
+    <select id="searchEquipmentById" resultType="com.caimei365.commodity.model.search.EquipmentDO">
+        select <include refid="Search_Equipment_List"/>
+        from cm_page as e
+        where e.id = #{equipmentId}
+        and e.type = 2 and e.enabledStatus = 1
+    </select>
+
+    <!-- 文章 -->
+    <sql id="Search_Article_List">
+        a.id as a_id,
+        a.title as a_title,
+        a.guidanceImage as a_image,
+        a.publisher as a_publisher,
+        a.pubdate as a_publish_date,
+        a.recommendContent as a_intro,
+        a.infoContent as a_content,
+        (IFNULL(c.pv, 0) +IFNULL(c.num, 0) + IFNULL(a.basePv, 0) + IFNULL(a.basePraise, 0)) as a_pv,
+        a.label as a_label,
+        a.typeId as a_type_id,
+        b.name as a_type_text,
+        a.priorityIndex as a_sort
+    </sql>
+	<sql id="Article_Joins">
+		left join info_type b on a.typeId = b.id
+		left join info_praise c on a.id = c.infoId
+	</sql>
+    <select id="findArticleCount" resultType="java.lang.Integer">
+        select count(*)
+        from info
+        where enabledStatus = 1
+    </select>
+    <select id="findArticleInvalidCount" resultType="java.lang.Integer">
+        select count(*)
+        from info
+        where enabledStatus != 1
+    </select>
+    <select id="findArticleInvalidIds" resultType="java.lang.Integer">
+        select id
+        from info
+        where enabledStatus != 1
+    </select>
+    <select id="searchArticleList" resultType="com.caimei365.commodity.model.search.ArticleDO">
+        select <include refid="Search_Article_List"/>
+        from info as a
+        <include refid="Article_Joins"/>
+        where a.enabledStatus = 1
+        order by a.priorityIndex desc
+    </select>
+    <select id="searchArticleById" resultType="com.caimei365.commodity.model.search.ArticleDO">
+        select <include refid="Search_Article_List"/>
+        from info as a
+        <include refid="Article_Joins"/>
+        where a.id = #{articleId}
+        and a.enabledStatus = 1
+    </select>
+    <select id="findLabelIdsByName" resultType="java.lang.Integer">
+		select id
+		from info_label
+		where  infoLabelStatus = 1
+		and name in
+		<foreach collection="labelTexts" item="label" open="(" close=")" index="index" separator=",">
+			#{label}
+		</foreach>
+		group by name
+		order by clickRate desc
+    </select>
+
+    <!-- 搜索容错 商品列表 -->
+    <select id="queryProductFromDb" resultType="com.caimei365.commodity.model.vo.ProductListVo">
+        select
+            p.productID as productId,
+            p.`name` as name,
+            p.mainImage as image,
+            br.name as brandName,
+            p.unit as unit,
+            p.productCode as code,
+            p.price1TextFlag as priceFlag,
+            p.price1 as price,
+            p.shopID as shopId,
+            p.searchKey as keyword,
+            p.price8Text as p_act_flag
+        from product p
+        left join cm_brand as br on p.brandID = br.id
+        where p.productCategory = 1
+        <choose>
+            <when test="identity == 1">
+                and p.validFlag in (2,3,9)
+            </when>
+            <when test="identity == 2">
+                and p.validFlag = 2
+            </when>
+            <when test="identity == 4">
+                and p.visibility in (2,3) and p.validFlag = 2
+            </when>
+            <otherwise>
+                and p.visibility = 3 and p.validFlag = 2
+            </otherwise>
+        </choose>
+        <if test="shopId != null and shopId != ''">
+            and p.shopID = #{shopId}
+        </if>
+        <if test="bigTypeId != null and bigTypeId != ''">
+            and p.bigTypeID = #{bigTypeId}
+        </if>
+        <if test="smallTypeId != null and smallTypeId != ''">
+            and p.smallTypeID = #{smallTypeId}
+        </if>
+        <if test="tinyTypeId != null and tinyTypeId != ''">
+            and p.tinyTypeID = #{tinyTypeId}
+        </if>
+        <if test="keyword != null and keyword != ''">
+            and p.name like concat('%',#{keyword},'%')
+        </if>
+        <choose>
+            <when test="sortField != null and sortField != ''">
+                <choose>
+                    <when test="sortField == 'p_price'">
+                        order by p.price1
+                    </when>
+                    <when test="sortField == 'p_sales'">
+                        order by p.sellNumber
+                    </when>
+                    <when test="sortField == 'p_favorite'">
+                        order by p.favoriteTimes
+                    </when>
+                    <otherwise>
+                        order by p.productID
+                    </otherwise>
+                </choose>
+            </when>
+            <otherwise>
+                order by p.productID
+            </otherwise>
+        </choose>
+        <choose>
+            <when test="sortType == 1">
+                desc
+            </when>
+            <otherwise>
+                asc
+            </otherwise>
+        </choose>
+    </select>
+
+
+
+
+    <select id="searchDbSupplierByKeyword" resultType="com.caimei365.commodity.model.search.SupplierDO">
+        select
+        s.shopID as s_id,
+        s.businessScope as s_business,
+        s.authorizationCertificateImage as s_license,
+        s.name as s_name,
+        s.logo as s_logo,
+        s.townID as s_town_id,
+        CONCAT(p.name, c.name) as s_address,
+        s.sortIndex as s_sort,
+        s.status as s_valid
+        from shop as s
+        left join town as t on s.townId = t.townID
+        left join city as c on t.cityID = c.cityID
+        left join province as p on c.provinceID = p.provinceID
+        where (status = 90 or status = 9)
+        and shopID != 1252
+        and s.name like concat('%',#{keyword},'%')
+    </select>
+
+
+    <select id="searchDbEquipmentByKeyword" resultType="com.caimei365.commodity.model.search.EquipmentDO">
+        select <include refid="Search_Equipment_List"/>
+        from cm_page as e
+        where e.type = 2 and e.enabledStatus = 1
+        and e.title like concat('%',#{keyword},'%')
+        order by e.docBoost desc
+    </select>
+
+    <select id="searchDbProduct" resultType="com.caimei365.commodity.model.search.ProductDO">
+        select <include refid="Search_Product_List"/>, p.productCategory
+        from product as p
+        <include refid="Product_Joins"/>
+        <where>
+            <choose>
+                <when test="identity == 1">
+                    p.validFlag in (2,3,9) and p.productCategory = 1
+                </when>
+                <when test="identity == 2">
+                    p.validFlag = 2 and p.productCategory = 1
+                </when>
+                <when test="identity == 4">
+                    p.visibility in (2,3) and p.validFlag = 2 and p.productCategory = 1
+                </when>
+                <otherwise>
+                    p.visibility = 3 and p.validFlag = 2 and p.productCategory = 1
+                </otherwise>
+            </choose>
+            <if test="keyword != null and keyword != ''">
+                and p.name LIKE concat('%',#{keyword},'%')
+            </if>
+            <if test="shopID != null">
+                and sh.shopID = #{shopID}
+            </if>
+            <if test="typeId != null">
+                <choose>
+                    <when test="idType == 1">
+                        and p.bigTypeId = #{typeId}
+                    </when>
+                    <when test="idType == 2">
+                        and p.smallTypeId = #{typeId}
+                    </when>
+                    <when test="idType == 3">
+                        and p.tinyTypeId = #{typeId}
+                    </when>
+                </choose>
+            </if>
+        </where>
+        <choose>
+            <when test="sortField != null and sortField != ''">
+                <choose>
+                    <when test="sortField == 'p_price'">
+                        order by p.price1
+                    </when>
+                    <when test="sortField == 'p_sales'">
+                        order by p.sellNumber
+                    </when>
+                    <when test="sortField == 'p_favorite'">
+                        order by p.favoriteTimes
+                    </when>
+                    <otherwise>
+                        order by p.productID
+                    </otherwise>
+                </choose>
+            </when>
+            <otherwise>
+                order by p.productID
+            </otherwise>
+        </choose>
+        <choose>
+            <when test="sortType == 1">
+                desc
+            </when>
+            <otherwise>
+                asc
+            </otherwise>
+        </choose>
+    </select>
+    <select id="searchDbArticle" resultType="com.caimei365.commodity.model.search.DbArticleDO">
+        select <include refid="Search_Article_List"/>
+        from info as a
+        <include refid="Article_Joins"/>
+        <where>
+            a.enabledStatus = 1
+            <if test="articleId != null">
+                and a.id = #{articleId}
+            </if>
+            <if test="keyword != null and keyword != ''">
+                and a.title like concat ('%',#{keyword},'%')
+            </if>
+            <if test="typeId != null">
+                and a.typeId = #{typeId}
+            </if>
+            <if test="labelId != null">
+                and a.label = #{labelId}
+            </if>
+        </where>
+        <choose>
+            <when test="typeId != null or labelId != null">
+                order by a.id desc
+            </when>
+            <otherwise>
+                order by a.priorityIndex desc
+            </otherwise>
+        </choose>
+    </select>
+</mapper>

+ 1 - 1
src/test/java/com/caimei365/product/ProductApplicationTests.java → src/test/java/com/caimei365/commodity/ProductApplicationTests.java

@@ -1,4 +1,4 @@
-package com.caimei365.product;
+package com.caimei365.commodity;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;