Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
KnowledgePlanet
road-map
xfg-frame-archetype-lite
提交
f440f6b3
xfg-frame-archetype-lite
项目概览
KnowledgePlanet
/
road-map
/
xfg-frame-archetype-lite
通知
1731
Star
86
Fork
101
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
xfg-frame-archetype-lite
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
f440f6b3
编写于
1月 23, 2025
作者:
鲸落和鲨掉
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
修改抽奖策略,将抽奖分为抽奖策略抽奖类:概率装配接口、抽奖调度接口 以及 抽奖策略算法实现类依赖抽奖算法抽象类
上级
133be328
变更
11
隐藏空白更改
内联
并排
Showing
11 changed file
with
494 addition
and
189 deletion
+494
-189
bigmarket-app/src/main/resources/mybatis/mapper/strategy_award_mapper.xml
...c/main/resources/mybatis/mapper/strategy_award_mapper.xml
+4
-0
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java
...stack/domain/strategy/repository/IStrategyRepository.java
+25
-1
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/AbstractStrategyAlgorithm.java
...in/strategy/service/armory/AbstractStrategyAlgorithm.java
+130
-0
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/StrategyArmoryDispatch.java
...omain/strategy/service/armory/StrategyArmoryDispatch.java
+36
-184
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/AbstractAlgorithm.java
.../strategy/service/armory/algorithm/AbstractAlgorithm.java
+31
-0
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/IAlgorithm.java
.../domain/strategy/service/armory/algorithm/IAlgorithm.java
+16
-0
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/impl/O1Algorithm.java
...n/strategy/service/armory/algorithm/impl/O1Algorithm.java
+55
-0
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/impl/OLogNAlgorithm.java
...trategy/service/armory/algorithm/impl/OLogNAlgorithm.java
+158
-0
bigmarket-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyAwardDao.java
...tack/infrastructure/persistent/dao/IStrategyAwardDao.java
+1
-1
bigmarket-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java
...rastructure/persistent/repository/StrategyRepository.java
+37
-3
bigmarket-types/src/main/java/cn/bugstack/types/common/Constants.java
...pes/src/main/java/cn/bugstack/types/common/Constants.java
+1
-0
未找到文件。
bigmarket-app/src/main/resources/mybatis/mapper/strategy_award_mapper.xml
浏览文件 @
f440f6b3
...
@@ -46,5 +46,9 @@
...
@@ -46,5 +46,9 @@
from strategy_award
from strategy_award
where strategy_id = #{strategyId} and award_id = #{awardId}
where strategy_id = #{strategyId} and award_id = #{awardId}
</select>
</select>
<select
id=
"queryOpenActivityStrategyAwardList"
resultMap=
"dataMap"
>
select strategy_id, award_id from strategy_award
where strategy_id in (select strategy_id from raffle_activity where state = 'open')
</select>
</mapper>
</mapper>
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/repository/IStrategyRepository.java
浏览文件 @
f440f6b3
...
@@ -21,12 +21,14 @@ public interface IStrategyRepository {
...
@@ -21,12 +21,14 @@ public interface IStrategyRepository {
List
<
StrategyAwardEntity
>
queryStrategyAwardList
(
Long
strategyId
);
List
<
StrategyAwardEntity
>
queryStrategyAwardList
(
Long
strategyId
);
void
storeStrategyAwardSearchRateTable
(
String
key
,
Integer
rateRange
,
Map
<
Integer
,
Integer
>
strategyAwardSearchRateTable
);
<
K
,
V
>
void
storeStrategyAwardSearchRateTable
(
String
key
,
Integer
rateRange
,
Map
<
K
,
V
>
strategyAwardSearchRateTable
);
<
K
,
V
>
Map
<
K
,
V
>
getMap
(
String
key
);
Integer
getStrategyAwardAssemble
(
String
key
,
Integer
rateKey
);
Integer
getStrategyAwardAssemble
(
String
key
,
Integer
rateKey
);
int
getRateRange
(
Long
strategyId
);
int
getRateRange
(
Long
strategyId
);
int
getRateRange
(
String
key
);
int
getRateRange
(
String
key
);
StrategyEntity
queryStrategyEntityByStrategyId
(
Long
strategyId
);
StrategyEntity
queryStrategyEntityByStrategyId
(
Long
strategyId
);
...
@@ -142,5 +144,27 @@ public interface IStrategyRepository {
...
@@ -142,5 +144,27 @@ public interface IStrategyRepository {
* @return 权重规则
* @return 权重规则
*/
*/
List
<
RuleWeightVO
>
queryAwardRuleWeight
(
Long
strategyId
);
List
<
RuleWeightVO
>
queryAwardRuleWeight
(
Long
strategyId
);
/**
* 查询有效活动的奖品配置
*
* @return 奖品配置列表
*/
List
<
StrategyAwardStockKeyVO
>
queryOpenActivityStrategyAwardList
();
/**
* 存储抽奖策略对应的Bean算法
*
* @param key 策略ID
* @param beanName 策略对象名称
*/
void
cacheStrategyArmoryAlgorithm
(
String
key
,
String
beanName
);
/**
* 获取存储抽奖策略对应的Bean算法
*
* @param key 策略ID
* @return 策略对象名称
*/
String
queryStrategyArmoryAlgorithmFromCache
(
String
key
);
}
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/AbstractStrategyAlgorithm.java
0 → 100644
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
cn.bugstack.domain.strategy.model.entity.StrategyEntity
;
import
cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity
;
import
cn.bugstack.domain.strategy.repository.IStrategyRepository
;
import
cn.bugstack.types.common.Constants
;
import
cn.bugstack.types.enums.ResponseCode
;
import
cn.bugstack.types.exception.AppException
;
import
javax.annotation.Resource
;
import
java.math.BigDecimal
;
import
java.security.SecureRandom
;
import
java.util.*
;
/**
* @ClassName: AbstractStrageyAlgorithm
* @Description: 抽奖策略算法抽象类
* @Author: zhaoyongfeng
* @Date: 2025/1/23 22:22
*/
public
abstract
class
AbstractStrategyAlgorithm
implements
IStrategyArmory
,
IStrategyDispatch
{
@Resource
protected
IStrategyRepository
repository
;
protected
final
SecureRandom
secureRandom
=
new
SecureRandom
();
@Override
public
boolean
assembleLotteryStrategyByActivityId
(
Long
activityId
){
Long
strategyId
=
repository
.
queryStrategyIdByActivityId
(
activityId
);
return
assembleLotteryStrategy
(
strategyId
);
}
@Override
public
boolean
assembleLotteryStrategy
(
Long
strategyId
){
// 1.查询策略配置
List
<
StrategyAwardEntity
>
strategyAwardEntities
=
repository
.
queryStrategyAwardList
(
strategyId
);
// 2. 缓存奖品库存【用于decr扣减库存使用】
for
(
StrategyAwardEntity
strategyAward
:
strategyAwardEntities
)
{
Integer
awardId
=
strategyAward
.
getAwardId
();
Integer
awardCountSurplus
=
strategyAward
.
getAwardCountSurplus
();
cacheStrategyAwardCount
(
strategyId
,
awardId
,
awardCountSurplus
);
}
// 3.1 默认装配配置【全量抽奖概率】
armoryAlgorithm
(
String
.
valueOf
(
strategyId
),
strategyAwardEntities
);
// 3.2 权重策略配置 - 适用于 rule_weight 权重规则配置【4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109】
StrategyEntity
strategyEntity
=
repository
.
queryStrategyEntityByStrategyId
(
strategyId
);
String
ruleWeight
=
strategyEntity
.
getRuleWeight
();
// 为空抛出异常,设定异常
if
(
null
==
ruleWeight
)
return
true
;
StrategyRuleEntity
strategyRuleEntity
=
repository
.
queryStrategyRule
(
strategyId
,
ruleWeight
);
// 业务异常,策略规则中 rule_weight 权重规则已适用但未配置
if
(
null
==
strategyRuleEntity
){
throw
new
AppException
(
ResponseCode
.
STRATEGY_RULE_WEIGHT_IS_NULL
.
getCode
(),
ResponseCode
.
STRATEGY_RULE_WEIGHT_IS_NULL
.
getInfo
());
}
Map
<
String
,
List
<
Integer
>>
ruleWeightValueMap
=
strategyRuleEntity
.
getRuleWeightValues
();
for
(
String
key
:
ruleWeightValueMap
.
keySet
())
{
List
<
Integer
>
ruleWeightValues
=
ruleWeightValueMap
.
get
(
key
);
ArrayList
<
StrategyAwardEntity
>
strategyAwardEntitiesClone
=
new
ArrayList
<>(
strategyAwardEntities
);
strategyAwardEntitiesClone
.
removeIf
(
entity
->
!
ruleWeightValues
.
contains
(
entity
.
getAwardId
()));
armoryAlgorithm
(
String
.
valueOf
(
strategyId
).
concat
(
Constants
.
UNDERLINE
).
concat
(
key
),
strategyAwardEntitiesClone
);
}
return
true
;
}
/*
* @description: 装配算法
* @param: key 为策略ID、权重ID、strategyAwardEntities 对应的奖品概率
* @return: void
* @author zhaoyongfeng
* @date: 2025/1/23 23:02
*/
protected
abstract
void
armoryAlgorithm
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
);
/*
* @description: 缓存奖品库存到Redis
* @param: strategyId 策略ID awardId 奖品ID awardCount 奖品库存
* @author zhaoyongfeng
* @date: 2025/1/23 23:04
*/
private
void
cacheStrategyAwardCount
(
Long
strategyId
,
Integer
awardId
,
Integer
awardCount
){
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_AWARD_COUNT_KEY
+
strategyId
+
Constants
.
UNDERLINE
+
awardId
;
repository
.
cacheStrategyAwardCount
(
cacheKey
,
awardCount
);
}
protected
abstract
Integer
dispatchAlgorithm
(
String
key
);
@Override
public
Integer
getRandomAwardId
(
Long
strategyId
)
{
return
dispatchAlgorithm
(
String
.
valueOf
(
strategyId
));
}
@Override
public
Integer
getRandomAwardId
(
Long
strategyId
,
String
ruleWeightValue
)
{
String
key
=
String
.
valueOf
(
strategyId
).
concat
(
Constants
.
UNDERLINE
).
concat
(
ruleWeightValue
);
return
getRandomAwardId
(
key
);
}
@Override
public
Integer
getRandomAwardId
(
String
key
)
{
return
dispatchAlgorithm
(
key
);
}
@Override
public
Boolean
subtractionAwardStock
(
Long
strategyId
,
Integer
awardId
,
Date
endDateTime
)
{
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_AWARD_COUNT_KEY
+
strategyId
+
Constants
.
UNDERLINE
+
awardId
;
return
repository
.
subtractionAwardStock
(
cacheKey
,
endDateTime
);
}
// 概率最小值
protected
BigDecimal
minAwardRate
(
List
<
StrategyAwardEntity
>
strategyAwardEntities
){
return
strategyAwardEntities
.
stream
()
.
map
(
StrategyAwardEntity:
:
getAwardRate
)
.
min
(
BigDecimal:
:
compareTo
)
.
orElse
(
BigDecimal
.
ZERO
);
}
// 概率范围值,百分位、千分位、万分位
protected
double
convert
(
double
min
)
{
if
(
0
==
min
)
return
1
D
;
String
minStr
=
String
.
valueOf
(
min
);
// 小数点前
String
beginVale
=
minStr
.
substring
(
0
,
minStr
.
indexOf
(
"."
));
int
beginLength
=
0
;
if
(
Double
.
parseDouble
(
beginVale
)
>
0
)
{
beginLength
=
minStr
.
substring
(
0
,
minStr
.
indexOf
(
"."
)).
length
();
}
// 小数点后
String
endValue
=
minStr
.
substring
(
minStr
.
indexOf
(
"."
)
+
1
);
int
endLength
=
0
;
if
(
Double
.
parseDouble
(
endValue
)
>
0
)
{
endLength
=
minStr
.
substring
(
minStr
.
indexOf
(
"."
)
+
1
).
length
();
}
return
Math
.
pow
(
10
,
beginLength
+
endLength
);
}
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/StrategyArmoryDispatch.java
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory
;
package
cn.bugstack.domain.strategy.service.armory
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
cn.bugstack.domain.strategy.model.entity.StrategyEntity
;
import
cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm
;
import
cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity
;
import
cn.bugstack.domain.strategy.service.armory.algorithm.IAlgorithm
;
import
cn.bugstack.domain.strategy.repository.IStrategyRepository
;
import
cn.bugstack.types.common.Constants
;
import
cn.bugstack.types.enums.ResponseCode
;
import
cn.bugstack.types.exception.AppException
;
import
lombok.extern.slf4j.Slf4j
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.math.BigDecimal
;
import
java.math.BigDecimal
;
import
java.
security.SecureRandom
;
import
java.
util.List
;
import
java.util.
*
;
import
java.util.
Map
;
/**
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @ClassName: StrategyArmoryDispatch
* @description 策略装配库(兵工厂),负责初始化策略计算
* @Description:
* @create 2023-12-23 10:02
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:26
*/
*/
@Slf4j
@Slf4j
@Service
@Service
(
"strategyArmoryDispatch"
)
public
class
StrategyArmoryDispatch
implements
IStrategyArmory
,
IStrategyDispatch
{
public
class
StrategyArmoryDispatch
extends
AbstractStrategyAlgorithm
{
// 抽奖策略算法
private
final
Map
<
String
,
IAlgorithm
>
algorithmMap
;
// 抽奖算法阈值,在多少范围内开始选择不同选择
private
final
Integer
ALGORITHM_THRESHOLD_VALUE
=
10000
;
@Resource
public
StrategyArmoryDispatch
(
Map
<
String
,
IAlgorithm
>
algorithmMap
)
{
private
IStrategyRepository
repository
;
this
.
algorithmMap
=
algorithmMap
;
private
final
SecureRandom
secureRandom
=
new
SecureRandom
();
@Override
public
boolean
assembleLotteryStrategyByActivityId
(
Long
activityId
)
{
Long
strategyId
=
repository
.
queryStrategyIdByActivityId
(
activityId
);
return
assembleLotteryStrategy
(
strategyId
);
}
}
@Override
public
boolean
assembleLotteryStrategy
(
Long
strategyId
)
{
// 1. 查询策略配置
List
<
StrategyAwardEntity
>
strategyAwardEntities
=
repository
.
queryStrategyAwardList
(
strategyId
);
// 2 缓存奖品库存【用于decr扣减库存使用】
for
(
StrategyAwardEntity
strategyAward
:
strategyAwardEntities
)
{
Integer
awardId
=
strategyAward
.
getAwardId
();
Integer
awardCount
=
strategyAward
.
getAwardCountSurplus
();
cacheStrategyAwardCount
(
strategyId
,
awardId
,
awardCount
);
}
// 3.1 默认装配配置【全量抽奖概率】
assembleLotteryStrategy
(
String
.
valueOf
(
strategyId
),
strategyAwardEntities
);
// 3.2 权重策略配置 - 适用于 rule_weight 权重规则配置【4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109】
StrategyEntity
strategyEntity
=
repository
.
queryStrategyEntityByStrategyId
(
strategyId
);
String
ruleWeight
=
strategyEntity
.
getRuleWeight
();
if
(
null
==
ruleWeight
)
return
true
;
StrategyRuleEntity
strategyRuleEntity
=
repository
.
queryStrategyRule
(
strategyId
,
ruleWeight
);
// 业务异常,策略规则中 rule_weight 权重规则已适用但未配置
if
(
null
==
strategyRuleEntity
)
{
throw
new
AppException
(
ResponseCode
.
STRATEGY_RULE_WEIGHT_IS_NULL
.
getCode
(),
ResponseCode
.
STRATEGY_RULE_WEIGHT_IS_NULL
.
getInfo
());
}
Map
<
String
,
List
<
Integer
>>
ruleWeightValueMap
=
strategyRuleEntity
.
getRuleWeightValues
();
for
(
String
key
:
ruleWeightValueMap
.
keySet
())
{
List
<
Integer
>
ruleWeightValues
=
ruleWeightValueMap
.
get
(
key
);
ArrayList
<
StrategyAwardEntity
>
strategyAwardEntitiesClone
=
new
ArrayList
<>(
strategyAwardEntities
);
strategyAwardEntitiesClone
.
removeIf
(
entity
->
!
ruleWeightValues
.
contains
(
entity
.
getAwardId
()));
assembleLotteryStrategy
(
String
.
valueOf
(
strategyId
).
concat
(
Constants
.
UNDERLINE
).
concat
(
key
),
strategyAwardEntitiesClone
);
}
return
true
;
}
/**
* 计算公式;
* 1. 找到范围内最小的概率值,比如 0.1、0.02、0.003,需要找到的值是 0.003
* 2. 基于1找到的最小值,0.003 就可以计算出百分比、千分比的整数值。这里就是1000
* 3. 那么「概率 * 1000」分别占比100个、20个、3个,总计是123个
* 4. 后续的抽奖就用123作为随机数的范围值,生成的值100个都是0.1概率的奖品、20个是概率0.02的奖品、最后是3个是0.003的奖品。
*/
private
void
assembleLotteryStrategy
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
)
{
// 1. 获取最小概率值
BigDecimal
minAwardRate
=
strategyAwardEntities
.
stream
()
.
map
(
StrategyAwardEntity:
:
getAwardRate
)
.
min
(
BigDecimal:
:
compareTo
)
.
orElse
(
BigDecimal
.
ZERO
);
// 2. 循环计算找到概率范围值
BigDecimal
rateRange
=
BigDecimal
.
valueOf
(
convert
(
minAwardRate
.
doubleValue
()));
// 3. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
List
<
Integer
>
strategyAwardSearchRateTables
=
new
ArrayList
<>(
rateRange
.
intValue
());
for
(
StrategyAwardEntity
strategyAward
:
strategyAwardEntities
)
{
Integer
awardId
=
strategyAward
.
getAwardId
();
BigDecimal
awardRate
=
strategyAward
.
getAwardRate
();
// 计算出每个概率值需要存放到查找表的数量,循环填充
for
(
int
i
=
0
;
i
<
rateRange
.
multiply
(
awardRate
).
intValue
();
i
++)
{
strategyAwardSearchRateTables
.
add
(
awardId
);
}
}
// 4. 对存储的奖品进行乱序操作
Collections
.
shuffle
(
strategyAwardSearchRateTables
);
// 5. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map
<
Integer
,
Integer
>
shuffleStrategyAwardSearchRateTable
=
new
LinkedHashMap
<>();
for
(
int
i
=
0
;
i
<
strategyAwardSearchRateTables
.
size
();
i
++)
{
shuffleStrategyAwardSearchRateTable
.
put
(
i
,
strategyAwardSearchRateTables
.
get
(
i
));
}
// 6. 存放到 Redis
repository
.
storeStrategyAwardSearchRateTable
(
key
,
shuffleStrategyAwardSearchRateTable
.
size
(),
shuffleStrategyAwardSearchRateTable
);
}
/**
* 转换计算,只根据小数位来计算。如【0.01返回100】、【0.009返回1000】、【0.0018返回10000】
*/
private
double
convertOld1
(
double
min
)
{
if
(
0
==
min
)
return
1
D
;
double
current
=
min
;
double
max
=
1
;
while
(
current
%
1
!=
0
)
{
current
=
current
*
10
;
max
=
max
*
10
;
}
return
max
;
}
private
double
convertOld2
(
double
min
)
{
if
(
min
==
0
)
return
1
D
;
String
minStr
=
Double
.
toString
(
min
);
int
decimalPlaces
=
0
;
int
decimalPointIndex
=
minStr
.
indexOf
(
'.'
);
if
(
decimalPointIndex
!=
-
1
)
{
decimalPlaces
=
minStr
.
length
()
-
decimalPointIndex
-
1
;
}
return
Math
.
pow
(
10
,
decimalPlaces
);
}
private
double
convert
(
double
min
)
{
if
(
0
==
min
)
return
1
D
;
String
minStr
=
String
.
valueOf
(
min
);
// 小数点前
String
beginVale
=
minStr
.
substring
(
0
,
minStr
.
indexOf
(
"."
));
int
beginLength
=
0
;
if
(
Double
.
parseDouble
(
beginVale
)
>
0
)
{
beginLength
=
minStr
.
substring
(
0
,
minStr
.
indexOf
(
"."
)).
length
();
}
// 小数点后
String
endValue
=
minStr
.
substring
(
minStr
.
indexOf
(
"."
)
+
1
);
int
endLength
=
0
;
if
(
Double
.
parseDouble
(
endValue
)
>
0
)
{
endLength
=
minStr
.
substring
(
minStr
.
indexOf
(
"."
)
+
1
).
length
();
}
return
Math
.
pow
(
10
,
beginLength
+
endLength
);
}
/**
* 缓存奖品库存到Redis
*
* @param strategyId 策略ID
* @param awardId 奖品ID
* @param awardCount 奖品库存
*/
private
void
cacheStrategyAwardCount
(
Long
strategyId
,
Integer
awardId
,
Integer
awardCount
)
{
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_AWARD_COUNT_KEY
+
strategyId
+
Constants
.
UNDERLINE
+
awardId
;
repository
.
cacheStrategyAwardCount
(
cacheKey
,
awardCount
);
}
@Override
@Override
public
Integer
getRandomAwardId
(
Long
strategyId
)
{
protected
void
armoryAlgorithm
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
)
{
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
// 1. 概率最小值
int
rateRange
=
repository
.
getRateRange
(
strategyId
);
BigDecimal
minAwardRate
=
minAwardRate
(
strategyAwardEntities
);
// 通过生成的随机值,获取概率值奖品查找表的结果
// 2. 概率范围值
return
repository
.
getStrategyAwardAssemble
(
String
.
valueOf
(
strategyId
),
secureRandom
.
nextInt
(
rateRange
));
double
rateRange
=
convert
(
minAwardRate
.
doubleValue
());
}
// 3. 根据概率值范围选择算法
if
(
rateRange
<=
ALGORITHM_THRESHOLD_VALUE
)
{
@Override
IAlgorithm
o1Algorithm
=
algorithmMap
.
get
(
AbstractAlgorithm
.
Algorithm
.
O1
.
getKey
());
public
Integer
getRandomAwardId
(
Long
strategyId
,
String
ruleWeightValue
)
{
o1Algorithm
.
armoryAlgorithm
(
key
,
strategyAwardEntities
,
new
BigDecimal
(
rateRange
));
String
key
=
String
.
valueOf
(
strategyId
).
concat
(
Constants
.
UNDERLINE
).
concat
(
ruleWeightValue
);
repository
.
cacheStrategyArmoryAlgorithm
(
key
,
AbstractAlgorithm
.
Algorithm
.
O1
.
getKey
());
return
getRandomAwardId
(
key
);
}
else
{
IAlgorithm
oLogNAlgorithm
=
algorithmMap
.
get
(
AbstractAlgorithm
.
Algorithm
.
OLogN
.
getKey
());
oLogNAlgorithm
.
armoryAlgorithm
(
key
,
strategyAwardEntities
,
new
BigDecimal
(
rateRange
));
repository
.
cacheStrategyArmoryAlgorithm
(
key
,
AbstractAlgorithm
.
Algorithm
.
OLogN
.
getKey
());
}
}
}
@Override
@Override
p
ublic
Integer
getRandomAwardId
(
String
key
)
{
p
rotected
Integer
dispatchAlgorithm
(
String
key
)
{
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
String
beanName
=
repository
.
queryStrategyArmoryAlgorithmFromCache
(
key
);
i
nt
rateRange
=
repository
.
getRateRange
(
key
);
i
f
(
null
==
beanName
)
throw
new
RuntimeException
(
"key "
+
key
+
" beanName is "
+
beanName
);
// 通过生成的随机值,获取概率值奖品查找表的结果
IAlgorithm
algorithm
=
algorithmMap
.
get
(
beanName
);
return
repository
.
getStrategyAwardAssemble
(
key
,
secureRandom
.
nextInt
(
rateRange
)
);
return
algorithm
.
dispatchAlgorithm
(
key
);
}
}
@Override
public
Boolean
subtractionAwardStock
(
Long
strategyId
,
Integer
awardId
,
Date
endDateTime
)
{
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_AWARD_COUNT_KEY
+
strategyId
+
Constants
.
UNDERLINE
+
awardId
;
return
repository
.
subtractionAwardStock
(
cacheKey
,
endDateTime
);
}
}
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/AbstractAlgorithm.java
0 → 100644
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory.algorithm
;
import
cn.bugstack.domain.strategy.repository.IStrategyRepository
;
import
lombok.AllArgsConstructor
;
import
lombok.Getter
;
import
lombok.NoArgsConstructor
;
import
javax.annotation.Resource
;
import
java.security.SecureRandom
;
/**
* @ClassName: AbstractAlgorithm
* @Description: 抽奖算法抽象类
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:32
*/
public
abstract
class
AbstractAlgorithm
implements
IAlgorithm
{
@Resource
protected
IStrategyRepository
repository
;
protected
final
SecureRandom
secureRandom
=
new
SecureRandom
();
@Getter
@AllArgsConstructor
@NoArgsConstructor
public
enum
Algorithm
{
O1
(
"o1Algorithm"
),
OLogN
(
"oLogNAlgorithm"
);
private
String
key
;
}
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/IAlgorithm.java
0 → 100644
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory.algorithm
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
java.math.BigDecimal
;
import
java.util.List
;
/**
* @author
* @description 抽奖算法
* @create 2024-08-24 13:02
*/
public
interface
IAlgorithm
{
void
armoryAlgorithm
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
,
BigDecimal
rateRange
);
Integer
dispatchAlgorithm
(
String
key
);
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/impl/O1Algorithm.java
0 → 100644
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory.algorithm.impl
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
java.math.BigDecimal
;
import
java.util.*
;
/**
* @ClassName: O1Algorithm
* @Description: o(1) 时间复杂度算法,将抽奖概率分配到 Redis Map 结构中,1个概率对应的奖品就是一个Key,抽奖时直接通过 get 获取。
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:34
*/
@Slf4j
@Component
(
"o1Algorithm"
)
public
class
O1Algorithm
extends
AbstractAlgorithm
{
@Override
public
void
armoryAlgorithm
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
,
BigDecimal
rateRange
)
{
log
.
info
(
"抽奖算法 O(1) 装配 key:{}"
,
key
);
// 1. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
List
<
Integer
>
strategyAwardSearchRateTables
=
new
ArrayList
<>(
rateRange
.
intValue
());
for
(
StrategyAwardEntity
strategyAward
:
strategyAwardEntities
)
{
Integer
awardId
=
strategyAward
.
getAwardId
();
BigDecimal
awardRate
=
strategyAward
.
getAwardRate
();
// 计算出每个概率值需要存放到查找表的数量,循环填充
for
(
int
i
=
0
;
i
<
rateRange
.
multiply
(
awardRate
).
intValue
();
i
++)
{
strategyAwardSearchRateTables
.
add
(
awardId
);
}
}
// 2. 对存储的奖品进行乱序操作
Collections
.
shuffle
(
strategyAwardSearchRateTables
);
// 3. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map
<
Integer
,
Integer
>
shuffleStrategyAwardSearchRateTable
=
new
LinkedHashMap
<>();
for
(
int
i
=
0
;
i
<
strategyAwardSearchRateTables
.
size
();
i
++)
{
shuffleStrategyAwardSearchRateTable
.
put
(
i
,
strategyAwardSearchRateTables
.
get
(
i
));
}
// 4. 存放到 Redis
repository
.
storeStrategyAwardSearchRateTable
(
key
,
shuffleStrategyAwardSearchRateTable
.
size
(),
shuffleStrategyAwardSearchRateTable
);
}
@Override
public
Integer
dispatchAlgorithm
(
String
key
)
{
log
.
info
(
"抽奖算法 O(1) 抽奖计算 key:{}"
,
key
);
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
int
rateRange
=
repository
.
getRateRange
(
key
);
// 通过生成的随机值,获取概率值奖品查找表的结果
return
repository
.
getStrategyAwardAssemble
(
key
,
secureRandom
.
nextInt
(
rateRange
));
}
}
bigmarket-domain/src/main/java/cn/bugstack/domain/strategy/service/armory/algorithm/impl/OLogNAlgorithm.java
0 → 100644
浏览文件 @
f440f6b3
package
cn.bugstack.domain.strategy.service.armory.algorithm.impl
;
import
cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity
;
import
cn.bugstack.domain.strategy.service.armory.algorithm.AbstractAlgorithm
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.Resource
;
import
java.math.BigDecimal
;
import
java.util.*
;
import
java.util.concurrent.CompletableFuture
;
import
java.util.concurrent.CompletionException
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.stream.Collectors
;
/**
* @ClassName: OLogNAlgorithm
* @Description: 循环、二分、多线程,查找抽奖算法
* @Author: zhaoyongfeng
* @Date: 2025/1/23 23:37
*/
@Slf4j
@Component
(
"oLogNAlgorithm"
)
public
class
OLogNAlgorithm
extends
AbstractAlgorithm
{
@Resource
private
ThreadPoolExecutor
threadPoolExecutor
;
/**
* 预热活动概率值
* 如概率值为;3、4、2、9,存储为 [1~3]、[4~7]、[8~9]、[10~18],抽奖时,for循环匹配。
*
* @param key 为策略ID、权重ID
* @param strategyAwardEntities 对应的奖品概率
*/
@Override
public
void
armoryAlgorithm
(
String
key
,
List
<
StrategyAwardEntity
>
strategyAwardEntities
,
BigDecimal
rateRange
)
{
log
.
info
(
"抽奖算法 OLog(n) 装配 key:{}"
,
key
);
int
from
=
1
;
int
to
=
0
;
Map
<
Map
<
Integer
,
Integer
>,
Integer
>
table
=
new
HashMap
<>();
for
(
StrategyAwardEntity
strategyAward
:
strategyAwardEntities
)
{
Integer
awardId
=
strategyAward
.
getAwardId
();
BigDecimal
awardRate
=
strategyAward
.
getAwardRate
();
to
+=
rateRange
.
multiply
(
awardRate
).
intValue
();
Map
<
Integer
,
Integer
>
ft
=
new
HashMap
<>();
ft
.
put
(
from
,
to
);
table
.
put
(
ft
,
awardId
);
from
=
to
+
1
;
}
repository
.
storeStrategyAwardSearchRateTable
(
key
,
to
,
table
);
}
@Override
public
Integer
dispatchAlgorithm
(
String
key
)
{
int
rateRange
=
repository
.
getRateRange
(
key
);
Map
<
Map
<
String
,
Integer
>,
Integer
>
table
=
repository
.
getMap
(
key
);
// 小于等于8 for循环、小于等于16 二分查找、更多检索走多线程
if
(
table
.
size
()
<=
8
)
{
log
.
info
(
"抽奖算法 OLog(n) 抽奖计算(循环) key:{}"
,
key
);
return
forSearch
(
secureRandom
.
nextInt
(
rateRange
),
table
);
}
else
if
(
table
.
size
()
<=
16
)
{
log
.
info
(
"抽奖算法 OLog(n) 抽奖计算(二分) key:{}"
,
key
);
return
binarySearch
(
secureRandom
.
nextInt
(
rateRange
),
table
);
}
else
{
log
.
info
(
"抽奖算法 OLog(n) 抽奖计算(多线程) key:{}"
,
key
);
return
threadSearch
(
secureRandom
.
nextInt
(
rateRange
),
table
);
}
}
private
Integer
forSearch
(
int
rateKey
,
Map
<
Map
<
String
,
Integer
>,
Integer
>
table
)
{
Integer
awardId
=
null
;
for
(
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>
entry
:
table
.
entrySet
())
{
Map
<
String
,
Integer
>
rangeMap
=
entry
.
getKey
();
for
(
Map
.
Entry
<
String
,
Integer
>
range
:
rangeMap
.
entrySet
())
{
int
start
=
Integer
.
parseInt
(
range
.
getKey
());
int
end
=
range
.
getValue
();
if
(
rateKey
>=
start
&&
rateKey
<=
end
)
{
awardId
=
entry
.
getValue
();
break
;
}
}
if
(
awardId
!=
null
)
{
break
;
}
}
return
awardId
;
}
private
Integer
binarySearch
(
int
rateKey
,
Map
<
Map
<
String
,
Integer
>,
Integer
>
table
)
{
List
<
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>>
entries
=
new
ArrayList
<>(
table
.
entrySet
());
entries
.
sort
(
Comparator
.
comparingInt
(
e
->
Integer
.
parseInt
(
e
.
getKey
().
keySet
().
iterator
().
next
())));
int
left
=
0
;
int
right
=
entries
.
size
()
-
1
;
while
(
left
<=
right
)
{
int
mid
=
left
+
(
right
-
left
)
/
2
;
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>
entry
=
entries
.
get
(
mid
);
Map
<
String
,
Integer
>
rangeMap
=
entry
.
getKey
();
Map
.
Entry
<
String
,
Integer
>
range
=
rangeMap
.
entrySet
().
iterator
().
next
();
int
start
=
Integer
.
parseInt
(
range
.
getKey
());
int
end
=
range
.
getValue
();
if
(
rateKey
<
start
)
{
right
=
mid
-
1
;
}
else
if
(
rateKey
>
end
)
{
left
=
mid
+
1
;
}
else
{
return
entry
.
getValue
();
}
}
return
null
;
}
private
Integer
threadSearch
(
int
rateKey
,
Map
<
Map
<
String
,
Integer
>,
Integer
>
table
)
{
List
<
CompletableFuture
<
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>>>
futures
=
table
.
entrySet
().
stream
()
.
map
(
entry
->
CompletableFuture
.
supplyAsync
(()
->
{
Map
<
String
,
Integer
>
rangeMap
=
entry
.
getKey
();
for
(
Map
.
Entry
<
String
,
Integer
>
rangeEntry
:
rangeMap
.
entrySet
())
{
int
start
=
Integer
.
parseInt
(
rangeEntry
.
getKey
());
int
end
=
rangeEntry
.
getValue
();
if
(
rateKey
>=
start
&&
rateKey
<=
end
)
{
return
entry
;
}
}
return
null
;
},
threadPoolExecutor
))
.
collect
(
Collectors
.
toList
());
CompletableFuture
<
Void
>
allFutures
=
CompletableFuture
.
allOf
(
futures
.
toArray
(
new
CompletableFuture
[
0
]));
try
{
// 等待所有异步任务完成,同时返回第一个匹配的结果
allFutures
.
join
();
for
(
CompletableFuture
<
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>>
future
:
futures
)
{
Map
.
Entry
<
Map
<
String
,
Integer
>,
Integer
>
result
=
future
.
getNow
(
null
);
if
(
result
!=
null
)
{
return
result
.
getValue
();
}
}
}
catch
(
CompletionException
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
}
\ No newline at end of file
bigmarket-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/dao/IStrategyAwardDao.java
浏览文件 @
f440f6b3
...
@@ -22,5 +22,5 @@ public interface IStrategyAwardDao {
...
@@ -22,5 +22,5 @@ public interface IStrategyAwardDao {
void
updateStrategyAwardStock
(
StrategyAward
strategyAward
);
void
updateStrategyAwardStock
(
StrategyAward
strategyAward
);
StrategyAward
queryStrategyAward
(
StrategyAward
strategyAwardReq
);
StrategyAward
queryStrategyAward
(
StrategyAward
strategyAwardReq
);
List
<
StrategyAward
>
queryOpenActivityStrategyAwardList
();
}
}
bigmarket-infrastructure/src/main/java/cn/bugstack/infrastructure/persistent/repository/StrategyRepository.java
浏览文件 @
f440f6b3
...
@@ -87,14 +87,22 @@ public class StrategyRepository implements IStrategyRepository {
...
@@ -87,14 +87,22 @@ public class StrategyRepository implements IStrategyRepository {
* 简单来说,getMap 方法返回的 RMap 对象是懒加载的,只有在你实际进行操作时,Redis 数据库中的数据结构才会被创建或修改。
* 简单来说,getMap 方法返回的 RMap 对象是懒加载的,只有在你实际进行操作时,Redis 数据库中的数据结构才会被创建或修改。
*/
*/
@Override
@Override
public
void
storeStrategyAwardSearchRateTable
(
String
key
,
Integer
rateRange
,
Map
<
Integer
,
Integer
>
strategyAwardSearchRateTable
)
{
public
<
K
,
V
>
void
storeStrategyAwardSearchRateTable
(
String
key
,
Integer
rateRange
,
Map
<
K
,
V
>
strategyAwardSearchRateTable
)
{
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
// 1. 存储抽奖策略范围值,如10000,用于生成1000以内的随机数
redisService
.
setValue
(
Constants
.
RedisKey
.
STRATEGY_RATE_RANGE_KEY
+
key
,
rateRange
);
redisService
.
setValue
(
Constants
.
RedisKey
.
STRATEGY_RATE_RANGE_KEY
+
key
,
rateRange
);
// 2. 存储概率查找表
// 2. 存储概率查找表 - 存在则删除重新装配
Map
<
Integer
,
Integer
>
cacheRateTable
=
redisService
.
getMap
(
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
);
String
tableCacheKey
=
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
;
if
(
redisService
.
isExists
(
tableCacheKey
))
{
redisService
.
remove
(
tableCacheKey
);
}
Map
<
K
,
V
>
cacheRateTable
=
redisService
.
getMap
(
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
);
cacheRateTable
.
putAll
(
strategyAwardSearchRateTable
);
cacheRateTable
.
putAll
(
strategyAwardSearchRateTable
);
}
}
@Override
public
<
K
,
V
>
Map
<
K
,
V
>
getMap
(
String
key
)
{
return
redisService
.
getMap
(
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
);
}
@Override
@Override
public
Integer
getStrategyAwardAssemble
(
String
key
,
Integer
rateKey
)
{
public
Integer
getStrategyAwardAssemble
(
String
key
,
Integer
rateKey
)
{
return
redisService
.
getFromMap
(
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
,
rateKey
);
return
redisService
.
getFromMap
(
Constants
.
RedisKey
.
STRATEGY_RATE_TABLE_KEY
+
key
,
rateKey
);
...
@@ -404,5 +412,31 @@ public class StrategyRepository implements IStrategyRepository {
...
@@ -404,5 +412,31 @@ public class StrategyRepository implements IStrategyRepository {
return
ruleWeightVOS
;
return
ruleWeightVOS
;
}
}
@Override
public
List
<
StrategyAwardStockKeyVO
>
queryOpenActivityStrategyAwardList
()
{
List
<
StrategyAward
>
strategyAwards
=
strategyAwardDao
.
queryOpenActivityStrategyAwardList
();
if
(
null
==
strategyAwards
||
strategyAwards
.
isEmpty
())
return
null
;
List
<
StrategyAwardStockKeyVO
>
strategyAwardStockKeyVOS
=
new
ArrayList
<>();
for
(
StrategyAward
strategyAward
:
strategyAwards
)
{
StrategyAwardStockKeyVO
strategyAwardStockKeyVO
=
StrategyAwardStockKeyVO
.
builder
()
.
strategyId
(
strategyAward
.
getStrategyId
())
.
awardId
(
strategyAward
.
getAwardId
())
.
build
();
strategyAwardStockKeyVOS
.
add
(
strategyAwardStockKeyVO
);
}
return
strategyAwardStockKeyVOS
;
}
@Override
public
void
cacheStrategyArmoryAlgorithm
(
String
key
,
String
beanName
)
{
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_ARMORY_ALGORITHM_KEY
+
key
;
redisService
.
setValue
(
cacheKey
,
beanName
);
}
@Override
public
String
queryStrategyArmoryAlgorithmFromCache
(
String
key
)
{
String
cacheKey
=
Constants
.
RedisKey
.
STRATEGY_ARMORY_ALGORITHM_KEY
+
key
;
if
(!
redisService
.
isExists
(
cacheKey
))
return
null
;
return
redisService
.
getValue
(
cacheKey
);
}
}
}
bigmarket-types/src/main/java/cn/bugstack/types/common/Constants.java
浏览文件 @
f440f6b3
...
@@ -29,6 +29,7 @@ public class Constants {
...
@@ -29,6 +29,7 @@ public class Constants {
public
static
String
ACTIVITY_ACCOUNT_LOCK
=
"activity_account_lock_"
;
public
static
String
ACTIVITY_ACCOUNT_LOCK
=
"activity_account_lock_"
;
public
static
String
ACTIVITY_ACCOUNT_UPDATE_LOCK
=
"activity_account_update_lock_"
;
public
static
String
ACTIVITY_ACCOUNT_UPDATE_LOCK
=
"activity_account_update_lock_"
;
public
static
String
USER_CREDIT_ACCOUNT_LOCK
=
"user_credit_account_lock_"
;
public
static
String
USER_CREDIT_ACCOUNT_LOCK
=
"user_credit_account_lock_"
;
public
static
String
STRATEGY_ARMORY_ALGORITHM_KEY
=
"strategy_armory_algorithm_key_"
;
}
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录