diff --git a/mallinkService/src/main/java/com/iformall/domain/po/WxOrder.java b/mallinkService/src/main/java/com/iformall/domain/po/WxOrder.java index 53d4b1c31..acc08859b 100644 --- a/mallinkService/src/main/java/com/iformall/domain/po/WxOrder.java +++ b/mallinkService/src/main/java/com/iformall/domain/po/WxOrder.java @@ -93,6 +93,10 @@ public class WxOrder implements Serializable { /**砍价人数**/ @io.swagger.annotations.ApiModelProperty(value="砍价人数",name="pressCurrentNum") private Integer pressCurrentNum; + /**已砍价格**/ + @io.swagger.annotations.ApiModelProperty(value="已砍价格",name="pressCurrentValue") + private Integer pressCurrentValue; + public Long getOrderNumber() { @@ -214,6 +218,14 @@ public class WxOrder implements Serializable { this.pressCurrentNum = pressCurrentNum; } + public Integer getPressCurrentValue() { + return pressCurrentValue; + } + + public void setPressCurrentValue(Integer pressCurrentValue) { + this.pressCurrentValue = pressCurrentValue; + } + public static enum Field { Id_ASC("`id` ASC"),Id_DESC("`id` DESC") diff --git a/mallinkService/src/main/java/com/iformall/service/impl/WxOrderServiceImpl.java b/mallinkService/src/main/java/com/iformall/service/impl/WxOrderServiceImpl.java index dfbf97634..426d6938a 100644 --- a/mallinkService/src/main/java/com/iformall/service/impl/WxOrderServiceImpl.java +++ b/mallinkService/src/main/java/com/iformall/service/impl/WxOrderServiceImpl.java @@ -16,6 +16,7 @@ import com.iformall.exception.MallinkException; import com.iformall.mapper.*; import com.iformall.service.*; import com.iformall.utils.DateUtils; +import com.iformall.utils.PressUtils; import com.iformall.utils.RedisLock; import com.iformall.utils.Utility; import org.slf4j.Logger; @@ -98,6 +99,9 @@ public class WxOrderServiceImpl implements WxOrderService { @Autowired WxCouponSendService wxCouponSendService; + @Autowired + WxOrderPressMapper wxOrderPressMapper; + @Override public PageInfo listAsPage(WxOrder record, Integer pageIndex, Integer pageSize) { return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxOrderMapper.findList(record)); @@ -338,9 +342,18 @@ public class WxOrderServiceImpl implements WxOrderService { // 减库存操作 stockReduce(user, coupon, couponIdStr); + boolean isPress = false; + if (coupon.getType().equals(EnumCouponType.COUPON_PRESS.getCode())) { + isPress = true; + } + Date curr = new Date(); int payment = coupon.getSalePrice(); + if (isPress) { + // 砍价券 + payment = coupon.getPrice(); + } final IdWorker idWorker = IdWorker.get(); Long orderNumber = idWorker.nextId(); @@ -370,6 +383,30 @@ public class WxOrderServiceImpl implements WxOrderService { record.setDetail(bodyStr); record.setCreateDate(curr); record.setUpdateDate(curr); + if(isPress) { + record.setPressCurrentNum(1); + record.setPressEndDate(DateUtils.getHourTimeAfter(coupon.getPressLimitHours(), curr)); + + // 添加 wx_order_press + int total = coupon.getPrice() - coupon.getSalePrice(); + int left_total = total; + WxOrderPress orderPress = new WxOrderPress(); + orderPress.setId(idWorker.nextId()); + orderPress.setOrderId(orderNumber); + orderPress.setUserId(user.getId()); + orderPress.setCreateDate(curr); + orderPress.setPressValue(PressUtils.stateLessPressValue(total, left_total, coupon.getPressLimitNum(), 0)); + + try { + // 保存订单 + wxOrderPressMapper.insertSelective(orderPress); + } catch (RuntimeException e) { + // 库存恢复 + stockBack(record); + logger.error("保存砍价记录Err:" + e.getMessage()); + throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); + } + } try { // 保存订单 @@ -385,6 +422,8 @@ public class WxOrderServiceImpl implements WxOrderService { try { wxCouponActionLogService.addOne(user.getTenantId(), couponId, orderNumber, EnumCouponSendSendType.ACTIVE.getCode(), couponChannelId); } catch (Exception e) { + // 库存恢复 + stockBack(record); logger.error("保存订单:" + e.getMessage()); throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); } diff --git a/mallinkService/src/main/java/com/iformall/utils/DateUtils.java b/mallinkService/src/main/java/com/iformall/utils/DateUtils.java index d32d321ea..d2f8a2e1d 100755 --- a/mallinkService/src/main/java/com/iformall/utils/DateUtils.java +++ b/mallinkService/src/main/java/com/iformall/utils/DateUtils.java @@ -371,6 +371,21 @@ public class DateUtils { return mDate; } + /** + * 获得当前时间后几小时的时间 + * + * @param hour + * 后几小时 + * @return + */ + public static Date getHourTimeAfter(int hour, Date myDate) { + Date testDate = myDate; + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + long myTime = (testDate.getTime() / 1000) + 60 * 60 * hour; + testDate.setTime(myTime * 1000); + return testDate; + } + /** * 获得指定时间前几天,格式yyyy-MM-dd * diff --git a/mallinkService/src/main/java/com/iformall/utils/PressUtils.java b/mallinkService/src/main/java/com/iformall/utils/PressUtils.java new file mode 100755 index 000000000..ed0327567 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/PressUtils.java @@ -0,0 +1,52 @@ +package com.iformall.utils; + +import com.iformall.utils.press.stateless.DefaultStatelessReduceRule; +import com.iformall.utils.press.stateless.StatelessPingReduceRule; +import com.iformall.utils.press.stateless.StatelessReduceRule; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 日期处理 + * + * @author stormeye.wu + * @email wuguoqiang@iformall.com + * @date 2019年1月5月 + */ +public class PressUtils { + public final static int scale = 2; + + /** + * randomPressValue + * @param total 砍价总钱数(分) + * @param left_total 已砍价总钱数(分) + * @param limit_num 砍价限制次数 + * @param limit_index 砍价index + * @return + */ + public static int randomPressValue(int total, int left_total, int limit_num, int limit_index) { + BigDecimal tt = BigDecimal.valueOf(total * 1.0D / 100).setScale(scale, RoundingMode.HALF_UP); + BigDecimal lt = BigDecimal.valueOf(left_total* 1.0D / 100); + StatelessReduceRule rule = new DefaultStatelessReduceRule(); + BigDecimal current = rule.getReduce(tt, tt.subtract(lt), limit_num,limit_num - limit_index, scale); + return current.intValue() * 100; + } + + /** + * stateLessPressValue 优先 1/3砍掉一半 + * @param total 砍价总钱数(分) + * @param left_total 已砍价总钱数(分) + * @param limit_num 砍价限制次数 + * @param limit_index 砍价index + * @return + */ + public static int stateLessPressValue(int total, int left_total, int limit_num, int limit_index) { + BigDecimal tt = BigDecimal.valueOf(total * 1.0D / 100).setScale(scale, RoundingMode.HALF_UP); + BigDecimal lt = BigDecimal.valueOf(left_total* 1.0D / 100); + StatelessReduceRule rule = new StatelessPingReduceRule(); + BigDecimal current = rule.getReduce(tt, tt.subtract(lt), limit_num, limit_num - limit_index, scale); + return current.intValue() * 100; + } + +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/PingDuoDuoReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/PingDuoDuoReduceRule.java new file mode 100644 index 000000000..017aa8674 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/PingDuoDuoReduceRule.java @@ -0,0 +1,63 @@ +package com.iformall.utils.press; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 仿拼多多砍价规则。 + */ +public class PingDuoDuoReduceRule implements ReduceRule { + + private final int percentOfFirstNReduce; + private final int firstNReduce; + + private final SfReduceRule sfReduceRule = new SfReduceRule(); + + /** + * new PingDuoDuoReduceRule(80, 10) 表示前10个人可砍掉总砍价金额的80%。 + * + * @param percentOfFirstNReduce 前N个人可砍掉的百分比,取值范围(0, 100] + * @param firstNReduce 前N个砍价的人 + */ + public PingDuoDuoReduceRule(int percentOfFirstNReduce, int firstNReduce) { + this.percentOfFirstNReduce = percentOfFirstNReduce; + this.firstNReduce = firstNReduce; + } + + + /** + * @param totalReduce 总共可砍价的金额 + * @param totalReduceTimes 总共可砍价的次数 + * @return 每次砍掉的金额组成的列表。 + */ + @Override + public List getReduceList(BigDecimal totalReduce, int totalReduceTimes) { + if (totalReduceTimes < firstNReduce) { + throw new IllegalArgumentException("total reduce times is less than first n reduce times"); + } + + if (percentOfFirstNReduce > 100 || percentOfFirstNReduce <= 0) { + throw new IllegalArgumentException("reduce percent should in (0, 100]"); + } + + BigDecimal firstNReduceTotal = BigDecimal.valueOf(totalReduce.doubleValue() * percentOfFirstNReduce / 100).setScale(2, BigDecimal.ROUND_HALF_EVEN); + List ret = sfReduceRule.getReduceList(firstNReduceTotal, firstNReduce); + BigDecimal leftReduceTotal = totalReduce.subtract(firstNReduceTotal).setScale(2, BigDecimal.ROUND_HALF_EVEN); + if (totalReduceTimes - firstNReduce > 0) { + ret.addAll(sfReduceRule.getReduceList(leftReduceTotal, totalReduceTimes - firstNReduce)); + } + return ret; + } + + public static void main(String[] args) { + PingDuoDuoReduceRule rule = new PingDuoDuoReduceRule(79, 3); + List list = rule.getReduceList(new BigDecimal("100.21"), 11); + BigDecimal total = BigDecimal.valueOf(0); + int i = 0; + for (BigDecimal val : list) { + System.out.printf("第%d人砍掉了%s\n", ++i, val); + total = total.add(val); + } + System.out.printf("总金额=%s\n", total); + } +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/RecursiveReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/RecursiveReduceRule.java new file mode 100644 index 000000000..6439a458e --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/RecursiveReduceRule.java @@ -0,0 +1,60 @@ +package com.iformall.utils.press; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 砍价的几个因素: + * 1. 可砍价的人数(N) + * 2. 可砍掉的总金额(T) + * 3. 每次砍价的最低砍价金额(M) + * 4. 总砍价次数(K) + *

+ * 本砍价规则将'每次砍价的最低砍价金额'默认为 := T / (K * 2)。 + * + * 本砍价规则只是{@link com.iformall.utils.RecursiveReduceRule}的递归版本。 + */ +public class RecursiveReduceRule implements ReduceRule { + + private final int scale; + + public RecursiveReduceRule(int scale) { + this.scale = scale; + } + + @Override + public List getReduceList(BigDecimal totalReduce, int totalReduceTimes) { + if (totalReduceTimes <= 0) { + throw new IllegalArgumentException("total reduce times should gte 1"); + } + + // 最低砍价金额默认为: 总砍价金额 / (砍价次数 * 2) + BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), BigDecimal.ROUND_HALF_EVEN); + return getReduceList(totalReduce, totalReduceTimes, minReduce); + } + + public List getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { + List ret = new ArrayList<>(); + doReduce(totalReduce, totalReduceTimes, minReduce, ret); + return ret; + } + + private void doReduce(BigDecimal leftTotalReduce, int leftReduceTimes, BigDecimal minReduce, List vs) { + if (leftReduceTimes <= 0) { + return; + } else if (leftReduceTimes == 1) { + vs.add(leftTotalReduce); + return; + } + double min = minReduce.doubleValue(); + double max = leftTotalReduce.doubleValue() - min * leftReduceTimes; + double adjustedMax = min + (leftReduceTimes > 10 ? max / 3 : max); + double ratio = new Random().nextDouble(); + BigDecimal current = BigDecimal.valueOf(min + (adjustedMax - min) * ratio).setScale(this.scale, RoundingMode.HALF_EVEN); + vs.add(current); + doReduce(leftTotalReduce.subtract(current), --leftReduceTimes, minReduce, vs); + } +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/ReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/ReduceRule.java new file mode 100644 index 000000000..34274e1c8 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/ReduceRule.java @@ -0,0 +1,17 @@ +package com.iformall.utils.press; + +import java.math.BigDecimal; +import java.util.List; + +public interface ReduceRule { + + /** + * 获取砍价列表。 + * + * @param totalReduce 总共可砍价的金额 + * @param totalReduceTimes 总共可砍价的次数 + * @return 砍价列表 + */ + List getReduceList(BigDecimal totalReduce, int totalReduceTimes); + +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/SfReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/SfReduceRule.java new file mode 100644 index 000000000..8fe88993b --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/SfReduceRule.java @@ -0,0 +1,68 @@ +package com.iformall.utils.press; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 砍价的几个因素: + * 1. 可砍价的人数(N) + * 2. 可砍掉的总金额(T) + * 3. 每次砍价的最低砍价金额(M) + * 4. 总砍价次数(K) + *

+ * 本砍价规则将'每次砍价的最低砍价金额'默认为 := T / (K * 2)。 + */ +public class SfReduceRule implements ReduceRule { + + @Override + public List getReduceList(BigDecimal totalReduce, int totalReduceTimes) { + if (totalReduceTimes <= 0) { + throw new IllegalArgumentException("total reduce times should gte 1"); + } + + // 最低砍价金额默认为: 总砍价金额 / (砍价次数 * 2) + BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), BigDecimal.ROUND_HALF_EVEN); + return getReduceList(totalReduce, totalReduceTimes, minReduce); + } + + public List getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { + List ret = new ArrayList<>(); + Random rand = new Random(); + double[] vs = new double[totalReduceTimes]; + double total = 0d; + for (int i = 0; i < totalReduceTimes; i++) { + double v = rand.nextDouble() * totalReduce.doubleValue(); + vs[i] = v; + total += v; + } + + double minReduceVal = minReduce.doubleValue(); + BigDecimal t = BigDecimal.valueOf(0d); + for (int i = 0; i < totalReduceTimes - 1; i++) { + double vss = vs[i] / total * (totalReduce.doubleValue() - minReduceVal * totalReduceTimes) + minReduceVal; + BigDecimal reduce = BigDecimal.valueOf(vss).setScale(2, RoundingMode.HALF_EVEN); + t = t.add(reduce); + ret.add(reduce); + } + ret.add(totalReduce.subtract(t).setScale(2, RoundingMode.HALF_EVEN)); + return ret; + } + + + public static void main(String[] args) { + int t = new Random().nextInt(20) + 1; + int i = 0; + BigDecimal tt = BigDecimal.valueOf(new Random().nextDouble() * 10000 + 1).setScale(2, RoundingMode.HALF_UP); + List reduceList = new SfReduceRule().getReduceList(tt, t); + BigDecimal total = new BigDecimal(0d); + for (BigDecimal d : reduceList) { + total = total.add(d); + System.out.printf("第%d个人砍掉了%s\n", ++i, d); + } + total = total.setScale(2, RoundingMode.HALF_UP); + System.out.println("砍价总金额=" + tt + ",合计总金额=" + total); + } +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/stateless/DefaultStatelessReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/stateless/DefaultStatelessReduceRule.java new file mode 100644 index 000000000..0cf2a7238 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/stateless/DefaultStatelessReduceRule.java @@ -0,0 +1,26 @@ +package com.iformall.utils.press.stateless; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Random; + +public class DefaultStatelessReduceRule implements StatelessReduceRule { + + @Override + public BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftReduce, int totalReduceTimes, int leftReduceTimes, int scale) { + if (leftReduceTimes <= 0) { + throw new IllegalArgumentException("leftReduceTimes should gte 1"); + } + if (leftReduceTimes == 1) { + return leftReduce; + } + + BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), RoundingMode.HALF_EVEN); + double min = minReduce.doubleValue(); + double max = leftReduce.doubleValue() - min * leftReduceTimes; + double adjustedMax = min + (leftReduceTimes > 10 ? max / 3 : max); + double ratio = new Random().nextDouble(); + return BigDecimal.valueOf(min + (adjustedMax - min) * ratio).setScale(scale, RoundingMode.HALF_EVEN); + } + +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessPingReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessPingReduceRule.java new file mode 100644 index 000000000..fa943411b --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessPingReduceRule.java @@ -0,0 +1,37 @@ +package com.iformall.utils.press.stateless; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 本砍价规则默认为:前1/3的人砍掉总砍价金额的一半,剩余的2/3的人瓜分另外一半总砍价金额。 + */ +public class StatelessPingReduceRule implements StatelessReduceRule { + + private DefaultStatelessReduceRule rule; + + public StatelessPingReduceRule() { + this.rule = new DefaultStatelessReduceRule(); + } + + public BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftTotalReduce, int totalReduceTimes, int leftReduceTimes, int scale) { + if (totalReduceTimes == 1) { + return totalReduce; + } + + int firstN = Math.max(totalReduceTimes / 3, 1); // 前1/3的人 + BigDecimal halfReduce = totalReduce.divide(BigDecimal.valueOf(2), RoundingMode.HALF_EVEN).setScale(scale, RoundingMode.HALF_EVEN); + int leftN = totalReduceTimes - firstN; + BigDecimal theOtherHalf = totalReduce.subtract(halfReduce); + + if (totalReduceTimes - leftReduceTimes < firstN) { + // 前1/3的人砍掉一半 + BigDecimal leftTotal = halfReduce.subtract(totalReduce.subtract(leftTotalReduce)); + return rule.getReduce(halfReduce, leftTotal, firstN, firstN - (totalReduceTimes - leftReduceTimes), scale); + } else { + // 剩下的人砍掉一半 + return rule.getReduce(theOtherHalf, leftTotalReduce, leftN, leftReduceTimes, scale); + } + } + +} diff --git a/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessReduceRule.java b/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessReduceRule.java new file mode 100644 index 000000000..9d2efa821 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessReduceRule.java @@ -0,0 +1,20 @@ +package com.iformall.utils.press.stateless; + +import java.math.BigDecimal; + +/** + * 该接口表示无状态的砍价规则,每次调用只生成当次的砍价金额。 + * 记录总砍价次数、剩余的可砍价次数、总砍价金额以及剩余的可砍价金额是应用应该考虑的问题。 + */ +public interface StatelessReduceRule { + /** + * 获取砍价列表。 + * + * @param totalReduce 总共可砍价的金额 + * @param leftReduce 剩下的可砍价金额,首次调用时 totalReduce 应该等于 leftReduce + * @param totalReduceTimes 总共可砍价的次数 + * @param leftReduceTimes 当前剩下的砍价次数,首次调用时 totalReduceTimes 应该等于 leftReduceTimes + * @return 砍价列表 + */ + BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftReduce, int totalReduceTimes, int leftReduceTimes, int scale); +}