Просмотр исходного кода

[砍价][新增]:砍价算法添加

release_toaliyun_real
Stormeye Wu 7 лет назад
Родитель
Сommit
2f4c41777a
11 измененных файлов: 409 добавлений и 0 удалений
  1. +12
    -0
      mallinkService/src/main/java/com/iformall/domain/po/WxOrder.java
  2. +39
    -0
      mallinkService/src/main/java/com/iformall/service/impl/WxOrderServiceImpl.java
  3. +15
    -0
      mallinkService/src/main/java/com/iformall/utils/DateUtils.java
  4. +52
    -0
      mallinkService/src/main/java/com/iformall/utils/PressUtils.java
  5. +63
    -0
      mallinkService/src/main/java/com/iformall/utils/press/PingDuoDuoReduceRule.java
  6. +60
    -0
      mallinkService/src/main/java/com/iformall/utils/press/RecursiveReduceRule.java
  7. +17
    -0
      mallinkService/src/main/java/com/iformall/utils/press/ReduceRule.java
  8. +68
    -0
      mallinkService/src/main/java/com/iformall/utils/press/SfReduceRule.java
  9. +26
    -0
      mallinkService/src/main/java/com/iformall/utils/press/stateless/DefaultStatelessReduceRule.java
  10. +37
    -0
      mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessPingReduceRule.java
  11. +20
    -0
      mallinkService/src/main/java/com/iformall/utils/press/stateless/StatelessReduceRule.java

+ 12
- 0
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") @io.swagger.annotations.ApiModelProperty(value="砍价人数",name="pressCurrentNum")
private Integer pressCurrentNum; private Integer pressCurrentNum;
/**已砍价格**/
@io.swagger.annotations.ApiModelProperty(value="已砍价格",name="pressCurrentValue")
private Integer pressCurrentValue;





public Long getOrderNumber() { public Long getOrderNumber() {
@@ -214,6 +218,14 @@ public class WxOrder implements Serializable {
this.pressCurrentNum = pressCurrentNum; this.pressCurrentNum = pressCurrentNum;
} }


public Integer getPressCurrentValue() {
return pressCurrentValue;
}

public void setPressCurrentValue(Integer pressCurrentValue) {
this.pressCurrentValue = pressCurrentValue;
}

public static enum Field public static enum Field
{ {
Id_ASC("`id` ASC"),Id_DESC("`id` DESC") Id_ASC("`id` ASC"),Id_DESC("`id` DESC")


+ 39
- 0
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.mapper.*;
import com.iformall.service.*; import com.iformall.service.*;
import com.iformall.utils.DateUtils; import com.iformall.utils.DateUtils;
import com.iformall.utils.PressUtils;
import com.iformall.utils.RedisLock; import com.iformall.utils.RedisLock;
import com.iformall.utils.Utility; import com.iformall.utils.Utility;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -98,6 +99,9 @@ public class WxOrderServiceImpl implements WxOrderService {
@Autowired @Autowired
WxCouponSendService wxCouponSendService; WxCouponSendService wxCouponSendService;


@Autowired
WxOrderPressMapper wxOrderPressMapper;

@Override @Override
public PageInfo<WxOrder> listAsPage(WxOrder record, Integer pageIndex, Integer pageSize) { public PageInfo<WxOrder> listAsPage(WxOrder record, Integer pageIndex, Integer pageSize) {
return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxOrderMapper.findList(record)); return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxOrderMapper.findList(record));
@@ -338,9 +342,18 @@ public class WxOrderServiceImpl implements WxOrderService {
// 减库存操作 // 减库存操作
stockReduce(user, coupon, couponIdStr); stockReduce(user, coupon, couponIdStr);


boolean isPress = false;
if (coupon.getType().equals(EnumCouponType.COUPON_PRESS.getCode())) {
isPress = true;
}

Date curr = new Date(); Date curr = new Date();


int payment = coupon.getSalePrice(); int payment = coupon.getSalePrice();
if (isPress) {
// 砍价券
payment = coupon.getPrice();
}


final IdWorker idWorker = IdWorker.get(); final IdWorker idWorker = IdWorker.get();
Long orderNumber = idWorker.nextId(); Long orderNumber = idWorker.nextId();
@@ -370,6 +383,30 @@ public class WxOrderServiceImpl implements WxOrderService {
record.setDetail(bodyStr); record.setDetail(bodyStr);
record.setCreateDate(curr); record.setCreateDate(curr);
record.setUpdateDate(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 { try {
// 保存订单 // 保存订单
@@ -385,6 +422,8 @@ public class WxOrderServiceImpl implements WxOrderService {
try { try {
wxCouponActionLogService.addOne(user.getTenantId(), couponId, orderNumber, EnumCouponSendSendType.ACTIVE.getCode(), couponChannelId); wxCouponActionLogService.addOne(user.getTenantId(), couponId, orderNumber, EnumCouponSendSendType.ACTIVE.getCode(), couponChannelId);
} catch (Exception e) { } catch (Exception e) {
// 库存恢复
stockBack(record);
logger.error("保存订单:" + e.getMessage()); logger.error("保存订单:" + e.getMessage());
throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); throw new MallinkException(ErrorCode.ORDER_SAVE_ERR);
} }


+ 15
- 0
mallinkService/src/main/java/com/iformall/utils/DateUtils.java Просмотреть файл

@@ -371,6 +371,21 @@ public class DateUtils {
return mDate; 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 * 获得指定时间前几天,格式yyyy-MM-dd
* *


+ 52
- 0
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;
}

}

+ 63
- 0
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<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);
}
}

+ 60
- 0
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)
* <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);
}
}

+ 17
- 0
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<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes);

}

+ 68
- 0
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)
* <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);
}
}

+ 26
- 0
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);
}

}

+ 37
- 0
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);
}
}

}

+ 20
- 0
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);
}

Загрузка…
Отмена
Сохранить