Merge remote-tracking branch 'origin/master'

This commit is contained in:
sin 2019-04-26 00:23:38 +08:00
commit 1c5f413ca1
43 changed files with 698 additions and 273 deletions

View File

@ -1,5 +1,7 @@
package cn.iocoder.common.framework.util;
import org.springframework.util.Assert;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@ -116,4 +118,19 @@ public class DateUtil {
calendar.set(Calendar.MILLISECOND, milliSecond);
}
/**
* 判断当前时间是否在该时间范围内
*
* @param beginTime 开始时间
* @param endTime 结束时间
* @return 是否在
*/
public static boolean isBetween(Date beginTime, Date endTime) {
Assert.notNull(beginTime, "开始时间不能为空");
Assert.notNull(endTime, "结束时间不能为空");
Date now = new Date();
return beginTime.getTime() <= now.getTime()
&& now.getTime() <= endTime.getTime();
}
}

View File

@ -14,3 +14,13 @@ export function getProductPage({cid, keyword, pageNo, pageSize, sortField, sortO
}
});
}
export function getProductCondition({keyword}) {
return request({
url: '/search-api/users/product/condition',
method: 'get',
params: {
keyword,
}
});
}

View File

@ -9,16 +9,10 @@
>
<template slot="thumb">
<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.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>

View File

@ -8,14 +8,26 @@
<van-tab v-for="category in childCategories" :title="category.name" />
</van-tabs>
<!-- <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 { getProductCategoryList, getProductSpuPage } from '../../api/product';
import {getProductPage} from "../../api/search";
export default {
data() {
@ -28,8 +40,14 @@ export default {
id: parseInt(this.$route.query.cidSecond),
},
childCategories: [],
active: 2,
active: -1,
products: [],
page: 0,
pageSize: 10,
loading: false,
finished: false,
};
},
methods: {
@ -41,18 +59,40 @@ export default {
this.active = key;
//
this.products = [];
this.loadProductList(this.childCategories[key].id);
//
this.loadProductList(this.childCategories[key].id, 1);
},
loadProductList(categoryId) {
//
loadProductList(categoryId, page) {
this.childCategory.id = categoryId;
//
// alert('' + categoryId);
let response = getProductSpuPage(categoryId);
response.then(data => {
this.products.push(...data.spus);
})
getProductPage({
pageNo: page,
pageSize: this.pageSize,
cid: this.childCategory.id,
}).then(data => {
this.handleData(page, data);
});
},
onLoad() {
// debugger;
//
let page = this.page + 1;
//
this.loadProductList(this.childCategory.id, page);
},
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() {
let response = getProductCategoryList(this.rootCategory.id);
@ -70,7 +110,7 @@ export default {
}
}
//
this.loadProductList(this.childCategory.id);
// this.loadProductList(this.childCategory.id);
});
}
};

View File

@ -15,152 +15,160 @@
<li :class="filterIndex==12?'selected':''" v-on:click="onFilterBar(12)">价格最高</li>
</ul>
</div>
<van-popup v-model="filterShow" position="right" class="filterlayer" >
<div class="filterInner" style="overflow-y: scroll;max-height: 100%;">
<div :class="'item_options '+(filterShow?'show':'')">
<ul>
<li>
<van-cell title="清洁类型" is-link arrow-direction="down" />
<li v-for="category in categories" :class="category.id === categoryId ?'selected':''" v-on:click="onCategoryClick(category.id)">
{{ category.name }}
</li>
<div style="clear: both;"></div>
<div class="tags_selection">
<div class="option">
<a href="javascript:void 0;">牙龈护理111</a>
</div>
<div class="option ">
<a href="javascript:void 0;">抛光</a>
</div>
<div class="option ">
<a href="javascript:void 0;">清洁</a>
</div>
<div class="option ">
<a href="javascript:void 0;">正畸专用</a>
</div>
<div class="option ">
<a href="javascript:void 0;">敏感</a>
</div>
<div class="option ">
<a href="javascript:void 0;">亮白</a>
</div>
<div style="clear: both;"></div>
</div>
</ul>
<ul>
<li>
<van-cell title="清洁类型" is-link arrow-direction="down" />
</li>
<div style="clear: both;"></div>
<div class="tags_selection">
<div class="option">
<a href="javascript:void 0;">牙龈护理111</a>
</div>
<div class="option ">
<a href="javascript:void 0;">抛光</a>
</div>
<div class="option ">
<a href="javascript:void 0;">清洁</a>
</div>
<div class="option ">
<a href="javascript:void 0;">正畸专用</a>
</div>
<div class="option ">
<a href="javascript:void 0;">敏感</a>
</div>
<div class="option ">
<a href="javascript:void 0;">亮白</a>
</div>
<div style="clear: both;"></div>
</div>
</ul>
<ul>
<li>
<van-cell title="清洁类型" is-link arrow-direction="down" />
</li>
<div style="clear: both;"></div>
<div class="tags_selection">
<div class="option">
<a href="javascript:void 0;">牙龈护理111</a>
</div>
<div class="option ">
<a href="javascript:void 0;">抛光</a>
</div>
<div class="option ">
<a href="javascript:void 0;">清洁</a>
</div>
<div class="option ">
<a href="javascript:void 0;">正畸专用</a>
</div>
<div class="option ">
<a href="javascript:void 0;">敏感</a>
</div>
<div class="option ">
<a href="javascript:void 0;">亮白</a>
</div>
<div style="clear: both;"></div>
</div>
</ul>
<ul>
<li>
<van-cell title="清洁类型" is-link arrow-direction="down" />
</li>
<div style="clear: both;"></div>
<div class="tags_selection">
<div class="option">
<a href="javascript:void 0;">牙龈护理111</a>
</div>
<div class="option ">
<a href="javascript:void 0;">抛光</a>
</div>
<div class="option ">
<a href="javascript:void 0;">清洁</a>
</div>
<div class="option ">
<a href="javascript:void 0;">正畸专用</a>
</div>
<div class="option ">
<a href="javascript:void 0;">敏感</a>
</div>
<div class="option ">
<a href="javascript:void 0;">亮白</a>
</div>
<div style="clear: both;"></div>
</div>
</ul>
<ul>
<li>
<van-cell title="清洁类型" is-link arrow-direction="down" />
</li>
<div style="clear: both;"></div>
<div class="tags_selection">
<div class="option">
<a href="javascript:void 0;">牙龈护理111</a>
</div>
<div class="option ">
<a href="javascript:void 0;">抛光</a>
</div>
<div class="option ">
<a href="javascript:void 0;">清洁</a>
</div>
<div class="option ">
<a href="javascript:void 0;">正畸专用</a>
</div>
<div class="option ">
<a href="javascript:void 0;">敏感</a>
</div>
<div class="option ">
<a href="javascript:void 0;">亮白</a>
</div>
<div style="clear: both;"></div>
</div>
</ul>
<div style="clear: both;"></div>
<van-button size="large" style="height: 40px;margin-bottom: 15px;line-height: 40px;">清楚选项</van-button>
<div style="height:50px;"></div>
</div>
<div class="filterlayer_bottom_buttons">
<span class="filterlayer_bottom_button cancel">取消</span>
<span class="filterlayer_bottom_button confirm">确认</span>
</div>
</van-popup>
<!-- <van-popup v-model="filterShow" position="right" class="filterlayer" >-->
<!-- <div class="filterInner" style="overflow-y: scroll;max-height: 100%;">-->
<!-- <ul>-->
<!-- <li>-->
<!-- <van-cell title="清洁类型" is-link arrow-direction="down" />-->
<!-- </li>-->
<!-- <div style="clear: both;"></div>-->
<!-- <div class="tags_selection">-->
<!-- <div class="option">-->
<!-- <a href="javascript:void 0;">牙龈护理111</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">抛光</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">清洁</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">正畸专用</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">敏感</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">亮白</a>-->
<!-- </div>-->
<!-- <div style="clear: both;"></div>-->
<!-- </div>-->
<!-- </ul>-->
<!-- <ul>-->
<!-- <li>-->
<!-- <van-cell title="清洁类型" is-link arrow-direction="down" />-->
<!-- </li>-->
<!-- <div style="clear: both;"></div>-->
<!-- <div class="tags_selection">-->
<!-- <div class="option">-->
<!-- <a href="javascript:void 0;">牙龈护理111</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">抛光</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">清洁</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">正畸专用</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">敏感</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">亮白</a>-->
<!-- </div>-->
<!-- <div style="clear: both;"></div>-->
<!-- </div>-->
<!-- </ul>-->
<!-- <ul>-->
<!-- <li>-->
<!-- <van-cell title="清洁类型" is-link arrow-direction="down" />-->
<!-- </li>-->
<!-- <div style="clear: both;"></div>-->
<!-- <div class="tags_selection">-->
<!-- <div class="option">-->
<!-- <a href="javascript:void 0;">牙龈护理111</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">抛光</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">清洁</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">正畸专用</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">敏感</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">亮白</a>-->
<!-- </div>-->
<!-- <div style="clear: both;"></div>-->
<!-- </div>-->
<!-- </ul>-->
<!-- <ul>-->
<!-- <li>-->
<!-- <van-cell title="清洁类型" is-link arrow-direction="down" />-->
<!-- </li>-->
<!-- <div style="clear: both;"></div>-->
<!-- <div class="tags_selection">-->
<!-- <div class="option">-->
<!-- <a href="javascript:void 0;">牙龈护理111</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">抛光</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">清洁</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">正畸专用</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">敏感</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">亮白</a>-->
<!-- </div>-->
<!-- <div style="clear: both;"></div>-->
<!-- </div>-->
<!-- </ul>-->
<!-- <ul>-->
<!-- <li>-->
<!-- <van-cell title="清洁类型" is-link arrow-direction="down" />-->
<!-- </li>-->
<!-- <div style="clear: both;"></div>-->
<!-- <div class="tags_selection">-->
<!-- <div class="option">-->
<!-- <a href="javascript:void 0;">牙龈护理111</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">抛光</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">清洁</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">正畸专用</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">敏感</a>-->
<!-- </div>-->
<!-- <div class="option ">-->
<!-- <a href="javascript:void 0;">亮白</a>-->
<!-- </div>-->
<!-- <div style="clear: both;"></div>-->
<!-- </div>-->
<!-- </ul>-->
<!-- <div style="clear: both;"></div>-->
<!-- <van-button size="large" style="height: 40px;margin-bottom: 15px;line-height: 40px;">清楚选项</van-button>-->
<!-- <div style="height:50px;"></div>-->
<!-- </div>-->
<!-- <div class="filterlayer_bottom_buttons">-->
<!-- <span class="filterlayer_bottom_button cancel">取消</span>-->
<!-- <span class="filterlayer_bottom_button confirm">确认</span>-->
<!-- </div>-->
<!-- </van-popup>-->
</div>
<van-list
@ -179,7 +187,7 @@
<script>
import searchtop from "../../components/search/searchtop";
import {getProductPage} from "../../api/search";
import {getProductCondition, getProductPage} from "../../api/search";
export default {
components: {
@ -201,13 +209,16 @@ export default {
sortField: undefined,
sortOrder: undefined,
products:[]
products:[], //
categories: [], //
categoryId: undefined, //
};
},
methods: {
onFilterBar(value) {
if (value === 0) {
this.filterSort = !this.filterSort;
this.filterShow = false;
} else if (value === 3) {
this.filterShow = !this.filterShow;
} else {
@ -252,6 +263,25 @@ export default {
});
}
},
onCategoryClick(value) {
//
this.categoryId = value;
//
this.filterShow = false;
//
let page = 1;
getProductPage({
pageNo: page,
pageSize: this.pageSize,
keyword: this.keyword,
sortField: this.sortField,
sortOrder: this.sortOrder,
cid: this.categoryId,
}).then(data => {
this.products = [];
this.handleData(page, data);
});
},
showProduct(product){
this.$router.push('/product/'+product.id);
},
@ -265,6 +295,7 @@ export default {
this.filterShow = false;
this.sortField = undefined;
this.sortOrder = undefined;
this.categoryId = undefined;
//
let page = 1;
getProductPage({
@ -274,6 +305,7 @@ export default {
}).then(data => {
this.products = [];
this.handleData(page, data);
this.loadSearchCondition();
});
},
onLoad() {
@ -286,6 +318,7 @@ export default {
keyword: this.keyword,
}).then(data => {
this.handleData(page, data);
this.loadSearchCondition();
});
},
handleData(page, data) {
@ -300,6 +333,13 @@ export default {
}
//
this.loading = false;
},
loadSearchCondition() {
getProductCondition({
keyword: this.keyword,
}).then(data => {
this.categories = data.categories;
});
}
},
mounted() {

View File

@ -134,4 +134,5 @@ public class OrderController {
}
return commonResult;
}
}

View File

@ -18,6 +18,7 @@ import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.product.api.ProductSpuService;
import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO;
import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.user.api.UserAddressService;
import cn.iocoder.mall.user.api.bo.UserAddressBO;
import com.alibaba.dubbo.config.annotation.Reference;
@ -61,14 +62,17 @@ public class OrderServiceImpl implements OrderService {
@Autowired
private OrderReturnMapper orderReturnMapper;
@Reference
private ProductSpuService productSpuService;
@Autowired
private CartServiceImpl cartService;
@Reference
@Reference(validation = "true")
private ProductSpuService productSpuService;
@Reference(validation = "true")
private UserAddressService userAddressService;
@Reference(validation = "true")
private PayTransactionService payTransactionService;
@Reference(validation = "true")
private CouponService couponService;
@Override
public CommonResult<OrderPageBO> getOrderPage(OrderQueryDTO orderQueryDTO) {
@ -253,7 +257,12 @@ public class OrderServiceImpl implements OrderService {
.setPresentTotal(priceItem.getPresentTotal());
}
// TODO 芋艿标记优惠劵使用
// 标记优惠劵已使用
CommonResult<Boolean> useCouponCardResult = couponService.useCouponCard(userId, orderCreateDTO.getCouponCardId());
if (useCouponCardResult.isError()) {
return CommonResult.error(useCouponCardResult);
}
// TODO 芋艿扣除库存
// order

View File

@ -1,20 +1,18 @@
package cn.iocoder.mall.pay.biz.mq;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
package cn.iocoder.mall.pay.api.message;
/**
* {@link cn.iocoder.mall.pay.biz.constant.MQConstant#TOPIC_PAY_TRANSACTION_PAY_SUCCESS} 的消息对象
* 支付交易单支付成功的消息对象
*/
public class PayTransactionPaySuccessMessage {
public static final String TOPIC = "PAY_TRANSACTION_PAY_SUCCESS";
/**
* 编号自增
*/
private Integer id;
/**
* 交易编号
*
* {@link PayTransactionDO#getId()}
*/
private Integer transactionId;
/**

View File

@ -1,13 +0,0 @@
package cn.iocoder.mall.pay.biz.constant;
/**
* MQ 枚举类
*/
public class MQConstant {
/**
* Topic - 支付交易单支付成功
*/
public static final String TOPIC_PAY_TRANSACTION_PAY_SUCCESS = "PAY_TRANSACTION_PAY_SUCCESS";
}

View File

@ -6,7 +6,7 @@ import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
import cn.iocoder.mall.pay.biz.mq.PayTransactionPaySuccessMessage;
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

View File

@ -3,7 +3,7 @@ package cn.iocoder.mall.pay.biz.mq;
import cn.iocoder.common.framework.util.DateUtil;
import cn.iocoder.common.framework.util.ExceptionUtil;
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
import cn.iocoder.mall.pay.biz.constant.MQConstant;
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyLogMapper;
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
@ -31,8 +31,8 @@ import java.util.Date;
@Service
@RocketMQMessageListener(
topic = MQConstant.TOPIC_PAY_TRANSACTION_PAY_SUCCESS,
consumerGroup = "pay-consumer-group-" + MQConstant.TOPIC_PAY_TRANSACTION_PAY_SUCCESS
topic = PayTransactionPaySuccessMessage.TOPIC,
consumerGroup = "pay-consumer-group-" + PayTransactionPaySuccessMessage.TOPIC
)
public class PayTransactionPaySuccessConsumer implements RocketMQListener<PayTransactionPaySuccessMessage> {

View File

@ -1,6 +1,6 @@
package cn.iocoder.mall.pay.biz.scheduler;
import cn.iocoder.mall.pay.biz.constant.MQConstant;
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
@ -35,7 +35,7 @@ public class PayTransactionNotifyJob extends IJobHandler {
// 循环任务发送通知
for (PayTransactionNotifyTaskDO payTransactionNotifyTask : notifyTasks) {
// 发送 MQ
rocketMQTemplate.convertAndSend(MQConstant.TOPIC_PAY_TRANSACTION_PAY_SUCCESS,
rocketMQTemplate.convertAndSend(PayTransactionPaySuccessMessage.TOPIC,
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
// 更新最后通知时间
// 1. 这样操作虽然可能会出现 MQ 消费快于下面 PayTransactionNotifyTaskDO 的更新语句但是因为更新字段不同所以不会有问题

View File

@ -12,10 +12,10 @@ import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
import cn.iocoder.mall.pay.biz.client.TransactionPaySuccessBO;
import cn.iocoder.mall.pay.biz.constant.MQConstant;
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
import cn.iocoder.mall.pay.biz.dao.PayTransactionExtensionMapper;
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
@ -188,7 +188,7 @@ public class PayServiceImpl implements PayTransactionService {
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
logger.info("[updateTransactionPaySuccess][PayTransactionNotifyTaskDO({}) 新增一个任务]", payTransactionNotifyTask.getId());
// 3.2 发送 MQ
rocketMQTemplate.convertAndSend(MQConstant.TOPIC_PAY_TRANSACTION_PAY_SUCCESS,
rocketMQTemplate.convertAndSend(PayTransactionPaySuccessMessage.TOPIC,
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
logger.info("[updateTransactionPaySuccess][PayTransactionNotifyTaskDO({}) 发送 MQ 任务]", payTransactionNotifyTask.getId());
// 返回结果

View File

@ -42,6 +42,7 @@ public class UsersProductSpuController {
@ApiImplicitParam(name = "pageSize", value = "每页条数", required = true, example = "10"),
})
@PermitAll
@Deprecated // 使用商品搜索接口
public CommonResult<UsersProductSpuPageVO> page(@RequestParam(value = "cid", required = false) Integer cid,
@RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {

View File

@ -5,6 +5,7 @@ import cn.iocoder.mall.product.api.bo.ProductCategoryBO;
import cn.iocoder.mall.product.api.dto.ProductCategoryAddDTO;
import cn.iocoder.mall.product.api.dto.ProductCategoryUpdateDTO;
import java.util.Collection;
import java.util.List;
public interface ProductCategoryService {
@ -15,6 +16,14 @@ public interface ProductCategoryService {
*/
List<ProductCategoryBO> getListByPid(Integer pid);
/**
* 获得商品分类数组
*
* @param ids 商品分类编号
* @return 数组
*/
List<ProductCategoryBO> getListByIds(Collection<Integer> ids);
/**
* @return 返回所有产品分类们
*/

View File

@ -0,0 +1,20 @@
package cn.iocoder.mall.product.api.message;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 商品更新包括创建消息
*/
@Data
@Accessors(chain = true)
public class ProductUpdateMessage {
public static final String TOPIC = "ProductUpdate";
/**
* 商品编号
*/
private Integer id;
}

View File

@ -42,6 +42,11 @@
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -4,6 +4,7 @@ import cn.iocoder.mall.product.dataobject.ProductCategoryDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
@Repository
@ -16,6 +17,8 @@ public interface ProductCategoryMapper {
ProductCategoryDO selectById(@Param("id") Integer id);
List<ProductCategoryDO> selectByIds(@Param("ids") Collection<Integer> ids);
void insert(ProductCategoryDO productCategoryDO);
int update(ProductCategoryDO productCategoryDO);

View File

@ -16,6 +16,7 @@ import cn.iocoder.mall.product.dataobject.ProductCategoryDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@ -32,6 +33,12 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
return ProductCategoryConvert.INSTANCE.convertToBO(categoryList);
}
@Override
public List<ProductCategoryBO> getListByIds(Collection<Integer> ids) {
List<ProductCategoryDO> categoryList = productCategoryMapper.selectByIds(ids);
return ProductCategoryConvert.INSTANCE.convertToBO(categoryList);
}
@Override
public CommonResult<List<ProductCategoryBO>> getAll() {
List<ProductCategoryDO> categoryList = productCategoryMapper.selectList();

View File

@ -13,17 +13,20 @@ import cn.iocoder.mall.product.api.dto.ProductSkuAddOrUpdateDTO;
import cn.iocoder.mall.product.api.dto.ProductSpuAddDTO;
import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO;
import cn.iocoder.mall.product.api.dto.ProductSpuUpdateDTO;
import cn.iocoder.mall.product.api.message.ProductUpdateMessage;
import cn.iocoder.mall.product.convert.ProductSpuConvert;
import cn.iocoder.mall.product.dao.ProductSkuMapper;
import cn.iocoder.mall.product.dao.ProductSpuMapper;
import cn.iocoder.mall.product.dataobject.ProductCategoryDO;
import cn.iocoder.mall.product.dataobject.ProductSkuDO;
import cn.iocoder.mall.product.dataobject.ProductSpuDO;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
@ -41,6 +44,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
@Autowired
private ProductAttrServiceImpl productAttrService;
@Resource
private RocketMQTemplate rocketMQTemplate;
// @Override
// public ProductSpuBO getProductSpuDetail(Integer id) {
// ProductSpuDO productSpuDO = productSpuMapper.selectById(id);
@ -82,10 +88,20 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return CommonResult.success(spus);
}
@SuppressWarnings("Duplicates")
@Override
@Transactional
public CommonResult<ProductSpuDetailBO> addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO) {
CommonResult<ProductSpuDetailBO> result = addProductSpu0(adminId, productSpuAddDTO);
// 如果新增生成发送创建商品 Topic 消息
if (result.isSuccess()) {
// TODO 芋艿先不考虑事务的问题等后面的 fescar 一起搞
sendProductUpdateMessage(result.getData().getId());
}
return result;
}
@SuppressWarnings("Duplicates")
@Transactional
public CommonResult<ProductSpuDetailBO> addProductSpu0(Integer adminId, ProductSpuAddDTO productSpuAddDTO) {
// 校验商品分类分类存在
CommonResult<ProductCategoryDO> validCategoryResult = productCategoryService.validProductCategory(productSpuAddDTO.getCid());
if (validCategoryResult.isError()) {
@ -129,10 +145,19 @@ public class ProductSpuServiceImpl implements ProductSpuService {
validCategoryResult.getData()));
}
@SuppressWarnings("Duplicates")
@Override
@Transactional
public CommonResult<Boolean> updateProductSpu(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) {
CommonResult<Boolean> result = updateProductSpu0(adminId, productSpuUpdateDTO);
if (result.isSuccess()) {
// TODO 芋艿先不考虑事务的问题等后面的 fescar 一起搞
sendProductUpdateMessage(productSpuUpdateDTO.getId());
}
return result;
}
@SuppressWarnings("Duplicates")
@Transactional
public CommonResult<Boolean> updateProductSpu0(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) {
// 校验 Spu 是否存在
if (productSpuMapper.selectById(productSpuUpdateDTO.getId()) == null) {
return ServiceExceptionUtil.error(ProductErrorCodeEnum.PRODUCT_SPU_NOT_EXISTS.getCode());
@ -208,6 +233,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 更新排序
ProductSpuDO updateSpu = new ProductSpuDO().setId(spuId).setSort(sort);
productSpuMapper.update(updateSpu);
// 修改成功发送商品 Topic 消息
sendProductUpdateMessage(spuId);
// 返回成功
return CommonResult.success(true);
}
@ -329,4 +356,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
spu.setQuantity(skus.stream().mapToInt(ProductSkuAddOrUpdateDTO::getQuantity).sum()); // 求库存之和
}
private void sendProductUpdateMessage(Integer id) {
rocketMQTemplate.convertAndSend(ProductUpdateMessage.TOPIC, new ProductUpdateMessage().setId(id));
}
}

View File

@ -23,3 +23,10 @@ dubbo:
name: dubbo
scan:
base-packages: cn.iocoder.mall.product.service
# rocketmq
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: product-producer-group

View File

@ -32,6 +32,17 @@
AND deleted = 0
</select>
<select id="selectByIds" resultType="ProductCategoryDO">
SELECT
<include refid="FIELDS" />
FROM product_category
WHERE id IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
AND deleted = 0
</select>
<insert id="insert" parameterType="ProductCategoryDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO product_category (
pid, name, description, pic_url, sort,

View File

@ -46,11 +46,6 @@ public class UsersCouponCardVO {
// ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ==========
/**
* 是否使用
*/
@ApiModelProperty(value = "是否使用", required = true)
private Boolean used;
// TODO 芋艿后续要加优惠劵的使用日志因为下单后可能会取消

View File

@ -77,11 +77,9 @@ public interface CouponService {
*
* @param userId 用户编号
* @param couponCardId 优惠劵编号
* @param usedOrderId 下单的编号
* @param usedPrice 下单的价格
* @return 是否成功
*/
CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId, Integer usedOrderId, Integer usedPrice);
CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId);
/**
* 取消优惠劵的使用

View File

@ -95,18 +95,6 @@ public class CouponCardBO implements Serializable {
// ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ==========
/**
* 是否使用
*/
private Boolean used;
/**
* 使用订单号
*/
private Integer usedOrderId;
/**
* 订单中优惠面值单位
*/
private Integer usedPrice;
/**
* 使用时间
*/

View File

@ -29,6 +29,8 @@ public enum PromotionErrorCodeEnum {
COUPON_CARD_NOT_EXISTS(1006003000, "优惠劵不存在"),
COUPON_CARD_ERROR_USER(1006003001, "优惠劵不属于当前用户"),
COUPON_CARD_NOT_MATCH(1006003002, "优惠劵不匹配,无法使用"),
COUPON_CARD_STATUS_NOT_UNUSED(1006003003, "优惠劵不处于待使用状态"),
COUPON_CARD_STATUS_NOT_USED(1006003004, "优惠劵不处于已使用状态"),
;

View File

@ -29,4 +29,8 @@ public interface CouponCardMapper {
int update(CouponCardDO couponCardDO);
int updateByIdAndStatus(@Param("id") Integer id,
@Param("status") Integer status,
@Param("updateObj") CouponCardDO updateObj);
}

View File

@ -99,14 +99,14 @@ public class CouponCardDO extends BaseDO {
// ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ==========
/**
* 使用订单号
*/
private Integer usedOrderId;
/**
* 订单中优惠面值单位
*/
private Integer usedPrice;
// /**
// * 使用订单号
// */
// private Integer usedOrderId; // TODO 芋艿暂时不考虑这个字段
// /**
// * 订单中优惠面值单位
// */
// private Integer usedPrice; // TODO 芋艿暂时不考虑这个字段
/**
* 使用时间
*/

View File

@ -241,13 +241,51 @@ public class CouponServiceImpl implements CouponService {
}
@Override
public CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId, Integer usedOrderId, Integer usedPrice) {
return null;
public CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId) {
// 查询优惠劵
CouponCardDO card = couponCardMapper.selectById(couponCardId);
if (card == null) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_NOT_EXISTS.getCode());
}
if (!userId.equals(card.getUserId())) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_ERROR_USER.getCode());
}
if (CouponCardStatusEnum.UNUSED.getValue().equals(card.getStatus())) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
}
if (DateUtil.isBetween(card.getValidStartTime(), card.getValidEndTime())) { // 为避免定时器没跑实际优惠劵已经过期
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
}
// 更新优惠劵已使用
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.USED.getValue(),
new CouponCardDO().setStatus(CouponCardStatusEnum.USED.getValue()).setUsedTime(new Date()));
if (updateCount == 0) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
}
return CommonResult.success(true);
}
@Override
public CommonResult<Boolean> cancelUseCouponCard(Integer userId, Integer couponCardId) {
return null;
// 查询优惠劵
CouponCardDO card = couponCardMapper.selectById(couponCardId);
if (card == null) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_NOT_EXISTS.getCode());
}
if (!userId.equals(card.getUserId())) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_ERROR_USER.getCode());
}
if (CouponCardStatusEnum.USED.getValue().equals(card.getStatus())) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_USED.getCode());
}
// 更新优惠劵已使用
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.UNUSED.getValue(),
new CouponCardDO().setStatus(CouponCardStatusEnum.USED.getValue())); // TODO 芋艿usedTime 未设置空后面处理
if (updateCount == 0) {
return ServiceExceptionUtil.error(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_USED.getCode());
}
// 有一点要注意更新会未使用时优惠劵可能已经过期了直接让定时器跑过期这里不做处理
return CommonResult.success(true);
}
@Override

View File

@ -5,7 +5,7 @@
<sql id="FIELDS">
id, template_id, title, status, user_id, take_type,
price_available, valid_start_time, valid_end_time, preferential_type, percent_off, price_off,
discount_price_limit, used_order_id, used_price, used_time,
discount_price_limit, used_time,
create_time
</sql>
@ -94,12 +94,12 @@
INSERT INTO coupon_card (
template_id, title, status, user_id, take_type,
price_available, valid_start_time, valid_end_time, preferential_type, percent_off, price_off,
discount_price_limit, used_order_id, used_price, used_time,
discount_price_limit, used_time,
create_time
) VALUES (
#{templateId}, #{title}, #{status}, #{userId}, #{takeType},
#{priceAvailable}, #{validStartTime}, #{validEndTime}, #{preferentialType}, #{percentOff}, #{priceOff},
#{discountPriceLimit}, #{usedOrderId}, #{usedPrice}, #{usedTime},
#{discountPriceLimit}, #{usedTime},
#{createTime}
)
</insert>
@ -110,12 +110,6 @@
<if test="status != null">
status = #{status},
</if>
<if test="usedOrderId != null">
used_order_id = #{usedOrderId},
</if>
<if test="usedPrice != null">
used_price = #{usedPrice},
</if>
<if test="usedTime != null">
used_time = #{usedTime},
</if>
@ -123,4 +117,18 @@
WHERE id = #{id}
</update>
<update id="updateByIdAndStatus">
UPDATE coupon_card
<set>
<if test="updateObj.status != null">
status = #{updateObj.status},
</if>
<if test="updateObj.usedTime != null">
used_time = #{updateObj.usedTime},
</if>
</set>
WHERE id = #{id}
AND status = #{status}
</update>
</mapper>

View File

@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
public class SearchApplication {
public static void main(String[] args) {
// 解决 ES java.lang.IllegalStateException: availableProcessors is already
System.setProperty("es.set.netty.runtime.available.processors", "false");
SpringApplication.run(SearchApplication.class, args);
}

View File

@ -4,7 +4,9 @@ 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.bo.ProductConditionBO;
import cn.iocoder.mall.search.api.bo.ProductPageBO;
import cn.iocoder.mall.search.api.dto.ProductConditionDTO;
import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api;
@ -24,7 +26,7 @@ public class UsersProductSearchController {
private ProductSearchService productSearchService;
@GetMapping("/page") // TODO 芋艿后面把 BO 改成 VO
public CommonResult<ESProductPageBO> page(@RequestParam(value = "cid", required = false) Integer cid,
public CommonResult<ProductPageBO> 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,
@ -37,7 +39,16 @@ public class UsersProductSearchController {
productSearchPageDTO.setSorts(Collections.singletonList(new SortingField(sortField, sortOrder)));
}
// 执行搜索
return productSearchService.searchPage(productSearchPageDTO);
return productSearchService.getSearchPage(productSearchPageDTO);
}
@GetMapping("/condition") // TODO 芋艿后面把 BO 改成 VO
public CommonResult<ProductConditionBO> condition(@RequestParam(value = "keyword", required = false) String keyword) {
// 创建 ProductConditionDTO 对象
ProductConditionDTO productConditionDTO = new ProductConditionDTO().setKeyword(keyword)
.setFields(Collections.singleton(ProductConditionDTO.FIELD_CATEGORY));
// 执行搜索
return productSearchService.getSearchCondition(productConditionDTO);
}
}

View File

@ -1,13 +1,25 @@
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.bo.ProductConditionBO;
import cn.iocoder.mall.search.api.bo.ProductPageBO;
import cn.iocoder.mall.search.api.dto.ProductConditionDTO;
import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
public interface ProductSearchService {
CommonResult<Integer> rebuild();
CommonResult<ESProductPageBO> searchPage(ProductSearchPageDTO searchPageDTO);
/**
* 构建商品的搜索索引
*
* @param id 商品编号
* @return 构建结果
*/
CommonResult<Boolean> save(Integer id);
CommonResult<ProductPageBO> getSearchPage(ProductSearchPageDTO searchPageDTO);
CommonResult<ProductConditionBO> getSearchCondition(ProductConditionDTO conditionDTO);
}

View File

@ -11,7 +11,7 @@ import java.util.List;
*/
@Data
@Accessors(chain = true)
public class ESProductBO implements Serializable {
public class ProductBO implements Serializable {
private Integer id;

View File

@ -0,0 +1,35 @@
package cn.iocoder.mall.search.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 商品搜索条件返回 BO
*/
@Data
@Accessors(chain = true)
public class ProductConditionBO {
/**
* 商品分类数组
*/
private List<Category> categories;
@Data
@Accessors(chain = true)
public static class Category {
/**
* 分类编号
*/
private Integer id;
/**
* 分类名称
*/
private String name;
}
}

View File

@ -8,12 +8,12 @@ import java.util.List;
@Data
@Accessors(chain = true)
public class ESProductPageBO implements Serializable {
public class ProductPageBO implements Serializable {
/**
* 管理员数组
*/
private List<ESProductBO> list;
private List<ProductBO> list;
/**
* 总量
*/

View File

@ -0,0 +1,29 @@
package cn.iocoder.mall.search.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Collection;
/**
* 获得商品检索条件 DTO
*/
@Data
@Accessors(chain = true)
public class ProductConditionDTO {
/**
* Field - 商品分类
*/
public static final String FIELD_CATEGORY = "category";
/**
* 关键字
*/
private String keyword;
/**
* 需要返回的搜索条件的 fields
*/
private Collection<String> fields;
}

View File

@ -43,6 +43,11 @@
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -3,7 +3,7 @@ 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.api.bo.ProductBO;
import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
@ -34,6 +34,6 @@ public interface ProductSearchConvert {
return product;
}
List<ESProductBO> convert(List<ESProductDO> list);
List<ProductBO> convert(List<ESProductDO> list);
}

View File

@ -54,11 +54,13 @@ public interface ProductRepository extends ElasticsearchRepository<ESProductDO,
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
}
// 排序
if (CollectionUtil.isEmpty(sortFields)) {
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
} else {
if (!CollectionUtil.isEmpty(sortFields)) {
sortFields.forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getField())
.order(SortOrder.fromString(sortField.getOrder()))));
} else if (StringUtil.hasText(keyword)) {
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
} else {
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sort").order(SortOrder.DESC));
}
// 执行查询
return search(nativeSearchQueryBuilder.build());

View File

@ -0,0 +1,28 @@
package cn.iocoder.mall.search.biz.mq;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.product.api.message.ProductUpdateMessage;
import cn.iocoder.mall.search.api.ProductSearchService;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
@Service
@RocketMQMessageListener(
topic = ProductUpdateMessage.TOPIC,
consumerGroup = "search-consumer-group-" + ProductUpdateMessage.TOPIC
)
public class PayTransactionPaySuccessConsumer implements RocketMQListener<ProductUpdateMessage> {
@Autowired
private ProductSearchService productSearchService;
@Override
public void onMessage(ProductUpdateMessage message) {
CommonResult<Boolean> result = productSearchService.save(message.getId());
Assert.isTrue(result.isSuccess(), String.format("重构商品 ES 索引,必然成功。实际结果是 %s", result));
}
}

View File

@ -1,26 +1,39 @@
package cn.iocoder.mall.search.biz.service;
import cn.iocoder.common.framework.util.CollectionUtil;
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.order.api.CartService;
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.product.api.ProductCategoryService;
import cn.iocoder.mall.product.api.ProductSpuService;
import cn.iocoder.mall.product.api.bo.ProductCategoryBO;
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.bo.ProductConditionBO;
import cn.iocoder.mall.search.api.bo.ProductPageBO;
import cn.iocoder.mall.search.api.dto.ProductConditionDTO;
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.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@ -31,10 +44,14 @@ public class ProductSearchServiceImpl implements ProductSearchService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate; // 因为需要使用到聚合操作只好引入 ElasticsearchTemplate
@Reference(validation = "true")
private ProductSpuService productSpuService;
@Reference(validation = "true")
private ProductCategoryService productCategoryService;
@Reference(validation = "true")
private CartService cartService;
@Override
@ -57,9 +74,22 @@ public class ProductSearchServiceImpl implements ProductSearchService {
lastId = spus.get(spus.size() - 1).getId();
}
}
// 返回成功
return CommonResult.success(rebuildCounts);
}
@Override
public CommonResult<Boolean> save(Integer id) {
// 获得商品性情
CommonResult<ProductSpuDetailBO> result = productSpuService.getProductSpuDetail(id);
Assert.isTrue(result.isSuccess(), "获得商品详情必然成功");
// 存储到 ES
ESProductDO product = convert(result.getData());
productRepository.save(product);
// 返回成功
return CommonResult.success(Boolean.TRUE);
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
private ESProductDO convert(ProductSpuDetailBO spu) {
// 获得最小价格的 SKU 用于下面的价格计算
@ -72,13 +102,13 @@ public class ProductSearchServiceImpl implements ProductSearchService {
}
@Override
public CommonResult<ESProductPageBO> searchPage(ProductSearchPageDTO searchPageDTO) {
public CommonResult<ProductPageBO> getSearchPage(ProductSearchPageDTO searchPageDTO) {
checkSortFieldInvalid(searchPageDTO.getSorts());
// 执行查询
Page<ESProductDO> searchPage = productRepository.search(searchPageDTO.getCid(), searchPageDTO.getKeyword(),
searchPageDTO.getPageNo(), searchPageDTO.getPageSize(), searchPageDTO.getSorts());
// 转换结果
ESProductPageBO resultPage = new ESProductPageBO()
ProductPageBO resultPage = new ProductPageBO()
.setList(ProductSearchConvert.INSTANCE.convert(searchPage.getContent()))
.setTotal((int) searchPage.getTotalElements());
return CommonResult.success(resultPage);
@ -92,4 +122,46 @@ public class ProductSearchServiceImpl implements ProductSearchService {
String.format("排序字段(%s) 不在允许范围内", sortingField.getField())));
}
@Override
public CommonResult<ProductConditionBO> getSearchCondition(ProductConditionDTO conditionDTO) {
// 创建 ES 搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 筛选
if (StringUtil.hasText(conditionDTO.getKeyword())) { // 如果有 keyword 就去匹配
nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(conditionDTO.getKeyword(),
"name", "sellPoint", "categoryName"));
} else {
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
}
// 聚合
if (conditionDTO.getFields().contains(ProductConditionDTO.FIELD_CATEGORY)) { // 商品分类
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("cids").field("cid"));
}
// 执行查询
ProductConditionBO condition = elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), response -> {
ProductConditionBO result = new ProductConditionBO();
// categoryIds 聚合
Aggregation categoryIdsAggregation = response.getAggregations().get("cids");
if (categoryIdsAggregation != null) {
result.setCategories(new ArrayList<>());
for (LongTerms.Bucket bucket : (((LongTerms) categoryIdsAggregation).getBuckets())) {
result.getCategories().add(new ProductConditionBO.Category().setId(bucket.getKeyAsNumber().intValue()));
}
}
// 返回结果
return result;
});
// 聚合其它数据源
if (!CollectionUtil.isEmpty(condition.getCategories())) {
// 查询指定的 ProductCategoryBO 数组并转换成 ProductCategoryBO Map
Map<Integer, ProductCategoryBO> categoryMap = productCategoryService.getListByIds(
condition.getCategories().stream().map(ProductConditionBO.Category::getId).collect(Collectors.toList()))
.stream().collect(Collectors.toMap(ProductCategoryBO::getId, category -> category));
// 设置分类名
condition.getCategories().forEach(category -> category.setName(categoryMap.get(category.getId()).getName()));
}
// 返回结果
return CommonResult.success(condition);
}
}

View File

@ -3,7 +3,7 @@ spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 192.168.88.10:9300
cluster-nodes: 180.167.213.26:9300
repositories:
enable: true
@ -18,3 +18,9 @@ dubbo:
name: dubbo
scan:
base-packages: cn.iocoder.mall.search.biz.service
# rocketmq
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: search-producer-group