package com.platform.service;
import com.platform.entity.LotteryConfigDO;
import com.platform.entity.LotteryDO;
import com.platform.entity.PartnerDO;
import com.platform.entity.UserAssetDO;
import com.platform.mapper.LotteryMapper;
import com.platform.util.BigDecimalUtil;
import com.platform.util.LotteryUtil;
import com.platform.vo.LotteryVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* @program:
* @Description:
* @Author: liweihai
* @Date: Created in 2019/4/16 20:00
*/
@Slf4j
@Service("lotteryService")
public class LotteryService {
@Autowired
LotteryMapper lotteryMapper;
@Autowired
LotteryConfigService lotteryConfigService;
@Autowired
LotteryRecordService lotteryRecordService;
@Autowired
private UserAssetService userAssetService;
@Autowired
private PartnerService partnerService;
@Transactional(rollbackFor = HtmlLotteryException.class)
public LotteryVO lucky(Long userid, UserAssetDO asset, LotteryConfigDO conf) {
//用户信息
PartnerDO user = partnerService.getPartnerById(userid);
//配置倍率
BigDecimal power = conf.getPower();
//消费
BigDecimal cost = conf.getNumber();
int type = conf.getType();
//奖品集合
List<LotteryDO> lotteries = lotteryMapper.selectList(type);
//抽奖
LotteryDO lottery = this.lotteryDraw(lotteries);
BigDecimal coin = BigDecimal.ZERO;
if (lottery.getIsSpc() > 0) {
coin = BigDecimalUtil.subtract(lottery.getPrice(), cost, 5);
asset.setSpcCoin(BigDecimalUtil.add(asset.getSpcCoin(), coin));
} else {
asset.setSpcCoin(BigDecimalUtil.subtract(asset.getSpcCoin(), cost,5));
}
//中奖纪录
lotteryRecordService.saveRecord(user, lottery, cost);
//修改用户资产(并发发会失败)
boolean result = userAssetService.updateCoinAccount(asset);
//添加消费记录
energySpcService.insert(userid, user.getMobile(), cost, userid, user.getMobile(), CoinTypeEnum.COIN_TYPE_GAME_OUT.getKey(), CoinTypeEnum.COIN_TYPE_GAME_OUT.getValue(),conf.getLogo(), 1);
//添加赚取记录
energySpcService.insert(userid, user.getMobile(), lottery.getPrice(), userid, user.getMobile(),CoinTypeEnum.COIN_TYPE_GAME_IN.getKey(), CoinTypeEnum.COIN_TYPE_GAME_IN.getValue(),conf.getLogo(), 0);
if (!result) {
throw new HtmlLotteryException("奖品飞走了");
}
LotteryVO vo = LotteryVO.builder().level(lottery.getLevel()).levelName(lottery.getLevelName()).lotteryName(lottery.getName()).name(user.getName()).price(lottery.getPrice()).build();
return vo;
}
public LotteryDO lotteryDraw(List<LotteryDO> list) {
/*
* 用于从分布中采样的随机数生成器
* */
final Random random = new Random();
/*
* 概率和别名表
**/
final int[] alias;
// private final double[] probability;
final BigDecimal[] probability1;
/*
* 参数检查
* */
if (list == null || random == null)
throw new NullPointerException();
if (list.size() == 0)
throw new IllegalArgumentException("Probability vector must be nonempty.");
final int size = list.size();
/*
* 为概率和别名表分配空间
**/
probability1 = new BigDecimal[size];
alias = new int[size];
/*
* 计算平均概率并将其缓存以供以后使用
**/
final BigDecimal average = BigDecimalUtil.divide(BigDecimal.ONE, BigDecimal.valueOf(size), 5);
List<BigDecimal> rates = list.stream().map(LotteryDO::getRate).collect(Collectors.toList());
/*
* 创建两个堆栈, 以便在填充表时充当工作列表.
**/
Deque<Integer> small = new ArrayDeque<Integer>();
Deque<Integer> large = new ArrayDeque<Integer>();
/*
* 使用输入概率填充堆栈
**/
for (int i = 0; i < size; ++i) {
/*
*如果概率低于平均概率, 那么我们添加
*它到小列表;否则我们会将其添加到大列表中
*/
if (rates.get(i).compareTo(average) >= 0)
large.add(i);
else
small.add(i);
}
while (!small.isEmpty() && !large.isEmpty()) {
/*
* 获取小概率和大概率的索引
* */
int less = small.removeLast();
int more = large.removeLast();
probability1[less] = BigDecimalUtil.multiply(rates.get(less), BigDecimal.valueOf(size), 5);
alias[less] = more;
BigDecimal difference = BigDecimalUtil.subtract(BigDecimalUtil.add(rates.get(more), rates.get(less)), average, 5);
rates.set(more, difference);
if (rates.get(more).compareTo(average) >= 0)
large.add(more);
else
small.add(more);
}
while (!small.isEmpty())
probability1[small.removeLast()] = BigDecimal.ONE;
while (!large.isEmpty())
probability1[large.removeLast()] = BigDecimal.ONE;
int column = random.nextInt(probability1.length);
boolean coinToss = BigDecimal.valueOf(random.nextDouble()).compareTo(probability1[column]) < 0;
int index = coinToss ? column : alias[column];
return list.get(index);
}
}
package com.platform.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
//奖品类
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LotteryDO implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;//奖品名称
private BigDecimal price;//价值
private Integer number;
private Integer type;
private Date beginDate;
private Date endDate;
private Integer level;//级别
private BigDecimal rate;//奖品概率
private String levelName;//级别名称
private Date createDate;
private Integer required;
private Integer isSpc;
}
package com.platform.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
// 抽奖游戏配置类
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LotteryConfigDO implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;//游戏名称
private BigDecimal number;// 消费数量
private Integer type;//类型
private Integer priority;//优先级或排序
private String user;// 指定用户
private String logo;// 游戏logo
private Integer level;//级别
private BigDecimal power;//倍率
private Date createDate;
private Integer isOpen;//是否开启
}
CREATE TABLE `lottery_config` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) DEFAULT NULL COMMENT '名称',
`number` decimal(10,4) DEFAULT NULL COMMENT '消费数量',
`type` int(2) DEFAULT NULL COMMENT '类型1',
`priority` int(11) DEFAULT NULL COMMENT '优先级',
`user` varchar(250) DEFAULT NULL COMMENT '用户编号逗号分隔',
`level` int(11) DEFAULT NULL COMMENT '中奖级别',
`power` int(11) DEFAULT NULL COMMENT '中奖倍率',
`is_open` int(1) DEFAULT NULL COMMENT '0否1是',
`logo` varchar(300) DEFAULT NULL COMMENT '游戏logo',
`url` varchar(300) DEFAULT NULL COMMENT 'html地址',
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COMMENT='抽奖配置'
CREATE TABLE `lottery` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) DEFAULT NULL COMMENT '名称',
`price` decimal(20,5) DEFAULT '0.00000' COMMENT '价格',
`number` int(10) DEFAULT NULL COMMENT '数量',
`type` int(2) DEFAULT NULL COMMENT '类型1spc',
`begin_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '开始',
`end_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '结束',
`level` int(11) DEFAULT NULL COMMENT '1,2,3,4,5,6,7,8...',
`rate` decimal(10,4) DEFAULT NULL COMMENT '概率',
`level_name` varchar(50) DEFAULT NULL COMMENT '奖品级别名称',
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`required` int(1) DEFAULT '1' COMMENT '0否1是',
`is_spc` int(1) DEFAULT NULL COMMENT '是否spc0否1是',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8 COMMENT='奖品表'
CREATE TABLE `lottery_record` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) DEFAULT NULL COMMENT '用户',
`uid` bigint(11) DEFAULT NULL COMMENT '用户编号',
`phone` varchar(11) DEFAULT NULL COMMENT '手机',
`lottery_id` int(11) DEFAULT NULL COMMENT '奖品编号',
`lottery_name` varchar(50) DEFAULT NULL COMMENT '奖品名称',
`level` int(2) DEFAULT NULL COMMENT '中奖级别',
`level_name` varchar(50) DEFAULT NULL COMMENT '奖品名称',
`type` int(2) DEFAULT NULL COMMENT '类型',
`price` decimal(20,5) DEFAULT NULL COMMENT '价格',
`spend` decimal(20,5) DEFAULT NULL COMMENT '花费',
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8107 DEFAULT CHARSET=utf8 COMMENT='抽奖记录'
<?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="com.platform.mapper.LotteryMapper">
<resultMap type="com.platform.entity.LotteryDO" id="resultMap">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
<result property="number" column="number"/>
<result property="type" column="type"/>
<result property="beginDate" column="begin_date"/>
<result property="endDate" column="end_date"/>
<result property="level" column="level"/>
<result property="rate" column="rate"/>
<result property="levelName" column="level_name"/>
<result property="createDate" column="create_dete"/>
<result property="required" column="required"/>
<result property="isSpc" column="is_spc"/>
</resultMap>
<select id="selectList" resultMap="resultMap">
select
`id`,
`name`,
`price`,
`number`,
`type`,
`begin_date`,
`end_date`,
`level`,
`rate`,
`level_name`,
`create_date`,
`required`,
is_spc
FROM lottery
WHERE required=1 and `type` =#{type}
</select>
</mapper>
<mapper namespace="com.platform.mapper.LotteryConfigMapper">
<resultMap type="com.platform.entity.LotteryConfigDO" id="resultMap">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="number" column="number"/>
<result property="type" column="type"/>
<result property="priority" column="priority"/>
<result property="user" column="user"/>
<result property="level" column="level"/>
<result property="logo" column="logo"/>
<result property="power" column="power"/>
<result property="createDate" column="create_date"/>
<result property="isOpen" column="is_open"/>
</resultMap>
<select id="selectLotteryConfig" resultMap="resultMap">
select
*
from lottery_config
where type = #{type} limit 1
</select>
</mapper>