| @@ -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") | |||
| @@ -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<WxOrder> 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); | |||
| } | |||
| @@ -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 | |||
| * | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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<BigDecimal> 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<BigDecimal> 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<BigDecimal> 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); | |||
| } | |||
| } | |||
| @@ -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) | |||
| * <p> | |||
| * 本砍价规则将'每次砍价的最低砍价金额'默认为 := 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<BigDecimal> 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<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { | |||
| List<BigDecimal> ret = new ArrayList<>(); | |||
| doReduce(totalReduce, totalReduceTimes, minReduce, ret); | |||
| return ret; | |||
| } | |||
| private void doReduce(BigDecimal leftTotalReduce, int leftReduceTimes, BigDecimal minReduce, List<BigDecimal> 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); | |||
| } | |||
| } | |||
| @@ -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<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes); | |||
| } | |||
| @@ -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) | |||
| * <p> | |||
| * 本砍价规则将'每次砍价的最低砍价金额'默认为 := T / (K * 2)。 | |||
| */ | |||
| public class SfReduceRule implements ReduceRule { | |||
| @Override | |||
| public List<BigDecimal> 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<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { | |||
| List<BigDecimal> 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<BigDecimal> 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); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||