后端:增加 MyBatis JSONTypeHandler

后端:增加指定商品的促销价格计算
前端:商品详情页,增加促销价格计算
This commit is contained in:
YunaiV 2019-04-16 23:31:22 +08:00
parent 4aac5bd2c6
commit 3909a95495
32 changed files with 686 additions and 104 deletions

View File

@ -64,6 +64,23 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.7</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,70 @@
package cn.iocoder.common.framework.mybatis;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* TODO 芋艿
*
* 参考 https://www.cnblogs.com/waterystone/p/5547254.html
*
* 后续补充下注释和测试类以及文章
*
* @param <T>
*/
public class JSONTypeHandler<T extends Object> extends BaseTypeHandler<T> {
private Class<T> clazz;
public JSONTypeHandler(Class<T> clazz) {
if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
this.clazz = clazz;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, this.toJson(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return this.toObject(rs.getString(columnName), clazz);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.toObject(rs.getString(columnIndex), clazz);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.toObject(cs.getString(columnIndex), clazz);
}
private String toJson(T object) {
try {
return JSON.toJSONString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private T toObject(String content, Class<?> clazz) {
if (content != null && !content.isEmpty()) {
try {
return (T) JSON.parseObject(content, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
}

View File

@ -120,6 +120,16 @@ export function getCartConfirmCreateOrder(skuId, quantity) {
});
}
export function getCartCalcSkuPrice(skuId) {
return request({
url: '/order-api/users/cart/calc_sku_price',
method: 'get',
params: {
skuId,
}
});
}
// 物流信息
export function getLogisticsInfo(params) {

View File

@ -7,20 +7,6 @@
</van-swipe-item>
</van-swipe>
<!-- TODO 这里需要优化下芋艿 -->
<div class="limit-dising-banner">
<div class="row no-wrap flex-center limit-price">
<span class="font-13 pusht">¥</span><span class="price">2.8</span>
<div class="pushl">
<div><del>¥14</del></div>
<div><span class="tag">限时抢购</span></div>
</div>
</div>
<div class="counting">
<p>距离结束仅剩</p>
<div class="row no-wrap flex-center counting-clock" style="display: block;"><span class="num">71</span><i class="wxIcon wxIcon-colon"></i><span class="num">42</span><i class="wxIcon wxIcon-colon"></i><span class="num">02</span></div>
</div>
</div>
<van-cell-group>
<van-cell>
<span class="goods-price">{{ formatPrice(initialSku.price) }}</span>
@ -79,6 +65,25 @@
</van-cell-group>
<!-- <van-cell is-link @click="sorry">-->
<!-- <template slot="title">-->
<!-- <van-tag type="danger">多买优惠</van-tag>-->
<!-- <span> 满2件总价打9折</span>-->
<!-- </template>-->
<!-- </van-cell>-->
<van-cell is-link @click="sorry">
<template slot="title">
<van-tag type="danger">满减</van-tag>
<span> 满100元减50元</span>
</template>
</van-cell>
<van-cell is-link @click="sorry">
<template slot="title">
<van-tag type="danger">限购</van-tag>
<span> 购买不超过5件时享受单件价8.00超出数量以结算价为准</span>
</template>
</van-cell>
<div class="goods-info">
<p class="goods-info-title">图文详情</p>
<div v-html="spu.description"></div>
@ -99,28 +104,6 @@
</van-goods-action-big-btn>
</van-goods-action>
<!--<van-actionsheet v-model="show" title="促销" style="font-size:14px;">-->
<!---->
<!--<van-cell is-link @click="sorry" >-->
<!--<template slot="title">-->
<!--<van-tag type="danger">多买优惠</van-tag>-->
<!--<span> 满2件总价打9折</span>-->
<!--</template>-->
<!--</van-cell>-->
<!--<van-cell is-link @click="sorry" >-->
<!--<template slot="title">-->
<!--<van-tag type="danger">满减</van-tag>-->
<!--<span> 满100元减50元</span>-->
<!--</template>-->
<!--</van-cell>-->
<!--<van-cell is-link @click="sorry" >-->
<!--<template slot="title">-->
<!--<van-tag type="danger">限购</van-tag>-->
<!--<span> 购买不超过5件时享受单件价8.00超出数量以结算价为准</span>-->
<!--</template>-->
<!--</van-cell>-->
<!--</van-actionsheet>-->
<!--<van-actionsheet v-model="showTag" title="服务说明" style="font-size:14px;">-->
<!---->
<!--<van-cell>-->
@ -181,8 +164,8 @@
<script>
// import skuData from '../../data/sku';
import {getProductSpuInfo} from '../../api/product';
import {addCart, countCart} from '../../api/order';
import { Dialog } from 'vant';
import {addCart, countCart, getCartCalcSkuPrice} from '../../api/order';
import {Dialog} from 'vant';
export default {
components: {},
@ -212,6 +195,10 @@
cartCount: 0,
calSkuPriceResult: {
},
};
},
methods: {
@ -233,6 +220,7 @@
this.initialSku.quantity = value;
},
skuSelected({skuValue, selectedSku, selectedSkuComb}) { // sku
// TODO
// console.log(skuValue);
// console.log(selectedSku);
// console.log(selectedSkuComb);
@ -240,8 +228,14 @@
...selectedSkuComb,
quantity: 1,
};
// sku
this.doCalcSkuPrice(this.initialSku.id)
},
doCalcSkuPrice(skuId) {
getCartCalcSkuPrice(skuId).then(data => {
this.calSkuPriceResult = data;
});
},
onClickCart() {
this.$router.push('/cart');
},
@ -347,6 +341,8 @@
// TODO sku
this.initialSku = vanSku.list[0];
this.initialSku.quantity = 1;
// sku
this.doCalcSkuPrice(this.initialSku.id);
});
//
countCart().then(data => {

View File

@ -4,9 +4,11 @@ import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.order.api.CartService;
import cn.iocoder.mall.order.api.OrderService;
import cn.iocoder.mall.order.api.bo.CalcOrderPriceBO;
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.order.api.bo.CartItemBO;
import cn.iocoder.mall.order.api.dto.CalcOrderPriceDTO;
import cn.iocoder.mall.order.application.convert.CartConvert;
import cn.iocoder.mall.order.application.vo.UsersCalcSkuPriceVO;
import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
@ -129,6 +131,17 @@ public class UsersCartController {
return cartService.calcOrderPrice(calcOrderPriceDTO);
}
@GetMapping("/calc_sku_price")
public CommonResult<UsersCalcSkuPriceVO> calcSkuPrice(@RequestParam("skuId") Integer skuId) {
// 计算 sku 的价格
CommonResult<CalcSkuPriceBO> calcSkuPriceResult = cartService.calcSkuPrice(skuId);
// 返回结果
if (calcSkuPriceResult.isError()) {
return CommonResult.error(calcSkuPriceResult);
}
return CommonResult.success(CartConvert.INSTANCE.convert2(calcSkuPriceResult.getData()));
}
public CommonResult<Object> confirmOrder() {
// 查询购物车列表选中的
// cartService.list(userId, true);

View File

@ -1,6 +1,8 @@
package cn.iocoder.mall.order.application.convert;
import cn.iocoder.mall.order.api.bo.CalcOrderPriceBO;
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.order.application.vo.UsersCalcSkuPriceVO;
import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import org.mapstruct.Mapper;
@ -15,4 +17,6 @@ public interface CartConvert {
UsersCartDetailVO convert2(CalcOrderPriceBO calcOrderPriceBO);
UsersCalcSkuPriceVO convert2(CalcSkuPriceBO calcSkuPriceBO);
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.mall.order.application.vo;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
@ApiModel("计算商品 SKU 价格结果 VO")
@Data
@Accessors(chain = true)
public class UsersCalcSkuPriceVO {
/**
* 满减送促销活动
*
* TODO 芋艿后续改成 VO
*/
private PromotionActivityBO fullPrivilege;
/**
* 电视和折扣促销活动
*
* TODO 芋艿后续改成 VO
*/
private PromotionActivityBO timeLimitedDiscount;
/**
* 原价格单位
*/
private Integer originalPrice;
/**
* 最终价格单位
*/
private Integer presentPrice;
}

View File

@ -87,24 +87,14 @@ public interface CartService {
*/
CommonResult<CalcOrderPriceBO> calcOrderPrice(CalcOrderPriceDTO calcOrderPriceDTO);
/**
* 计算指定商品 SKU 的金额并返回计算结果
*
* TODO 芋艿此处只会计算限时折扣带来的价格变化
*
* @param skuId 商品 SKU 编号
* @return 计算订单金额结果
*/
CommonResult<CalcSkuPriceBO> calcSkuPrice(Integer skuId);
/**
* 获得购物车明细
*
* TODO 芋艿可能放在 Controller 更合适
*
* @param userId 用户编号
* @return 购物车明细
*/
CommonResult<CartBO> details(Integer userId);
/**
* 基于购物车创建订单
*
* @param userId 用户编号
* @return 订单信息
*/
CommonResult<OrderCreateBO> createOrder(Integer userId);
}

View File

@ -4,7 +4,9 @@ import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.order.api.CartService;
import cn.iocoder.mall.order.api.bo.*;
import cn.iocoder.mall.order.api.bo.CalcOrderPriceBO;
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.order.api.bo.CartItemBO;
import cn.iocoder.mall.order.api.constant.CartItemStatusEnum;
import cn.iocoder.mall.order.api.constant.OrderErrorCodeEnum;
import cn.iocoder.mall.order.api.dto.CalcOrderPriceDTO;
@ -14,6 +16,11 @@ import cn.iocoder.mall.order.biz.dataobject.CartItemDO;
import cn.iocoder.mall.product.api.ProductSpuService;
import cn.iocoder.mall.product.api.bo.ProductSkuBO;
import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO;
import cn.iocoder.mall.promotion.api.PromotionActivityService;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
import cn.iocoder.mall.promotion.api.constant.PreferentialTypeEnum;
import cn.iocoder.mall.promotion.api.constant.PromotionActivityStatusEnum;
import cn.iocoder.mall.promotion.api.constant.PromotionActivityTypeEnum;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -30,6 +37,8 @@ public class CartServiceImpl implements CartService {
@Reference(validation = "true")
private ProductSpuService productSpuService;
@Reference(validation = "true")
private PromotionActivityService promotionActivityService;
@Autowired
private CartMapper cartMapper;
@ -188,17 +197,62 @@ public class CartServiceImpl implements CartService {
}
@Override
@SuppressWarnings("Duplicates")
public CommonResult<CalcSkuPriceBO> calcSkuPrice(Integer skuId) {
return null;
// 查询 SKU 是否合法
CommonResult<ProductSkuBO> skuResult = productSpuService.getProductSku(skuId);
if (skuResult.isError()) {
return CommonResult.error(skuResult);
}
ProductSkuBO sku = skuResult.getData();
if (sku == null
|| CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode());
}
// 查询促销活动
CommonResult<List<PromotionActivityBO>> activityListResult = promotionActivityService.getPromotionActivityListBySpuId(sku.getSpuId(),
Arrays.asList(PromotionActivityStatusEnum.WAIT.getValue(), PromotionActivityStatusEnum.RUN.getValue()));
if (activityListResult.isError()) {
return CommonResult.error(activityListResult);
}
// 如果无促销活动则直接返回默认结果即可
List<PromotionActivityBO> activityList = activityListResult.getData();
if (activityList.isEmpty()) {
return CommonResult.success(new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setPresentPrice(sku.getPrice()));
}
// 如果有促销活动则开始做计算 TODO 芋艿因为现在暂时只有限时折扣 + 满减送所以写的比较简单先
PromotionActivityBO fullPrivilege = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.FULL_PRIVILEGE);
PromotionActivityBO timeLimitedDiscount = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT);
Integer presentPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
// 返回结果
return CommonResult.success(new CalcSkuPriceBO().setFullPrivilege(fullPrivilege).setTimeLimitedDiscount(timeLimitedDiscount)
.setOriginalPrice(sku.getPrice()).setPresentPrice(presentPrice));
}
@Override
public CommonResult<CartBO> details(Integer userId) {
return null;
private Integer calcSkuPriceByTimeLimitDiscount(ProductSkuBO sku, PromotionActivityBO timeLimitedDiscount) {
// 获得对应的优惠项
PromotionActivityBO.TimeLimitedDiscount.Item item = timeLimitedDiscount.getTimeLimitedDiscount().getItems().stream()
.filter(item0 -> item0.getSpuId().equals(sku.getSpuId()))
.findFirst().orElse(null);
if (item == null) {
throw new IllegalArgumentException(String.format("折扣活动(%s) 不存在商品(%s) 的优惠配置",
timeLimitedDiscount.toString(), sku.toString()));
}
// 计算价格
if (PreferentialTypeEnum.PRICE.getValue().equals(item.getPreferentialType())) { // 减价
int presentPrice = sku.getPrice() - item.getPreferentialValue();
return presentPrice >= 0 ? presentPrice : sku.getPrice(); // 如果计算优惠价格小于 0 则说明无法使用优惠
}
if (PreferentialTypeEnum.DISCOUNT.getValue().equals(item.getPreferentialType())) { // 打折
return sku.getPrice() * item.getPreferentialValue() / 100;
}
throw new IllegalArgumentException(String.format("折扣活动(%s) 的优惠类型不正确", timeLimitedDiscount.toString()));
}
@Override
public CommonResult<OrderCreateBO> createOrder(Integer userId) {
return null;
private PromotionActivityBO findPromotionActivityByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
return activityList.stream()
.filter(activity -> type.getValue().equals(activity.getActivityType()))
.findFirst().orElse(null);
}
}

View File

@ -103,7 +103,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
// 校验 Sku 规格
CommonResult<Boolean> validProductSkuResult = validProductSku(productSpuAddDTO.getSkus(), validAttrResult.getData());
if (validProductSkuResult.isError()) {
return CommonResult.error(validProductSkuResult);
// return CommonResult.error(validProductSkuResult);
throw ServiceExceptionUtil.exception(validProductSkuResult.getCode());
}
productSkuMapper.insertList(skus);
// 返回成功

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
@ -10,7 +11,7 @@ import java.util.Date;
*/
@Data
@Accessors(chain = true)
public class BannerBO {
public class BannerBO implements Serializable {
/**
* 编号

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@ -10,7 +11,7 @@ import java.util.List;
*/
@Data
@Accessors(chain = true)
public class BannerPageBO {
public class BannerPageBO implements Serializable {
/**
* Banner 数组

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
@ -10,7 +11,7 @@ import java.util.Date;
*/
@Data
@Accessors(chain = true)
public class CouponCardBO {
public class CouponCardBO implements Serializable {
// ========== 基本信息 BEGIN ==========
/**

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@ -10,7 +11,7 @@ import java.util.List;
*/
@Data
@Accessors(chain = true)
public class CouponCardPageBO {
public class CouponCardPageBO implements Serializable {
/**
* 优惠劵数组

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
@ -10,7 +11,7 @@ import java.util.Date;
*/
@Data
@Accessors(chain = true)
public class CouponTemplateBO {
public class CouponTemplateBO implements Serializable {
// ========== 基本信息 BEGIN ==========
/**

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@ -10,7 +11,7 @@ import java.util.List;
*/
@Data
@Accessors(chain = true)
public class CouponTemplatePageBO {
public class CouponTemplatePageBO implements Serializable {
/**
* 优惠劵数组

View File

@ -4,6 +4,7 @@ import cn.iocoder.mall.promotion.api.constant.ProductRecommendTypeEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
@ -11,7 +12,7 @@ import java.util.Date;
*/
@Data
@Accessors(chain = true)
public class ProductRecommendBO {
public class ProductRecommendBO implements Serializable {
/**
* 编号

View File

@ -3,6 +3,7 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@ -10,7 +11,7 @@ import java.util.List;
*/
@Data
@Accessors(chain = true)
public class ProductRecommendPageBO {
public class ProductRecommendPageBO implements Serializable {
/**
* ProductRecommend 数组

View File

@ -3,12 +3,12 @@ package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
@Data
@Accessors(chain = true)
public class PromotionActivityBO {
public class PromotionActivityBO implements Serializable {
/**
* 活动编号
@ -23,7 +23,7 @@ public class PromotionActivityBO {
*
* 参见 {@link cn.iocoder.mall.promotion.api.constant.PromotionActivityTypeEnum} 枚举
*/
private Integer type;
private Integer activityType;
/**
* 活动状态
*
@ -31,23 +31,27 @@ public class PromotionActivityBO {
*/
private Integer status;
/**
* 匹配的商品 SPU 编号
* 限制折扣
*/
private Set<Integer> spuIds;
private TimeLimitedDiscount timeLimitedDiscount;
/**
* 满减送
*/
private FullPrivilege fullPrivilege;
/**
* 限制折扣
*/
@Data
@Accessors(chain = true)
public static class TimeLimitedDiscount {
public static class TimeLimitedDiscount implements Serializable {
/**
* 商品折扣
*/
@Data
@Accessors(chain = true)
public static class Item {
public static class Item implements Serializable {
/**
* 商品 SPU 编号
@ -82,14 +86,14 @@ public class PromotionActivityBO {
*/
@Data
@Accessors(chain = true)
public static class FullPrivilege {
public static class FullPrivilege implements Serializable {
/**
* 优惠
*/
@Data
@Accessors(chain = true)
public static class Privilege {
public static class Privilege implements Serializable {
/**
* 满足类型

View File

@ -9,8 +9,8 @@ import java.util.Arrays;
*/
public enum PreferentialTypeEnum implements IntArrayValuable {
PRICE(1, "代金卷"),
DISCOUNT(2, "扣卷"),
PRICE(1, "减价"),
DISCOUNT(2, ""),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PreferentialTypeEnum::getValue).toArray();

View File

@ -7,10 +7,10 @@ import java.util.Arrays;
public enum RangeTypeEnum implements IntArrayValuable {
ALL(10, "所有可用"),
PRODUCT_INCLUDE_PRT(20, "部分商品可用,或指定商品可用"),
PRODUCT_EXCLUDE_PRT(21, "部分商品不可用,或指定商品可用"),
CATEGORY_INCLUDE_PRT(30, "部分分类可用,或指定分类可用"),
CATEGORY_EXCLUDE_PRT(31, "部分分类不可用,或指定分类可用"),
PRODUCT_INCLUDE_PART(20, "部分商品可用,或指定商品可用"),
PRODUCT_EXCLUDE_PART(21, "部分商品不可用,或指定商品可用"),
CATEGORY_INCLUDE_PART(30, "部分分类可用,或指定分类可用"),
CATEGORY_EXCLUDE_PART(31, "部分分类不可用,或指定分类可用"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(RangeTypeEnum::getValue).toArray();

View File

@ -16,15 +16,15 @@ public interface ProductRecommendConvert {
ProductRecommendConvert INSTANCE = Mappers.getMapper(ProductRecommendConvert.class);
@Mappings({})
ProductRecommendBO convertToBO(ProductRecommendDO banner);
ProductRecommendBO convertToBO(ProductRecommendDO recommend);
@Mappings({})
List<ProductRecommendBO> convertToBO(List<ProductRecommendDO> bannerList);
List<ProductRecommendBO> convertToBO(List<ProductRecommendDO> recommendList);
@Mappings({})
ProductRecommendDO convert(ProductRecommendAddDTO bannerAddDTO);
ProductRecommendDO convert(ProductRecommendAddDTO recommendAddDTO);
@Mappings({})
ProductRecommendDO convert(ProductRecommendUpdateDTO bannerUpdateDTO);
ProductRecommendDO convert(ProductRecommendUpdateDTO recommendUpdateDTO);
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.mall.promotion.biz.convert;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
import cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface PromotionActivityConvert {
PromotionActivityConvert INSTANCE = Mappers.getMapper(PromotionActivityConvert.class);
@Mappings({})
PromotionActivityBO convertToBO(PromotionActivityDO activity);
@Mappings({})
List<PromotionActivityBO> convertToBO(List<PromotionActivityDO> activityList);
// @Mappings({})
// PromotionActivityDO convert(PromotionActivityAddDTO activityAddDTO);
//
// @Mappings({})
// PromotionActivityDO convert(PromotionActivityUpdateDTO activityUpdateDTO);
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.mall.promotion.biz.dao;
import cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
@Repository
public interface PromotionActivityMapper {
PromotionActivityDO selectById(@Param("id") Integer id);
List<PromotionActivityDO> selectListByStatus(@Param("statuses")Collection<Integer> statuses);
void insert(PromotionActivityDO activity);
}

View File

@ -58,11 +58,11 @@ public class PromotionActivityDO extends BaseDO {
/**
* 限制折扣字符串使用 JSON 序列化成字符串存储
*/
private String timeLimitedDiscount;
private TimeLimitedDiscount timeLimitedDiscount;
/**
* 满减送字符串使用 JSON 序列化成字符串存储
*/
private String fullPrivilege;
private FullPrivilege fullPrivilege;
/**
* 限制折扣
@ -173,6 +173,10 @@ public class PromotionActivityDO extends BaseDO {
* 指定可用商品列表
*/
private List<Integer> rangeValues;
/**
* 是否循环
*/
private Boolean cycled;
/**
* 优惠数组
*/

View File

@ -0,0 +1,12 @@
package cn.iocoder.mall.promotion.biz.mybatis;
import cn.iocoder.common.framework.mybatis.JSONTypeHandler;
import cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO;
public class TestHandler extends JSONTypeHandler<PromotionActivityDO.TimeLimitedDiscount> {
public TestHandler(Class<PromotionActivityDO.TimeLimitedDiscount> clazz) {
super(clazz);
}
}

View File

@ -0,0 +1,85 @@
package cn.iocoder.mall.promotion.biz.service;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.promotion.api.PromotionActivityService;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
import cn.iocoder.mall.promotion.api.constant.PromotionActivityTypeEnum;
import cn.iocoder.mall.promotion.api.constant.RangeTypeEnum;
import cn.iocoder.mall.promotion.biz.convert.PromotionActivityConvert;
import cn.iocoder.mall.promotion.biz.dao.PromotionActivityMapper;
import cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@Service // 实际上不用添加添加的原因是必须 Spring 报错提示
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
public class PromotionActivityServiceImpl implements PromotionActivityService {
@Autowired
private PromotionActivityMapper promotionActivityMapper;
@Override
public CommonResult<List<PromotionActivityBO>> getPromotionActivityListBySpuId(Integer spuId, Collection<Integer> activityStatuses) {
return this.getPromotionActivityListBySpuIds(Collections.singleton(spuId), activityStatuses);
}
@Override
public CommonResult<List<PromotionActivityBO>> getPromotionActivityListBySpuIds(Collection<Integer> spuIds, Collection<Integer> activityStatuses) {
if (spuIds.isEmpty() || activityStatuses.isEmpty()) {
return CommonResult.success(Collections.emptyList());
}
// 查询指定状态的促销活动
List<PromotionActivityDO> activityList = promotionActivityMapper.selectListByStatus(activityStatuses);
if (activityList.isEmpty()) {
return CommonResult.success(Collections.emptyList());
}
// 匹配商品
for (Iterator<PromotionActivityDO> iterator = activityList.iterator(); iterator.hasNext();) {
PromotionActivityDO activity = iterator.next();
boolean matched = false;
for (Integer spuId : spuIds) {
if (PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue().equals(activity.getActivityType())) {
matched = isSpuMatchTimeLimitDiscount(spuId, activity);
} else if (PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType())) {
matched = isSpuMatchFullPrivilege(spuId, activity);
}
if (matched) {
break;
}
}
// 不匹配则进行移除
if (!matched) {
iterator.remove();
}
}
// 返回最终结果
return CommonResult.success(PromotionActivityConvert.INSTANCE.convertToBO(activityList));
}
private boolean isSpuMatchTimeLimitDiscount(Integer spuId, PromotionActivityDO activity) {
Assert.isTrue(PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue().equals(activity.getActivityType()),
"传入的必须的促销活动必须是限时折扣");
return activity.getTimeLimitedDiscount().getItems().stream()
.anyMatch(item -> spuId.equals(item.getSpuId()));
}
private boolean isSpuMatchFullPrivilege(Integer spuId, PromotionActivityDO activity) {
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
"传入的必须的促销活动必须是满减送");
PromotionActivityDO.FullPrivilege fullPrivilege = activity.getFullPrivilege();
if (RangeTypeEnum.ALL.getValue().equals(fullPrivilege.getRangeType())) {
return true;
} else if (RangeTypeEnum.PRODUCT_INCLUDE_PART.getValue().equals(fullPrivilege.getRangeType())) {
return fullPrivilege.getRangeValues().contains(spuId);
} else {
throw new IllegalArgumentException(String.format("促销活动(%s) 可用范围的类型是不正确", activity.toString()));
}
}
}

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.iocoder.mall.promotion.biz.dao.PromotionActivityMapper">
<sql id="FIELDS">
id, title, activity_type, status, start_time,
end_time, invalid_time, delete_time, time_limited_discount, full_privilege,
create_time, update_time
</sql>
<resultMap id="PromotionActivityResultMap" type="PromotionActivityDO">
<result property="timeLimitedDiscount" column="time_limited_discount" javaType="cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO$TimeLimitedDiscount" typeHandler="cn.iocoder.common.framework.mybatis.JSONTypeHandler"/>
<result property="fullPrivilege" column="full_privilege" javaType="cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO$FullPrivilege" typeHandler="cn.iocoder.common.framework.mybatis.JSONTypeHandler"/>
</resultMap>
<!-- <select id="selectListByPidAndStatusOrderBySort" resultType="PromotionActivityDO">-->
<!-- SELECT-->
<!-- <include refid="FIELDS" />-->
<!-- FROM banner-->
<!-- WHERE pid = #{pid}-->
<!-- AND status = #{status}-->
<!-- AND deleted = 0-->
<!-- ORDER BY sort ASC-->
<!-- </select>-->
<!-- <select id="selectList" resultType="PromotionActivityDO">-->
<!-- SELECT-->
<!-- <include refid="FIELDS" />-->
<!-- FROM banner-->
<!-- WHERE deleted = 0-->
<!-- </select>-->
<select id="selectById" parameterType="Integer" resultMap="PromotionActivityResultMap">
SELECT
<include refid="FIELDS" />
FROM promotion_activity
WHERE id = #{id}
</select>
<select id="selectListByStatus" resultMap="PromotionActivityResultMap">
SELECT
<include refid="FIELDS" />
FROM promotion_activity
WHERE status IN
<foreach item="status" collection="statuses" separator="," open="(" close=")" index="">
#{status}
</foreach>
</select>
<!-- <select id="selectListByStatus" parameterType="Integer" resultType="PromotionActivityDO">-->
<!-- SELECT-->
<!-- <include refid="FIELDS" />-->
<!-- FROM banner-->
<!-- <where>-->
<!-- <if test="status != null">-->
<!-- status = #{status}-->
<!-- </if>-->
<!-- AND deleted = 0-->
<!-- </where>-->
<!-- </select>-->
<!-- <select id="selectListByTitleLike" resultType="PromotionActivityDO">-->
<!-- SELECT-->
<!-- <include refid="FIELDS" />-->
<!-- FROM banner-->
<!-- <where>-->
<!-- <if test="title != null">-->
<!-- title LIKE "%"#{title}"%"-->
<!-- </if>-->
<!-- AND deleted = 0-->
<!-- </where>-->
<!-- LIMIT #{offset}, #{limit}-->
<!-- </select>-->
<!-- <select id="selectCountByTitleLike" resultType="Integer">-->
<!-- SELECT-->
<!-- COUNT(1)-->
<!-- FROM banner-->
<!-- <where>-->
<!-- <if test="title != null">-->
<!-- title LIKE "%"#{title}"%"-->
<!-- </if>-->
<!-- AND deleted = 0-->
<!-- </where>-->
<!-- </select>-->
<insert id="insert" parameterType="PromotionActivityDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO promotion_activity (
title, activity_type, status, start_time,
end_time, invalid_time, delete_time,
time_limited_discount,
full_privilege,
create_time
) VALUES (
#{title}, #{activityType}, #{status}, #{startTime},
#{endTime}, #{invalidTime}, #{deleteTime},
#{timeLimitedDiscount, typeHandler=cn.iocoder.common.framework.mybatis.JSONTypeHandler},
#{fullPrivilege, typeHandler=cn.iocoder.common.framework.mybatis.JSONTypeHandler},
#{createTime}
)
</insert>
<!-- <update id="update" parameterType="PromotionActivityDO">-->
<!-- UPDATE banner-->
<!-- <set>-->
<!-- <if test="title != null">-->
<!-- title = #{title},-->
<!-- </if>-->
<!-- <if test="url != null">-->
<!-- url = #{url},-->
<!-- </if>-->
<!-- <if test="picUrl != null">-->
<!-- pic_url = #{picUrl} ,-->
<!-- </if>-->
<!-- <if test="sort != null">-->
<!-- sort = #{sort},-->
<!-- </if>-->
<!-- <if test="status != null">-->
<!-- status = #{status},-->
<!-- </if>-->
<!-- <if test="memo != null">-->
<!-- memo = #{memo},-->
<!-- </if>-->
<!-- <if test="deleted != null">-->
<!-- deleted = #{deleted}-->
<!-- </if>-->
<!-- </set>-->
<!-- WHERE id = #{id}-->
<!-- </update>-->
</mapper>

View File

@ -0,0 +1,7 @@
package cn.iocoder.mall.promotion.biz;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.promotion"})
public class Application {
}

View File

@ -0,0 +1,94 @@
package cn.iocoder.mall.promotion.biz.dao;
import cn.iocoder.common.framework.util.DateUtil;
import cn.iocoder.mall.promotion.api.constant.PreferentialTypeEnum;
import cn.iocoder.mall.promotion.api.constant.PromotionActivityStatusEnum;
import cn.iocoder.mall.promotion.api.constant.PromotionActivityTypeEnum;
import cn.iocoder.mall.promotion.api.constant.RangeTypeEnum;
import cn.iocoder.mall.promotion.biz.dataobject.PromotionActivityDO;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PromotionActivityMapperTest {
@Autowired
private PromotionActivityMapper promotionActivityMapper;
/**
* 插入限时折扣活动
*/
@Test
@Ignore
public void testInsert01() {
// 创建 PromotionActivityDO 对象
PromotionActivityDO activityDO = new PromotionActivityDO();
activityDO.setTitle("老板跑路了");
activityDO.setActivityType(PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue());
activityDO.setStatus(PromotionActivityStatusEnum.RUN.getValue());
activityDO.setStartTime(new Date());
activityDO.setEndTime(DateUtil.addDate(new Date(), Calendar.DAY_OF_YEAR, 100));
activityDO.setCreateTime(new Date());
// 创建 TimeLimitedDiscount 对象
PromotionActivityDO.TimeLimitedDiscount discount = new PromotionActivityDO.TimeLimitedDiscount();
discount.setQuota(0);
discount.setItems(new ArrayList<>());
PromotionActivityDO.TimeLimitedDiscount.Item item01 = new PromotionActivityDO.TimeLimitedDiscount.Item();
item01.setSpuId(32);
item01.setPreferentialType(PreferentialTypeEnum.DISCOUNT.getValue());
item01.setPreferentialValue(40);
discount.getItems().add(item01);
activityDO.setTimeLimitedDiscount(discount);
promotionActivityMapper.insert(activityDO);
}
/**
* 插入满减送活动
*/
@Test
public void testInsert02() {
// 创建 PromotionActivityDO 对象
PromotionActivityDO activityDO = new PromotionActivityDO();
activityDO.setTitle("老四赶海");
activityDO.setActivityType(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue());
activityDO.setStatus(PromotionActivityStatusEnum.RUN.getValue());
activityDO.setStartTime(new Date());
activityDO.setEndTime(DateUtil.addDate(new Date(), Calendar.DAY_OF_YEAR, 100));
activityDO.setCreateTime(new Date());
// 创建 TimeLimitedDiscount 对象
PromotionActivityDO.FullPrivilege fullPrivilege = new PromotionActivityDO.FullPrivilege();
fullPrivilege.setRangeType(RangeTypeEnum.ALL.getValue());
fullPrivilege.setCycled(Boolean.FALSE);
fullPrivilege.setPrivileges(new ArrayList<>());
PromotionActivityDO.FullPrivilege.Privilege privilege01 = new PromotionActivityDO.FullPrivilege.Privilege();
privilege01.setMeetType(1); // TODO 芋艿硬编码
privilege01.setMeetValue(20);
privilege01.setPreferentialType(PreferentialTypeEnum.DISCOUNT.getValue());
privilege01.setPreferentialValue(80);
fullPrivilege.getPrivileges().add(privilege01);
activityDO.setFullPrivilege(fullPrivilege);
promotionActivityMapper.insert(activityDO);
}
/**
* 查询促销活动
*/
@Test
public void testSelectById() {
PromotionActivityDO activity01 = promotionActivityMapper.selectById(1);
System.out.println(activity01);
PromotionActivityDO activity02 = promotionActivityMapper.selectById(2);
System.out.println(activity02);
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.mall.promotion.biz;