java 转盘抽奖概率可调

原创
2019/04/22 12:30
阅读数 276
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>

 

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部