后端:增加商品修改时,发送 MQ 消息

后端:增加搜索服务,监听 MQ 消息,建立商品索引
This commit is contained in:
YunaiV 2019-04-25 20:12:01 +08:00
parent cddffabeba
commit f529985c40
41 changed files with 642 additions and 251 deletions

View File

@ -1,5 +1,7 @@
package cn.iocoder.common.framework.util; package cn.iocoder.common.framework.util;
import org.springframework.util.Assert;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -116,4 +118,19 @@ public class DateUtil {
calendar.set(Calendar.MILLISECOND, milliSecond); 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

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

View File

@ -129,4 +129,5 @@ public class UsersOrderController {
orderInfoBO.setStatusText(dictResult.getData().getDisplayName()); orderInfoBO.setStatusText(dictResult.getData().getDisplayName());
return commonResult; return commonResult;
} }
} }

View File

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

View File

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

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.PayTransactionDO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO; import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO; 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.Mapper;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -28,4 +28,4 @@ public interface PayTransactionConvert {
@Mappings({}) @Mappings({})
PayTransactionPaySuccessMessage convert(PayTransactionNotifyTaskDO payTransactionNotifyTaskDO); PayTransactionPaySuccessMessage convert(PayTransactionNotifyTaskDO payTransactionNotifyTaskDO);
} }

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

View File

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

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

View File

@ -42,6 +42,7 @@ public class UsersProductSpuController {
@ApiImplicitParam(name = "pageSize", value = "每页条数", required = true, example = "10"), @ApiImplicitParam(name = "pageSize", value = "每页条数", required = true, example = "10"),
}) })
@PermitAll @PermitAll
@Deprecated // 使用商品搜索接口
public CommonResult<UsersProductSpuPageVO> page(@RequestParam(value = "cid", required = false) Integer cid, public CommonResult<UsersProductSpuPageVO> page(@RequestParam(value = "cid", required = false) Integer cid,
@RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo, @RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) { @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.ProductCategoryAddDTO;
import cn.iocoder.mall.product.api.dto.ProductCategoryUpdateDTO; import cn.iocoder.mall.product.api.dto.ProductCategoryUpdateDTO;
import java.util.Collection;
import java.util.List; import java.util.List;
public interface ProductCategoryService { public interface ProductCategoryService {
@ -15,6 +16,14 @@ public interface ProductCategoryService {
*/ */
List<ProductCategoryBO> getListByPid(Integer pid); List<ProductCategoryBO> getListByPid(Integer pid);
/**
* 获得商品分类数组
*
* @param ids 商品分类编号
* @return 数组
*/
List<ProductCategoryBO> getListByIds(Collection<Integer> ids);
/** /**
* @return 返回所有产品分类们 * @return 返回所有产品分类们
*/ */
@ -28,4 +37,4 @@ public interface ProductCategoryService {
CommonResult<Boolean> deleteProductCategory(Integer admin, Integer productCategoryId); CommonResult<Boolean> deleteProductCategory(Integer admin, Integer productCategoryId);
} }

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> <artifactId>guava</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -4,6 +4,7 @@ import cn.iocoder.mall.product.dataobject.ProductCategoryDO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List; import java.util.List;
@Repository @Repository
@ -16,8 +17,10 @@ public interface ProductCategoryMapper {
ProductCategoryDO selectById(@Param("id") Integer id); ProductCategoryDO selectById(@Param("id") Integer id);
List<ProductCategoryDO> selectByIds(@Param("ids") Collection<Integer> ids);
void insert(ProductCategoryDO productCategoryDO); void insert(ProductCategoryDO productCategoryDO);
int update(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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -32,6 +33,12 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
return ProductCategoryConvert.INSTANCE.convertToBO(categoryList); 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 @Override
public CommonResult<List<ProductCategoryBO>> getAll() { public CommonResult<List<ProductCategoryBO>> getAll() {
List<ProductCategoryDO> categoryList = productCategoryMapper.selectList(); 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.ProductSpuAddDTO;
import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO; import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO;
import cn.iocoder.mall.product.api.dto.ProductSpuUpdateDTO; 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.convert.ProductSpuConvert;
import cn.iocoder.mall.product.dao.ProductSkuMapper; import cn.iocoder.mall.product.dao.ProductSkuMapper;
import cn.iocoder.mall.product.dao.ProductSpuMapper; import cn.iocoder.mall.product.dao.ProductSpuMapper;
import cn.iocoder.mall.product.dataobject.ProductCategoryDO; import cn.iocoder.mall.product.dataobject.ProductCategoryDO;
import cn.iocoder.mall.product.dataobject.ProductSkuDO; import cn.iocoder.mall.product.dataobject.ProductSkuDO;
import cn.iocoder.mall.product.dataobject.ProductSpuDO; import cn.iocoder.mall.product.dataobject.ProductSpuDO;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -41,6 +44,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
@Autowired @Autowired
private ProductAttrServiceImpl productAttrService; private ProductAttrServiceImpl productAttrService;
@Resource
private RocketMQTemplate rocketMQTemplate;
// @Override // @Override
// public ProductSpuBO getProductSpuDetail(Integer id) { // public ProductSpuBO getProductSpuDetail(Integer id) {
// ProductSpuDO productSpuDO = productSpuMapper.selectById(id); // ProductSpuDO productSpuDO = productSpuMapper.selectById(id);
@ -82,10 +88,20 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return CommonResult.success(spus); return CommonResult.success(spus);
} }
@SuppressWarnings("Duplicates")
@Override @Override
@Transactional
public CommonResult<ProductSpuDetailBO> addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO) { 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()); CommonResult<ProductCategoryDO> validCategoryResult = productCategoryService.validProductCategory(productSpuAddDTO.getCid());
if (validCategoryResult.isError()) { if (validCategoryResult.isError()) {
@ -129,10 +145,19 @@ public class ProductSpuServiceImpl implements ProductSpuService {
validCategoryResult.getData())); validCategoryResult.getData()));
} }
@SuppressWarnings("Duplicates")
@Override @Override
@Transactional
public CommonResult<Boolean> updateProductSpu(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) { 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 是否存在 // 校验 Spu 是否存在
if (productSpuMapper.selectById(productSpuUpdateDTO.getId()) == null) { if (productSpuMapper.selectById(productSpuUpdateDTO.getId()) == null) {
return ServiceExceptionUtil.error(ProductErrorCodeEnum.PRODUCT_SPU_NOT_EXISTS.getCode()); 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); ProductSpuDO updateSpu = new ProductSpuDO().setId(spuId).setSort(sort);
productSpuMapper.update(updateSpu); productSpuMapper.update(updateSpu);
// 修改成功发送商品 Topic 消息
sendProductUpdateMessage(spuId);
// 返回成功 // 返回成功
return CommonResult.success(true); return CommonResult.success(true);
} }
@ -329,4 +356,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
spu.setQuantity(skus.stream().mapToInt(ProductSkuAddOrUpdateDTO::getQuantity).sum()); // 求库存之和 spu.setQuantity(skus.stream().mapToInt(ProductSkuAddOrUpdateDTO::getQuantity).sum()); // 求库存之和
} }
private void sendProductUpdateMessage(Integer id) {
rocketMQTemplate.convertAndSend(ProductUpdateMessage.TOPIC, new ProductUpdateMessage().setId(id));
}
} }

View File

@ -22,4 +22,11 @@ dubbo:
port: -1 port: -1
name: dubbo name: dubbo
scan: scan:
base-packages: cn.iocoder.mall.product.service 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 AND deleted = 0
</select> </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 id="insert" parameterType="ProductCategoryDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO product_category ( INSERT INTO product_category (
pid, name, description, pic_url, sort, pid, name, description, pic_url, sort,
@ -70,4 +81,4 @@
WHERE id = #{id} WHERE id = #{id}
</update> </update>
</mapper> </mapper>

View File

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

View File

@ -77,11 +77,9 @@ public interface CouponService {
* *
* @param userId 用户编号 * @param userId 用户编号
* @param couponCardId 优惠劵编号 * @param couponCardId 优惠劵编号
* @param usedOrderId 下单的编号
* @param usedPrice 下单的价格
* @return 是否成功 * @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 ========== // ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ========== // ========== 使用情况 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_NOT_EXISTS(1006003000, "优惠劵不存在"),
COUPON_CARD_ERROR_USER(1006003001, "优惠劵不属于当前用户"), COUPON_CARD_ERROR_USER(1006003001, "优惠劵不属于当前用户"),
COUPON_CARD_NOT_MATCH(1006003002, "优惠劵不匹配,无法使用"), 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 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 ========== // ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ========== // ========== 使用情况 BEGIN ==========
/** // /**
* 使用订单号 // * 使用订单号
*/ // */
private Integer usedOrderId; // private Integer usedOrderId; // TODO 芋艿暂时不考虑这个字段
/** // /**
* 订单中优惠面值单位 // * 订单中优惠面值单位
*/ // */
private Integer usedPrice; // private Integer usedPrice; // TODO 芋艿暂时不考虑这个字段
/** /**
* 使用时间 * 使用时间
*/ */

View File

@ -241,13 +241,51 @@ public class CouponServiceImpl implements CouponService {
} }
@Override @Override
public CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId, Integer usedOrderId, Integer usedPrice) { public CommonResult<Boolean> useCouponCard(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.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 @Override
public CommonResult<Boolean> cancelUseCouponCard(Integer userId, Integer couponCardId) { 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 @Override

View File

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

View File

@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
public class SearchApplication { public class SearchApplication {
public static void main(String[] args) { 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); 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.CommonResult;
import cn.iocoder.common.framework.vo.SortingField; import cn.iocoder.common.framework.vo.SortingField;
import cn.iocoder.mall.search.api.ProductSearchService; 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.api.dto.ProductSearchPageDTO;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -24,12 +26,12 @@ public class UsersProductSearchController {
private ProductSearchService productSearchService; private ProductSearchService productSearchService;
@GetMapping("/page") // TODO 芋艿后面把 BO 改成 VO @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 = "keyword", required = false) String keyword,
@RequestParam(value = "pageNo", required = false) Integer pageNo, @RequestParam(value = "pageNo", required = false) Integer pageNo,
@RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "pageSize", required = false) Integer pageSize,
@RequestParam(value = "sortField", required = false) String sortField, @RequestParam(value = "sortField", required = false) String sortField,
@RequestParam(value = "sortOrder", required = false) String sortOrder) { @RequestParam(value = "sortOrder", required = false) String sortOrder) {
// 创建 ProductSearchPageDTO 对象 // 创建 ProductSearchPageDTO 对象
ProductSearchPageDTO productSearchPageDTO = new ProductSearchPageDTO().setCid(cid).setKeyword(keyword) ProductSearchPageDTO productSearchPageDTO = new ProductSearchPageDTO().setCid(cid).setKeyword(keyword)
.setPageNo(pageNo).setPageSize(pageSize); .setPageNo(pageNo).setPageSize(pageSize);
@ -37,7 +39,16 @@ public class UsersProductSearchController {
productSearchPageDTO.setSorts(Collections.singletonList(new SortingField(sortField, sortOrder))); 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; package cn.iocoder.mall.search.api;
import cn.iocoder.common.framework.vo.CommonResult; 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; import cn.iocoder.mall.search.api.dto.ProductSearchPageDTO;
public interface ProductSearchService { public interface ProductSearchService {
CommonResult<Integer> rebuild(); 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 @Data
@Accessors(chain = true) @Accessors(chain = true)
public class ESProductBO implements Serializable { public class ProductBO implements Serializable {
private Integer id; 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 @Data
@Accessors(chain = true) @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> <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<!-- test --> <!-- test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <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.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO; 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 cn.iocoder.mall.search.biz.dataobject.ESProductDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
@ -34,6 +34,6 @@ public interface ProductSearchConvert {
return product; 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()); nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
} }
// 排序 // 排序
if (CollectionUtil.isEmpty(sortFields)) { if (!CollectionUtil.isEmpty(sortFields)) {
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
} else {
sortFields.forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getField()) sortFields.forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getField())
.order(SortOrder.fromString(sortField.getOrder())))); .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()); 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; package cn.iocoder.mall.search.biz.service;
import cn.iocoder.common.framework.util.CollectionUtil; 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.CommonResult;
import cn.iocoder.common.framework.vo.SortingField; import cn.iocoder.common.framework.vo.SortingField;
import cn.iocoder.mall.order.api.CartService; import cn.iocoder.mall.order.api.CartService;
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO; 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.ProductSpuService;
import cn.iocoder.mall.product.api.bo.ProductCategoryBO;
import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO;
import cn.iocoder.mall.search.api.ProductSearchService; 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.api.dto.ProductSearchPageDTO;
import cn.iocoder.mall.search.biz.convert.ProductSearchConvert; import cn.iocoder.mall.search.biz.convert.ProductSearchConvert;
import cn.iocoder.mall.search.biz.dao.ProductRepository; import cn.iocoder.mall.search.biz.dao.ProductRepository;
import cn.iocoder.mall.search.biz.dataobject.ESProductDO; import cn.iocoder.mall.search.biz.dataobject.ESProductDO;
import com.alibaba.dubbo.config.annotation.Reference; 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.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; 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.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -31,10 +44,14 @@ public class ProductSearchServiceImpl implements ProductSearchService {
@Autowired @Autowired
private ProductRepository productRepository; private ProductRepository productRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate; // 因为需要使用到聚合操作只好引入 ElasticsearchTemplate
@Reference(validation = "true") @Reference(validation = "true")
private ProductSpuService productSpuService; private ProductSpuService productSpuService;
@Reference(validation = "true") @Reference(validation = "true")
private ProductCategoryService productCategoryService;
@Reference(validation = "true")
private CartService cartService; private CartService cartService;
@Override @Override
@ -57,9 +74,22 @@ public class ProductSearchServiceImpl implements ProductSearchService {
lastId = spus.get(spus.size() - 1).getId(); lastId = spus.get(spus.size() - 1).getId();
} }
} }
// 返回成功
return CommonResult.success(rebuildCounts); 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") @SuppressWarnings("OptionalGetWithoutIsPresent")
private ESProductDO convert(ProductSpuDetailBO spu) { private ESProductDO convert(ProductSpuDetailBO spu) {
// 获得最小价格的 SKU 用于下面的价格计算 // 获得最小价格的 SKU 用于下面的价格计算
@ -72,13 +102,13 @@ public class ProductSearchServiceImpl implements ProductSearchService {
} }
@Override @Override
public CommonResult<ESProductPageBO> searchPage(ProductSearchPageDTO searchPageDTO) { public CommonResult<ProductPageBO> getSearchPage(ProductSearchPageDTO searchPageDTO) {
checkSortFieldInvalid(searchPageDTO.getSorts()); checkSortFieldInvalid(searchPageDTO.getSorts());
// 执行查询 // 执行查询
Page<ESProductDO> searchPage = productRepository.search(searchPageDTO.getCid(), searchPageDTO.getKeyword(), Page<ESProductDO> searchPage = productRepository.search(searchPageDTO.getCid(), searchPageDTO.getKeyword(),
searchPageDTO.getPageNo(), searchPageDTO.getPageSize(), searchPageDTO.getSorts()); searchPageDTO.getPageNo(), searchPageDTO.getPageSize(), searchPageDTO.getSorts());
// 转换结果 // 转换结果
ESProductPageBO resultPage = new ESProductPageBO() ProductPageBO resultPage = new ProductPageBO()
.setList(ProductSearchConvert.INSTANCE.convert(searchPage.getContent())) .setList(ProductSearchConvert.INSTANCE.convert(searchPage.getContent()))
.setTotal((int) searchPage.getTotalElements()); .setTotal((int) searchPage.getTotalElements());
return CommonResult.success(resultPage); return CommonResult.success(resultPage);
@ -92,4 +122,46 @@ public class ProductSearchServiceImpl implements ProductSearchService {
String.format("排序字段(%s) 不在允许范围内", sortingField.getField()))); 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

@ -18,3 +18,9 @@ dubbo:
name: dubbo name: dubbo
scan: scan:
base-packages: cn.iocoder.mall.search.biz.service base-packages: cn.iocoder.mall.search.biz.service
# rocketmq
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: search-producer-group