1.表结构:
drop table T_LOTTERY_MANAGEMENT cascade constraints;
/*==============================================================*/
/* Table: T_LOTTERY_MANAGEMENT 抽奖活动管理 */
/*==============================================================*/
create table T_LOTTERY_MANAGEMENT
(
LOTTERY_ID VARCHAR2(50) not null,
LOTTERY_NAME VARCHAR2(50),
SPONSOR_POINT INT,
LOTTERY_TYPE INT,
LOTTERY_POINT INT,
START_DATE DATE,
STOP_DATE DATE,
LOTTERY_TOTAIL_NUM INT,
LOTTERY_SURPLUS_NUM INT,
LOTTERY_NUM INT,
LOTTERY_OVER INT,
CREATE_TIME DATE,
constraint PK_T_LOTTERY_MANAGEMENT primary key (LOTTERY_ID)
);
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_ID is
'抽奖活动编号';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_NAME is
'活动名称';
comment on column T_LOTTERY_MANAGEMENT.SPONSOR_POINT is
'公司赞助积分';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_TYPE is
'公司赞助(0否1是)';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_POINT is
'每次抽奖消耗积分';
comment on column T_LOTTERY_MANAGEMENT.START_DATE is
'活动开始时间';
comment on column T_LOTTERY_MANAGEMENT.STOP_DATE is
'活动结束时间';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_TOTAIL_NUM is
'抽奖总次数';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_SURPLUS_NUM is
'抽奖剩余次数';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_NUM is
'已抽奖次数';
comment on column T_LOTTERY_MANAGEMENT.LOTTERY_OVER is
'抽奖次数是否耗尽(0未耗尽1已耗尽)';
comment on column T_LOTTERY_MANAGEMENT.CREATE_TIME is
'创建时间';
drop table T_PRIZE cascade constraints;
/*==============================================================*/
/* Table: T_PRIZE 奖品表 */
/*==============================================================*/
create table T_PRIZE
(
ID INT not null,
LOTTERY_ID VARCHAR(50),
PRIZE_TYPE INT,
CLASS_ID INT,
PRODUCT_ID VARCHAR(50),
PRODUCT_ATTRIBUTES_ID VARCHAR(50),
PRIZE_NAME VARCHAR(50),
PRIZE_PRICE DECIMAL(7,2),
POINT INT,
PRIZE_NUM INT,
PRIZE_TOTAIL_POINT INT,
PRIZE_LEV INT,
PRIZE_PROBABILITY DECIMAL(6,4),
REMARKS CLOB,
CREATE_TIME DATE,
constraint PK_T_PRIZE primary key (ID)
);
comment on column T_PRIZE.ID is
'自增ID';
comment on column T_PRIZE.LOTTERY_ID is
'抽奖活动编号';
comment on column T_PRIZE.PRIZE_TYPE is
'奖品类型';
comment on column T_PRIZE.CLASS_ID is
'虚拟奖品分类ID';
comment on column T_PRIZE.PRODUCT_ID is
'虚拟奖品ID';
comment on column T_PRIZE.PRODUCT_ATTRIBUTES_ID is
'奖品属性ID';
comment on column T_PRIZE.PRIZE_NAME is
'奖品名称';
comment on column T_PRIZE.PRIZE_PRICE is
'剩余价值';
comment on column T_PRIZE.POINT is
'单个奖品价值(能力豆)';
comment on column T_PRIZE.PRIZE_NUM is
'奖品数量';
comment on column T_PRIZE.PRIZE_TOTAIL_POINT is
'奖品总价值(能力豆)';
comment on column T_PRIZE.PRIZE_LEV is
'奖品等级';
comment on column T_PRIZE.PRIZE_PROBABILITY is
'中奖概率';
comment on column T_PRIZE.REMARKS is
'备注';
comment on column T_PRIZE.CREATE_TIME is
'创建时间';
drop table T_WINNING_RECORD cascade constraints;
/*==============================================================*/
/* Table: T_WINNING_RECORD 中奖记录表 */
/*==============================================================*/
create table T_WINNING_RECORD
(
ID INT not null,
PRIZE_ID VARCHAR(50),
PRIZE_NAME VARCHAR(50),
USER_ID VARCHAR(50),
USER_NAME VARCHAR(50),
PRIZE_NUM INT,
CREATE_TIME DATE,
FIELD1 VARCHAR2(255),
FIELD2 VARCHAR2(255),
FIELD3 VARCHAR2(255),
constraint PK_T_WINNING_RECORD primary key (ID)
);
comment on column T_WINNING_RECORD.ID is
'自增ID';
comment on column T_WINNING_RECORD.PRIZE_ID is
'奖品ID';
comment on column T_WINNING_RECORD.PRIZE_NAME is
'奖品名称';
comment on column T_WINNING_RECORD.USER_ID is
'中奖用户ID';
comment on column T_WINNING_RECORD.USER_NAME is
'中奖用户名称';
comment on column T_WINNING_RECORD.PRIZE_NUM is
'奖品数量';
comment on column T_WINNING_RECORD.CREATE_TIME is
'中奖时间';
comment on column T_WINNING_RECORD.FIELD1 is
'备用字段1';
comment on column T_WINNING_RECORD.FIELD2 is
'备用字段2';
comment on column T_WINNING_RECORD.FIELD3 is
'备用字段3';
2.概率规则:
单个产品数量/总产品数量=单个产品概率。
每个产品概率相加=1,如果没有等于1数据有问题。
抽奖用户分不同lev等级,可以根据用户不同的lev来限制用户每天或每轮活动的抽奖次数。
3.设计思路:
本次补充方案和上一次的方案变化非常大(基本推翻重干。。。)
首先我们从一个抽奖活动,变成了多个抽奖活动,每个活动分为两种模式(赞助和非赞助)非赞助:抽奖次数 X 每次抽奖的积分 = 奖品的数量X奖品的价值;赞助:奖品的数量 X 奖品的价值 - 抽奖次数 X 每次抽奖的积分 = 公司赞助的积分;活动与活动之间的时间段是不能相交叉的,这就保证了我们在一个时间点最多只有一个抽奖活动在推广生效。每个活动有自己专属的活动奖品和自己的抽奖限制次数规则。基于上述规则,我从最小数 Min = 1,最大数 Max = 抽奖总次数,这个范围生成了有效的奖品总数量的随机数做奖品唯一兑换序列码。因为兑换码就在MIN ~ MAX 范围里面,用户每次抽奖我都会用一个活动特定标识(我这里用的是活动的ID,和redis单线程的特性)去自增1,然后拿自增后的数数去匹配兑换序列码,判断用户是否中奖,同时能判断本轮活动是否已经结果,同时技术要求和逻辑也没上一篇那么复杂,一举多得。
页面效果如下:
抽奖活动列表:
抽奖活动奖品列表:
5.注意事项:
二、参考代码
package com.rfg.shop;
public class Prize extends Object {
//奖品ID
private String id;
//唯一兑换序列码
private String val;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getVal() {
return val;
}
public void setVal(String val) {
this.val = val;
}
Prize(String id, String val) {
super();
this.id = id;
this.val = val;
}
Prize() {
super();
}
@Override
public int hashCode() {
// System.err.println(this + "..........hashCode=》" + val.hashCode());
return val.hashCode();
}
@Override
public boolean equals(Object obj) {
// System.err.println(this + "===========" + obj);
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Prize other = (Prize) obj;
if (val == null) {
if (other.val != null)
return false;
} else if (!val.equals(other.val))
return false;
return true;
}
@Override
public String toString() {
return id + ":" + val;
}
}
package com.rfg.shop;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import com.eos.system.annotation.Bizlet;
import commonj.sdo.DataObject;
/**
*
* @author XiongYC
*
*/
public class Common {
public static final String PRIZE_RULE = "prizeRule";
public static Jedis jedis = RedisPoolUtils.getJedis();
/**
* 计算总价值和中奖概率
* @param arr 奖品数组
* @param data 抽奖活动obj
* @return
*/
@Bizlet
public static HashMap calculation(DataObject[] arr ,DataObject data) {
HashMap map = new HashMap();
//本次抽奖活动每次抽奖消耗能量豆、
int lotteryPoint = data.getInt("lotteryPoint");
//本轮抽奖活动建构总价值能量豆
int tailNum = 0;
//本轮抽奖公司赞助能量豆
int sponsorPoint = 0;
//非赞助本轮抽奖总次数
int lotteryTotailNum_F = 0;
//非赞助本轮抽奖总次数
int lotteryTotailNum_T = 0;
//纪律本轮循环谢谢惠顾的下标
int index = 0;
//非赞助本轮抽奖总次数
int NotZeroPointPrizeNum = 0;
for (int i = 0; i < arr.length; i++) {
//单个奖品的数量
int prizeNum = arr[i].getInt("prizeNum");
lotteryTotailNum_T +=prizeNum;
//计算单个奖品总价值能量豆
int a = prizeNum * arr[i].getInt("point");
//本轮次抽奖活动建构总价值能量豆
tailNum += a;
if (a>0) {
//纪律有效间奖品数量
NotZeroPointPrizeNum+=prizeNum;
} else {
index = i;
}
arr[i].set("prizeTotailPoint",a );
}
lotteryTotailNum_F = tailNum/lotteryPoint;
//0非赞助1赞助
if (0 == data.getInt("lotteryType")) {
//
data.setLong("lotteryTotailNum", lotteryTotailNum_F);
arr[index].setInt("prizeNum", lotteryTotailNum_F - NotZeroPointPrizeNum);
} else {
data.setLong("lotteryTotailNum", lotteryTotailNum_T);
sponsorPoint = (lotteryTotailNum_F - lotteryTotailNum_T)*lotteryPoint;
}
//计算每个奖品的概率
for (int i = 0; i < arr.length; i++) {
// //单个奖品的数量
int prizeNum = arr[i].getInt("prizeNum");
//精确到小数点后4位
BigDecimal d = new BigDecimal(prizeNum).divide(new BigDecimal(lotteryTotailNum_T), 4, BigDecimal.ROUND_HALF_UP);
// //本轮次抽奖活动建构总价值能量豆
arr[i].setBigDecimal("prizeProbability",d);
}
data.setLong("sponsorPoint", sponsorPoint);
//初始化
data.setLong("lotteryNum", 0);
data.setLong("lotteryOver", 0);
data.setLong("lotterySurplusNum", data.getInt("lotteryTotailNum"));
//奖品数组
map.put("arr", arr);
//奖品活动
map.put("data", data);
//缓存建构数据
CacheLottery.cachePrizeListByLotteryId(data.getString("lotteryId"), data.getInt("lotteryTotailNum"), arr);
return map;
}
/**
* 缓存抽奖规则
* @param arr
* @return
*/
@Bizlet
public static int ruleList(Rule[] arr ,String lotteryId ) {
if(jedis == null){
jedis = RedisPoolUtils.getJedis();
}
try {
Pipeline p = jedis.pipelined();
p.del(PRIZE_RULE+lotteryId);
for (int i = 0; i < arr.length; i++) {
Rule rule = arr[i];
p.sadd(PRIZE_RULE+lotteryId, PRIZE_RULE+lotteryId+rule.getEngineerLevel());
p.set(PRIZE_RULE+lotteryId+rule.getEngineerLevel(), rule.toString());
}
p.sync();
} catch (Exception e) {
System.err.println("PRIZE_RULE_ERROR_MSG=======================>" + e.getMessage());
return 0;
} finally {
// RedisPoolUtils.returnResourceObject(jedis);
}
return 1;
}
/**
* 查询抽奖规则
* @param arr
* @return
*/
@Bizlet
public static Rule[] queryRuleList(String lotteryId) {
Rule[] ruleList = null;
try {
if(jedis == null){
jedis = RedisPoolUtils.getJedis();
}
Set sets = jedis.smembers(PRIZE_RULE + lotteryId);
Pipeline p = jedis.pipelined();
for (String str: sets) {
p.get(str);
}
List
package com.rfg.shop;
import java.util.HashSet;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import com.eos.system.annotation.Bizlet;
import commonj.sdo.DataObject;
/**
*
* @author XiongYC
*
*/
public class CacheLottery {
public static Jedis jedis = RedisPoolUtils.getJedis();
public static final String PRIZE_LIST = "prizeList";
public static final String PRIZE_ID = "prizeId";
public static void main(String[] args) {
HashSet set = new HashSet();
// set = randomSet(1, 2258, 2036, set);
System.err.println(set.size());
int index=0;
for (Integer integer : set) {
System.out.println("索引index="+ index+++"元素"+integer);
}
}
/**
* 缓存奖品信息
* @param lotteryId
* @param sponsorPoint
* @param arr
*/
@Bizlet
public static void cachePrizeListByLotteryId(String lotteryId,int sponsorPoint ,DataObject[] arr) {
try {
if(jedis == null){
jedis = RedisPoolUtils.getJedis();
}
//初始化本活动奖品抽奖次数
jedis.set(lotteryId, "0");
//奖品容器
HashSet set = new HashSet();
randomSet(1, sponsorPoint, set, arr);
Pipeline p = jedis.pipelined();
//修改清除本次奖品列表
p.del(PRIZE_LIST + lotteryId);
for (Prize obj : set) {
//缓存奖品随机码和奖品ID
p.zadd(PRIZE_LIST + lotteryId, Double.parseDouble(obj.getId()),obj.getVal());
}
p.sync();
} catch (Exception e) {
throw new RuntimeException("CACHE_PRIZE_LIST_ERROR_MSG");
} finally {
// RedisPoolUtils.returnResourceObject(jedis);
}
}
/**
* 随机指定范围内N个不重复的数
* 利用HashSet的特征,只能存放不同的值
* @param min 指定范围最小值
* @param max 指定范围最大值
* @param HashSet set 随机数结果集
* @param arr 奖品列表
* @return
*/
public static HashSet randomSet(int min, int max, HashSet set, DataObject[] arr) {
for (DataObject obj : arr) {
// 排除无效奖品
if (Integer.valueOf(String.valueOf(obj.get("point"))) > 0) {
// 被刺奖品的数量
int prizeNum = Integer.valueOf(String.valueOf(obj.get("prizeNum")));
// 记录本个奖品要停止生成的不重复的随机码的个数(hasHset数组大小 + 奖品个数)
int stopSize = set.size() + prizeNum;
// 生成指定建构数量的奖品唯一随机码
while (set.size() < stopSize) {
int num = (int) (Math.random() * (max - min)) + min;
set.add(new Prize(String.valueOf(obj.get("id")), num + ""));// 将不同的数存入HashSet中
}
}
}
return set;
}
/**
* 抽奖
* @param lotteryId
* @return
*/
@Bizlet
public static String luckDraw(String lotteryId,String engineerId) {
String prizeId = null;
try {
if (jedis == null) {
jedis = RedisPoolUtils.getJedis();
}
Long index = jedis.incr(lotteryId);
Double id = jedis.zscore(PRIZE_LIST + lotteryId, index.intValue()+"");
if(id != null){
prizeId = String.valueOf(id.intValue());
//设置每天
if(!jedis.exists(engineerId)){
jedis.incr(engineerId);
jedis.expire(engineerId, 60*60*24);
// jedis.expire(engineerId, 30);
}else {
jedis.incr(engineerId);
}
//每个轮询的次数
jedis.incr(lotteryId + engineerId);
//删除缓存奖品
jedis.zrem(PRIZE_LIST + lotteryId, index+"");
}
} catch (Exception e) {
throw new RuntimeException("CACHE_PRIZE_LIST_ERROR_MSG");
} finally {
// RedisPoolUtils.returnResourceObject(jedis);
}
return prizeId;
}
/**
* 保存抽奖记录
* @param lotteryId
* @param lotteryManagement
* @return
*/
@Bizlet
public static DataObject saveLotteryManagement(String lotteryId,DataObject lotteryManagement) {
try {
if (jedis == null) {
jedis = RedisPoolUtils.getJedis();
}
int lotteryNum = Integer.valueOf(jedis.get(lotteryId));
lotteryManagement.setLong("lotteryNum", lotteryNum);
int lotterySurplusNum = lotteryManagement.getInt("lotteryTotailNum") - lotteryNum;
lotteryManagement.setLong("lotterySurplusNum", lotterySurplusNum);
if(lotteryManagement.getInt("lotteryTotailNum") <= lotteryNum){
lotteryManagement.setLong("lotteryOver", 1);
}
} catch (Exception e) {
throw new RuntimeException("SAVE_LOTTERY_MANAGEMENT_ERROR_MSG");
} finally {
// RedisPoolUtils.returnResourceObject(jedis);
}
return lotteryManagement;
}
}
通过上图我们可以看到一个抽奖活动的一系列关键参数。奖品顺序的唯一性和随机性。通过前面一些我们可以核对一下这些数据都是可以匹配上的。以上就是全部内容了。有什么不足的或者不理解的地方还请在下面评论留言。