前端 + 后端:商品搜索
This commit is contained in:
parent
68dadab873
commit
b98e21e157
@ -9,6 +9,10 @@ import java.util.List;
|
||||
|
||||
public class StringUtil {
|
||||
|
||||
public static boolean hasText(String str) {
|
||||
return StringUtils.hasText(str);
|
||||
}
|
||||
|
||||
public static String join(Collection<?> coll, String delim) {
|
||||
return StringUtils.collectionToDelimitedString(coll, delim);
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package cn.iocoder.common.framework.vo;
|
||||
|
||||
/**
|
||||
* 排序字段 DTO
|
||||
*
|
||||
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
|
||||
*/
|
||||
public class SortingField {
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
private String field;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private String order;
|
||||
|
||||
public SortingField(String field, String order) {
|
||||
this.field = field;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public SortingField setField(String field) {
|
||||
this.field = field;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public SortingField setOrder(String order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
}
|
16
mobile-web/src/api/search.js
Normal file
16
mobile-web/src/api/search.js
Normal file
@ -0,0 +1,16 @@
|
||||
import request from "../config/request";
|
||||
|
||||
export function getProductPage({cid, keyword, pageNo, pageSize, sortField, sortOrder}) {
|
||||
return request({
|
||||
url: '/search-api/users/product/page',
|
||||
method: 'get',
|
||||
params: {
|
||||
cid,
|
||||
keyword,
|
||||
pageNo: pageNo || 1,
|
||||
pageSize: pageSize || 10,
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
}
|
||||
});
|
||||
}
|
@ -8,17 +8,20 @@
|
||||
style="background:#fff"
|
||||
>
|
||||
<template slot="thumb">
|
||||
<img :src="product.picUrls[0]"/>
|
||||
<img :src="product.picUrls && product.picUrls ? product.picUrls[0] : ''"/>
|
||||
<!-- TODO 芋艿 暂时去掉 -->
|
||||
<!-- <p v-if="product.imageTag!=null&&product.imageTag!=''" class="image_tag">{{product.imageTag}}</p>-->
|
||||
</template>
|
||||
<template slot="tags">
|
||||
<p class="price" v-if="product.price!=null && product.price !== ''">
|
||||
¥<span>{{product.price / 100.00}}</span>
|
||||
<p class="price" v-if="product.buyPrice || product.price">
|
||||
¥<span>{{product.buyPrice ? product.buyPrice / 100.00 : product.price / 100.00}}</span>
|
||||
<!-- TODO 芋艿 暂时去掉 -->
|
||||
<!-- <van-tag v-if="product.tags!=null" v-for="tag in product.tags" :key="tag" plain type="danger">-->
|
||||
<!-- {{tag}}-->
|
||||
<!-- </van-tag>-->
|
||||
<van-tag v-if="product.promotionActivityTitle" plain type="danger">
|
||||
{{ product.promotionActivityTitle }}
|
||||
</van-tag>
|
||||
|
||||
</p>
|
||||
<!-- TODO 芋艿 暂时去掉 -->
|
||||
|
@ -8,10 +8,10 @@
|
||||
placeholder="请输入搜索关键词"
|
||||
background="#fff"
|
||||
show-action
|
||||
@search="onSearch"
|
||||
@search="onSearchClick"
|
||||
slot="title"
|
||||
>
|
||||
<div slot="action" @click="onSearch">搜索</div>
|
||||
<div slot="action" @click="onSearchClick">搜索</div>
|
||||
</van-search>
|
||||
</van-nav-bar>
|
||||
|
||||
@ -27,14 +27,19 @@ export default {
|
||||
components:{
|
||||
[Search.name]:Search,
|
||||
},
|
||||
props: {
|
||||
// keyword: String,
|
||||
// onSearch: Function,
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
value:'',
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onSearch() {
|
||||
console.log(this.value);
|
||||
onSearchClick() {
|
||||
// this.props.onSearch(this.keyword);
|
||||
this.$emit('onSearch', this.value);
|
||||
},
|
||||
onBack() {
|
||||
history.back();
|
||||
|
@ -28,6 +28,10 @@ const serviceRouter = function(requestUrl) {
|
||||
prefix: '/pay-api',
|
||||
target: 'http://127.0.0.1:18084/pay-api',
|
||||
},
|
||||
'/search-api': {
|
||||
prefix: '/search-api',
|
||||
target: 'http://127.0.0.1:18086/search-api',
|
||||
},
|
||||
};
|
||||
|
||||
const configProd = {
|
||||
@ -51,6 +55,10 @@ const serviceRouter = function(requestUrl) {
|
||||
prefix: '/pay-api',
|
||||
target: 'http://api.shop.iocoder.cn:18099/pay-api',
|
||||
},
|
||||
'/search-api': {
|
||||
prefix: '/search-api',
|
||||
target: 'http://api.shop.iocoder.cn:18099/search-api',
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
|
@ -153,13 +153,6 @@ const routes = [
|
||||
title: '进度详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/product/:id',
|
||||
component: () => import('../page/product/detail'),
|
||||
meta: {
|
||||
title: '商品详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/product/search',
|
||||
component: () => import('../page/product/search'),
|
||||
@ -167,6 +160,13 @@ const routes = [
|
||||
title: '商品搜索'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/product/:id',
|
||||
component: () => import('../page/product/detail'),
|
||||
meta: {
|
||||
title: '商品详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/products/list',
|
||||
component: () => import('../page/product/list'),
|
||||
|
@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<div class="product-list">
|
||||
<searchtop/>
|
||||
<searchtop @onSearch="onSearch" />
|
||||
<div class="filterbar">
|
||||
<ul :class="filtersort?'show':''">
|
||||
<li :class="filterindex==0?'selected':''" v-on:click="onFilterBar(0)"><span>{{filterindex==11?'价格最低':(filterindex==12?'价格最高':'综合')}}</span><van-icon name="arrow" class="down" /></li>
|
||||
<li :class="filterindex==1?'selected':''" v-on:click="onFilterBar(1)"><span>销量</span></li>
|
||||
<li :class="filterindex==2?'selected':''" v-on:click="onFilterBar(2)"><span>上新</span></li>
|
||||
<li :class="filterindex==3?'selected':''" v-on:click="onFilterBar(3)"><span>筛选</span></li>
|
||||
<ul :class="filterSort?'show':''">
|
||||
<li :class="filterIndex===0?'selected':''" v-on:click="onFilterBar(0)"><span>{{filterIndex==11?'价格最低':(filterIndex==12?'价格最高':'综合')}}</span><van-icon name="arrow" class="down" /></li>
|
||||
<li :class="filterIndex===1?'selected':''" v-on:click="onFilterBar(1)"><span>销量</span></li>
|
||||
<li :class="filterIndex===2?'selected':''" v-on:click="onFilterBar(2)"><span>上新</span></li>
|
||||
<li :class="filterIndex===3?'selected':''" v-on:click="onFilterBar(3)"><span>筛选</span></li>
|
||||
</ul>
|
||||
<div :class="'item_options '+(filtersort?'show':'')">
|
||||
<div :class="'item_options '+(filterSort?'show':'')">
|
||||
<ul>
|
||||
<li :class="filterindex==10?'selected':''" v-on:click="onFilterBar(10)">综合</li>
|
||||
<li :class="filterindex==11?'selected':''" v-on:click="onFilterBar(11)">价格最低</li>
|
||||
<li :class="filterindex==12?'selected':''" v-on:click="onFilterBar(12)">价格最高</li>
|
||||
<li :class="filterIndex==10?'selected':''" v-on:click="onFilterBar(10)">综合</li>
|
||||
<li :class="filterIndex==11?'selected':''" v-on:click="onFilterBar(11)">价格降序</li>
|
||||
<li :class="filterIndex==12?'selected':''" v-on:click="onFilterBar(12)">价格最高</li>
|
||||
</ul>
|
||||
</div>
|
||||
<van-popup v-model="filtershow" position="right" class="filterlayer" >
|
||||
<van-popup v-model="filterShow" position="right" class="filterlayer" >
|
||||
<div class="filterInner" style="overflow-y: scroll;max-height: 100%;">
|
||||
<ul>
|
||||
<li>
|
||||
@ -163,14 +163,23 @@
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div v-for="(product,i) in products" :key="i">
|
||||
<product-card :product='product' @click="showProduct(product)" />
|
||||
</div>
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<div v-for="(product,i) in products" :key="i">
|
||||
<product-card :product='product' @click="showProduct(product)" />
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import searchtop from "../../components/search/searchtop";
|
||||
import {getProductPage} from "../../api/search";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -178,70 +187,120 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
filterindex: 0,
|
||||
filtersort: false,
|
||||
filtershow: false,
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
loading: false,
|
||||
finished: false,
|
||||
|
||||
products:[
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'13.00',
|
||||
},
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'499.00',
|
||||
tags:['满199减100','2件起购'],
|
||||
},
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'499.00',
|
||||
tags:['新品'],
|
||||
imageTag:'仅剩1件',
|
||||
},
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'499.00',
|
||||
tags:['赠'],
|
||||
imageTag:'预约',
|
||||
},
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'15.00',
|
||||
},
|
||||
{
|
||||
id:1,
|
||||
imageURL:'https://pop.nosdn.127.net/19e33c9b-6c22-4a4b-96da-1cb7afb32712',
|
||||
title:'BEYOND博洋家纺 床上套件 秋冬保暖纯棉床单被套 双人被罩 磨毛全棉印花床品四件套',
|
||||
price:'125.50',
|
||||
},
|
||||
]
|
||||
keyword: "",
|
||||
|
||||
filterIndex: 0,
|
||||
filterSort: false, // 是否展示几个【排序】
|
||||
filterShow: false, // 是否展示【筛选】
|
||||
|
||||
sortField: undefined,
|
||||
sortOrder: undefined,
|
||||
|
||||
products:[]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onFilterBar(value) {
|
||||
if (value == 0) {
|
||||
this.filtersort = !this.filtersort;
|
||||
} else if (value == 3) {
|
||||
this.filtershow = !this.filtershow;
|
||||
if (value === 0) {
|
||||
this.filterSort = !this.filterSort;
|
||||
} else if (value === 3) {
|
||||
this.filterShow = !this.filterShow;
|
||||
} else {
|
||||
this.filtersort = false;
|
||||
this.filterindex = value;
|
||||
// 如果非合法 10、11、12
|
||||
if (value !== 10
|
||||
&& value !== 11
|
||||
&& value !== 12) {
|
||||
alert('暂不支持');
|
||||
return;
|
||||
}
|
||||
// 设置 filterSort 和 filterIndex 属性
|
||||
this.filterSort = false;
|
||||
this.filterIndex = value;
|
||||
// 标记加载中
|
||||
this.loading = true;
|
||||
// 根据 value 的值,设置 sortField、sortOrder
|
||||
switch (value) {
|
||||
case 10:
|
||||
this.sortField = undefined;
|
||||
this.sortOrder = undefined;
|
||||
break;
|
||||
case 11:
|
||||
this.sortField = 'buyPrice';
|
||||
this.sortOrder = 'desc';
|
||||
break;
|
||||
case 12:
|
||||
this.sortField = 'buyPrice';
|
||||
this.sortOrder = 'asc';
|
||||
break;
|
||||
}
|
||||
// 根据排序,重新搜索
|
||||
let page = 1;
|
||||
getProductPage({
|
||||
pageNo: page,
|
||||
pageSize: this.pageSize,
|
||||
keyword: this.keyword,
|
||||
sortField: this.sortField,
|
||||
sortOrder: this.sortOrder,
|
||||
}).then(data => {
|
||||
this.products = [];
|
||||
this.handleData(page, data);
|
||||
});
|
||||
}
|
||||
},
|
||||
showProduct(product){
|
||||
this.$router.push('/product/'+product.id);
|
||||
},
|
||||
onSearch(keyword) {
|
||||
this.loading = true;
|
||||
// 设置 keyword
|
||||
this.keyword = keyword;
|
||||
// 重置其它字段
|
||||
this.filterIndex = 0;
|
||||
this.filterSort = false;
|
||||
this.filterShow = false;
|
||||
this.sortField = undefined;
|
||||
this.sortOrder = undefined;
|
||||
// 查询
|
||||
let page = 1;
|
||||
getProductPage({
|
||||
pageNo: page,
|
||||
pageSize: this.pageSize,
|
||||
keyword: keyword,
|
||||
}).then(data => {
|
||||
this.products = [];
|
||||
this.handleData(page, data);
|
||||
});
|
||||
},
|
||||
onLoad() {
|
||||
// 进入下一页
|
||||
let page = this.page + 1;
|
||||
getProductPage({
|
||||
pageNo: page,
|
||||
pageSize: this.pageSize,
|
||||
}).then(data => {
|
||||
this.handleData(page, data);
|
||||
});
|
||||
},
|
||||
handleData(page, data) {
|
||||
this.loading = true;
|
||||
// 设置下页面
|
||||
this.page = page;
|
||||
// 数据保存到 list 中
|
||||
this.products.push(...data.list);
|
||||
// 判断页数
|
||||
if (this.products.length >= data.total) {
|
||||
this.finished = true;
|
||||
}
|
||||
// 标记不在加载中
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -4,12 +4,14 @@ import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 计算商品 SKU 价格结果 BO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class CalcSkuPriceBO {
|
||||
public class CalcSkuPriceBO implements Serializable {
|
||||
|
||||
/**
|
||||
* 满减送促销活动
|
||||
|
@ -33,10 +33,10 @@
|
||||
FROM product_spu
|
||||
<where>
|
||||
<if test="id != null">
|
||||
id >= #{id}
|
||||
id > #{id}
|
||||
</if>
|
||||
AND deleted = 0
|
||||
</where>
|
||||
AND deleted = 0
|
||||
ORDER BY id ASC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
@ -11,5 +11,79 @@
|
||||
|
||||
<artifactId>search-application</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>user-sdk</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>search-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>search-service-impl</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 提供给 mapstruct 使用 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<!-- 打包 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -0,0 +1,13 @@
|
||||
package cn.iocoder.mall.search.application;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.search"})
|
||||
public class SearchApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SearchApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package cn.iocoder.mall.search.application.config;
|
||||
|
||||
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
||||
import cn.iocoder.common.framework.servlet.CorsFilter;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
|
||||
// AdminSecurityInterceptor.class, UserAccessLogInterceptor.class,
|
||||
// UserSecurityInterceptor.class, AdminAccessLogInterceptor.class,
|
||||
})
|
||||
public class MVCConfiguration implements WebMvcConfigurer {
|
||||
|
||||
// @Autowired
|
||||
// private UserSecurityInterceptor securityInterceptor;
|
||||
|
||||
// @Autowired
|
||||
// private UserSecurityInterceptor userSecurityInterceptor;
|
||||
// @Autowired
|
||||
// private UserAccessLogInterceptor userAccessLogInterceptor;
|
||||
// @Autowired
|
||||
// private AdminSecurityInterceptor adminSecurityInterceptor;
|
||||
// @Autowired
|
||||
// private AdminAccessLogInterceptor adminAccessLogInterceptor;
|
||||
//
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// // 用户
|
||||
// registry.addInterceptor(userAccessLogInterceptor).addPathPatterns("/users/**");
|
||||
// registry.addInterceptor(userSecurityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
|
||||
// // 管理员
|
||||
// registry.addInterceptor(adminAccessLogInterceptor).addPathPatterns("/admins/**");
|
||||
// registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 解决 swagger-ui.html 的访问,参考自 https://stackoverflow.com/questions/43545540/swagger-ui-no-mapping-found-for-http-request 解决
|
||||
registry.addResourceHandler("swagger-ui.html**").addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
|
||||
registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<CorsFilter> corsFilter() {
|
||||
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new CorsFilter());
|
||||
registrationBean.addUrlPatterns("/*");
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cn.iocoder.mall.search.application.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2 // TODO 生产环境时,禁用掉。
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.search.application.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("搜索子系统")
|
||||
.description("搜索子系统")
|
||||
.termsOfServiceUrl("http://www.iocoder.cn")
|
||||
.version("1.0.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package cn.iocoder.mall.search.application.controller.users;
|
||||
|
||||
import cn.iocoder.common.framework.util.StringUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.SortingField;
|
||||
import cn.iocoder.mall.search.api.ProductSearchService;
|
||||
import cn.iocoder.mall.search.api.bo.ESProductPageBO;
|
||||
import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import io.swagger.annotations.Api;
|
||||
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;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("users/product")
|
||||
@Api("商品搜索")
|
||||
public class UsersProductSearchController {
|
||||
|
||||
@Reference(validation = "true")
|
||||
private ProductSearchService productSearchService;
|
||||
|
||||
@GetMapping("/page") // TODO 芋艿,后面把 BO 改成 VO
|
||||
public CommonResult<ESProductPageBO> page(@RequestParam(value = "cid", required = false) Integer cid,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "pageNo", required = false) Integer pageNo,
|
||||
@RequestParam(value = "pageSize", required = false) Integer pageSize,
|
||||
@RequestParam(value = "sortField", required = false) String sortField,
|
||||
@RequestParam(value = "sortOrder", required = false) String sortOrder) {
|
||||
// 创建 ProductSearchPageDTO 对象
|
||||
ProductSearchPageDTO productSearchPageDTO = new ProductSearchPageDTO().setCid(cid).setKeyword(keyword)
|
||||
.setPageNo(pageNo).setPageSize(pageSize);
|
||||
if (StringUtil.hasText(sortField) && StringUtil.hasText(sortOrder)) {
|
||||
productSearchPageDTO.setSorts(Collections.singletonList(new SortingField(sortField, sortOrder)));
|
||||
}
|
||||
// 执行搜索
|
||||
return productSearchService.searchPage(productSearchPageDTO);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
spring:
|
||||
application:
|
||||
name: search-application
|
||||
|
||||
# server
|
||||
server:
|
||||
port: 18086
|
||||
servlet:
|
||||
context-path: /search-api/
|
@ -1,12 +1,13 @@
|
||||
package cn.iocoder.mall.search.api;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.search.api.bo.ESProductPageBO;
|
||||
import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
|
||||
|
||||
public interface ProductSearchService {
|
||||
|
||||
CommonResult<Integer> rebuild();
|
||||
|
||||
CommonResult searchPage(ProductSearchPageDTO searchPageDTO);
|
||||
CommonResult<ESProductPageBO> searchPage(ProductSearchPageDTO searchPageDTO);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package cn.iocoder.mall.search.api.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 ES BO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ESProductBO implements Serializable {
|
||||
|
||||
private Integer id;
|
||||
|
||||
// ========== 基本信息 =========
|
||||
/**
|
||||
* SPU 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 卖点
|
||||
*/
|
||||
private String sellPoint;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 分类编号
|
||||
*/
|
||||
private Integer cid;
|
||||
/**
|
||||
* 分类名
|
||||
*/
|
||||
private String categoryName;
|
||||
/**
|
||||
* 商品主图地数组
|
||||
*/
|
||||
private List<String> picUrls;
|
||||
|
||||
// ========== 其他信息 =========
|
||||
/**
|
||||
* 是否上架商品(是否可见)。
|
||||
*
|
||||
* true 为已上架
|
||||
* false 为已下架
|
||||
*/
|
||||
private Boolean visible;
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
// ========== Sku 相关字段 =========
|
||||
/**
|
||||
* 原价格,单位:分
|
||||
*/
|
||||
private Integer originalPrice;
|
||||
/**
|
||||
* 购买价格,单位:分。
|
||||
*/
|
||||
private Integer buyPrice;
|
||||
/**
|
||||
* 库存数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
// ========== 促销活动相关字段 =========
|
||||
// 目前只促销单体商品促销,目前仅限制折扣。
|
||||
/**
|
||||
* 促销活动编号
|
||||
*/
|
||||
private Integer promotionActivityId;
|
||||
/**
|
||||
* 促销活动标题
|
||||
*/
|
||||
private String promotionActivityTitle;
|
||||
/**
|
||||
* 促销活动类型
|
||||
*/
|
||||
private Integer promotionActivityType;
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cn.iocoder.mall.search.api.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ESProductPageBO implements Serializable {
|
||||
|
||||
/**
|
||||
* 管理员数组
|
||||
*/
|
||||
private List<ESProductBO> list;
|
||||
/**
|
||||
* 总量
|
||||
*/
|
||||
private Integer total;
|
||||
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package cn.iocoder.mall.search.api.dto;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtil;
|
||||
import cn.iocoder.common.framework.vo.SortingField;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 商品检索分页 DTO
|
||||
@ -12,6 +15,8 @@ import java.util.List;
|
||||
@Accessors(chain = true)
|
||||
public class ProductSearchPageDTO {
|
||||
|
||||
public static final Set<String> SORT_FIELDS = CollectionUtil.asSet("buyPrice");
|
||||
|
||||
/**
|
||||
* 分类编号
|
||||
*/
|
||||
@ -33,6 +38,6 @@ public class ProductSearchPageDTO {
|
||||
/**
|
||||
* 排序字段数组
|
||||
*/
|
||||
private List<SortFieldDTO> sorts;
|
||||
private List<SortingField> sorts;
|
||||
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package cn.iocoder.mall.search.api.dto;
|
||||
|
||||
/**
|
||||
* 排序字段 DTO
|
||||
*/
|
||||
public class SortFieldDTO {
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
private String field;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private String order;
|
||||
|
||||
}
|
@ -49,6 +49,18 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -0,0 +1,9 @@
|
||||
package cn.iocoder.mall.search.biz.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories(basePackages = "cn.iocoder.mall.search.biz.dao")
|
||||
public class JPAConfiguration {
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package cn.iocoder.mall.search.biz.convert;
|
||||
|
||||
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
|
||||
import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO;
|
||||
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
|
||||
import cn.iocoder.mall.search.api.bo.ESProductBO;
|
||||
import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductSearchConvert {
|
||||
|
||||
ProductSearchConvert INSTANCE = Mappers.getMapper(ProductSearchConvert.class);
|
||||
|
||||
@Mappings({})
|
||||
ESProductDO convert(ProductSpuDetailBO spu);
|
||||
|
||||
@Mappings({})
|
||||
default ESProductDO convert(ProductSpuDetailBO spu, CalcSkuPriceBO calcSkuPrice) {
|
||||
// Spu 的基础数据
|
||||
ESProductDO product = this.convert(spu);
|
||||
product.setOriginalPrice(calcSkuPrice.getOriginalPrice()).setBuyPrice(calcSkuPrice.getBuyPrice());
|
||||
// 设置促销活动相关字段
|
||||
if (calcSkuPrice.getTimeLimitedDiscount() != null) {
|
||||
PromotionActivityBO activity = calcSkuPrice.getTimeLimitedDiscount();
|
||||
product.setPromotionActivityId(activity.getId()).setPromotionActivityTitle(activity.getTitle())
|
||||
.setPromotionActivityType(activity.getActivityType());
|
||||
}
|
||||
// 返回
|
||||
return product;
|
||||
}
|
||||
|
||||
List<ESProductBO> convert(List<ESProductDO> list);
|
||||
|
||||
}
|
@ -1,13 +1,67 @@
|
||||
package cn.iocoder.mall.search.biz.dao;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtil;
|
||||
import cn.iocoder.common.framework.util.StringUtil;
|
||||
import cn.iocoder.common.framework.vo.SortingField;
|
||||
import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
|
||||
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
||||
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
|
||||
|
||||
@Repository
|
||||
public interface ProductRepository extends ElasticsearchRepository<ESProductDO, Integer> {
|
||||
|
||||
@Deprecated
|
||||
ESProductDO findByName(String name);
|
||||
|
||||
default Page<ESProductDO> search(Integer cid, String keyword, Integer pageNo, Integer pageSize,
|
||||
List<SortingField> sortFields) {
|
||||
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
|
||||
.withPageable(PageRequest.of(pageNo - 1, pageSize));
|
||||
// 筛选条件 cid
|
||||
if (cid != null) {
|
||||
nativeSearchQueryBuilder.withFilter(QueryBuilders.termQuery("cid", cid));
|
||||
}
|
||||
// 筛选
|
||||
if (StringUtil.hasText(keyword)) {
|
||||
FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = { // TODO 芋艿,分值随便打的
|
||||
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("name", keyword),
|
||||
ScoreFunctionBuilders.weightFactorFunction(10)),
|
||||
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("sellPoint", keyword),
|
||||
ScoreFunctionBuilders.weightFactorFunction(2)),
|
||||
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("categoryName", keyword),
|
||||
ScoreFunctionBuilders.weightFactorFunction(3)),
|
||||
// new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("description", keyword),
|
||||
// ScoreFunctionBuilders.weightFactorFunction(2)), // TODO 芋艿,目前这么做,如果商品描述很长,在按照价格降序,会命中超级多的关键字。
|
||||
};
|
||||
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(functions)
|
||||
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
|
||||
.setMinScore(2F); // TODO 芋艿,需要考虑下 score
|
||||
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
|
||||
} else {
|
||||
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
|
||||
}
|
||||
// 排序
|
||||
if (CollectionUtil.isEmpty(sortFields)) {
|
||||
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
|
||||
} else {
|
||||
sortFields.forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getField())
|
||||
.order(SortOrder.fromString(sortField.getOrder()))));
|
||||
}
|
||||
// 执行查询
|
||||
return search(nativeSearchQueryBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,35 +1,40 @@
|
||||
package cn.iocoder.mall.search.biz.service;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.SortingField;
|
||||
import cn.iocoder.mall.order.api.CartService;
|
||||
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
|
||||
import cn.iocoder.mall.product.api.ProductSpuService;
|
||||
import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO;
|
||||
import cn.iocoder.mall.search.api.ProductSearchService;
|
||||
import cn.iocoder.mall.search.api.bo.ESProductPageBO;
|
||||
import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
|
||||
import cn.iocoder.mall.search.biz.convert.ProductSearchConvert;
|
||||
import cn.iocoder.mall.search.biz.dao.ProductRepository;
|
||||
import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||
public class ProductSearchServiceImpl implements ProductSearchService {
|
||||
|
||||
private static final Integer REBUILD_FETCH_PER_SIZE = 2;
|
||||
private static final Integer REBUILD_FETCH_PER_SIZE = 100;
|
||||
|
||||
@Autowired
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Autowired
|
||||
@Reference(validation = "true")
|
||||
private ProductSpuService productSpuService;
|
||||
@Autowired
|
||||
@Reference(validation = "true")
|
||||
private CartService cartService;
|
||||
|
||||
@Override
|
||||
@ -39,17 +44,12 @@ public class ProductSearchServiceImpl implements ProductSearchService {
|
||||
int rebuildCounts = 0;
|
||||
while (true) {
|
||||
CommonResult<List<ProductSpuDetailBO>> result = productSpuService.getProductSpuDetailListForSync(lastId, REBUILD_FETCH_PER_SIZE);
|
||||
Assert.isTrue(result.isError(), "获得商品列表必然成功");
|
||||
Assert.isTrue(result.isSuccess(), "获得商品列表必然成功");
|
||||
List<ProductSpuDetailBO> spus = result.getData();
|
||||
rebuildCounts += spus.size();
|
||||
// 存储到 ES 中
|
||||
List<ESProductDO> products = spus.stream().map(new Function<ProductSpuDetailBO, ESProductDO>() {
|
||||
@Override
|
||||
public ESProductDO apply(ProductSpuDetailBO spu) {
|
||||
return convert(spu);
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<ESProductDO> products = spus.stream().map(this::convert).collect(Collectors.toList());
|
||||
productRepository.saveAll(products);
|
||||
// 设置新的 lastId ,或者结束
|
||||
if (spus.size() < REBUILD_FETCH_PER_SIZE) {
|
||||
break;
|
||||
@ -66,14 +66,30 @@ public class ProductSearchServiceImpl implements ProductSearchService {
|
||||
ProductSpuDetailBO.Sku sku = spu.getSkus().stream().min(Comparator.comparing(ProductSpuDetailBO.Sku::getPrice)).get();
|
||||
// 价格计算
|
||||
CommonResult<CalcSkuPriceBO> calSkuPriceResult = cartService.calcSkuPrice(sku.getId());
|
||||
Assert.isTrue(calSkuPriceResult.isError(), String.format("SKU(%d) 价格计算不会出错", sku.getId()));
|
||||
|
||||
return new ESProductDO();
|
||||
Assert.isTrue(calSkuPriceResult.isSuccess(), String.format("SKU(%d) 价格计算不会出错", sku.getId()));
|
||||
// 拼装结果
|
||||
return ProductSearchConvert.INSTANCE.convert(spu, calSkuPriceResult.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult searchPage(ProductSearchPageDTO searchPageDTO) {
|
||||
return null;
|
||||
public CommonResult<ESProductPageBO> searchPage(ProductSearchPageDTO searchPageDTO) {
|
||||
checkSortFieldInvalid(searchPageDTO.getSorts());
|
||||
// 执行查询
|
||||
Page<ESProductDO> searchPage = productRepository.search(searchPageDTO.getCid(), searchPageDTO.getKeyword(),
|
||||
searchPageDTO.getPageNo(), searchPageDTO.getPageSize(), searchPageDTO.getSorts());
|
||||
// 转换结果
|
||||
ESProductPageBO resultPage = new ESProductPageBO()
|
||||
.setList(ProductSearchConvert.INSTANCE.convert(searchPage.getContent()))
|
||||
.setTotal((int) searchPage.getTotalElements());
|
||||
return CommonResult.success(resultPage);
|
||||
}
|
||||
|
||||
private void checkSortFieldInvalid(List<SortingField> sorts) {
|
||||
if (CollectionUtil.isEmpty(sorts)) {
|
||||
return;
|
||||
}
|
||||
sorts.forEach(sortingField -> Assert.isTrue(ProductSearchPageDTO.SORT_FIELDS.contains(sortingField.getField()),
|
||||
String.format("排序字段(%s) 不在允许范围内", sortingField.getField())));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#
|
||||
# es
|
||||
spring:
|
||||
data:
|
||||
elasticsearch:
|
||||
@ -6,6 +6,7 @@ spring:
|
||||
cluster-nodes: 192.168.88.10:9300
|
||||
repositories:
|
||||
enable: true
|
||||
|
||||
# dubbo
|
||||
dubbo:
|
||||
application:
|
||||
@ -16,4 +17,4 @@ dubbo:
|
||||
port: -1
|
||||
name: dubbo
|
||||
scan:
|
||||
base-packages: cn.iocoder.mall.search.service.biz
|
||||
base-packages: cn.iocoder.mall.search.biz.service
|
@ -1,12 +1,15 @@
|
||||
package cn.iocoder.mall.search.biz.dao;
|
||||
|
||||
import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
public class ProductRepositoryTest {
|
||||
@ -15,6 +18,7 @@ public class ProductRepositoryTest {
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSave() {
|
||||
// productRepository.deleteById(1);
|
||||
ESProductDO product = new ESProductDO()
|
||||
@ -24,9 +28,23 @@ public class ProductRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testFindByName() {
|
||||
ESProductDO product = productRepository.findByName("锤子");
|
||||
System.out.println(product);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearch() {
|
||||
// Page<ESProductDO> page = productRepository.search(639, null, 1, 10);
|
||||
// console(page.getContent());
|
||||
|
||||
// Page<ESProductDO> page = productRepository.search(null, "数据库Oracle", 1, 10);
|
||||
// console(page.getContent());
|
||||
}
|
||||
|
||||
private void console(List<ESProductDO> list) {
|
||||
list.forEach(System.out::println);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package cn.iocoder.mall.search.biz.service;
|
||||
|
||||
import cn.iocoder.mall.search.biz.dao.ProductRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
public class ProductSearchServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private ProductSearchServiceImpl productSearchService;
|
||||
@Autowired
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Test
|
||||
public void testRebuild() {
|
||||
int counts = productSearchService.rebuild().getData();
|
||||
System.out.println("重建数量:" + counts);
|
||||
|
||||
System.out.println(productRepository.count());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user