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

后端:增加搜索服务,监听 MQ 消息,建立商品索引
pull/1/head
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;
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

@ -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%;">
<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 :class="'item_options '+(filterShow?'show':'')">
<ul>
<li v-for="category in categories" :class="category.id === categoryId ?'selected':''" v-on:click="onCategoryClick(category.id)">
{{ category.name }}
</li>
</ul>
</div>
<!-- <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

@ -129,4 +129,5 @@ public class UsersOrderController {
orderInfoBO.setStatusText(dictResult.getData().getDisplayName());
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.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;
@ -62,14 +63,17 @@ public class OrderServiceImpl implements OrderService {
@Autowired
private OrderCancelMapper orderCancelMapper;
@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) {
@ -248,7 +252,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;
/**
@ -88,4 +86,4 @@ public class PayTransactionPaySuccessMessage {
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.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;
@ -28,4 +28,4 @@ public interface PayTransactionConvert {
@Mappings({})
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.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 的更新语句。但是,因为更新字段不同,所以不会有问题。
@ -48,4 +48,4 @@ public class PayTransactionNotifyJob extends IJobHandler {
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.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
*/
@ -28,4 +37,4 @@ public interface ProductCategoryService {
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>
</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,8 +17,10 @@ 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

@ -22,4 +22,11 @@ dubbo:
port: -1
name: dubbo
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
</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,
@ -70,4 +81,4 @@
WHERE id = #{id}
</update>
</mapper>
</mapper>

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,12 +26,12 @@ public class UsersProductSearchController {
private ProductSearchService productSearchService;
@GetMapping("/page") // TODO 芋艿,后面把 BO 改成 VO
public CommonResult<ESProductPageBO> page(@RequestParam(value = "cid", required = false) Integer cid,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "pageNo", required = false) Integer pageNo,
@RequestParam(value = "pageSize", required = false) Integer pageSize,
@RequestParam(value = "sortField", required = false) String sortField,
@RequestParam(value = "sortOrder", required = false) String sortOrder) {
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,
@RequestParam(value = "sortField", required = false) String sortField,
@RequestParam(value = "sortOrder", required = false) String sortOrder) {
// 创建 ProductSearchPageDTO 对象
ProductSearchPageDTO productSearchPageDTO = new ProductSearchPageDTO().setCid(cid).setKeyword(keyword)
.setPageNo(pageNo).setPageSize(pageSize);
@ -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

@ -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