提交 40594015 编写于 作者: 鲸落和鲨掉's avatar 鲸落和鲨掉

添加不超卖规则

上级 d10092a1
24-11-13.22:15:50.764 [main ] ERROR HikariPool - HikariPool-1 - Exception during pool initialization.
java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:906)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:831)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:456)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:246)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112)
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:159)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:117)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:67)
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:337)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
at com.sun.proxy.$Proxy79.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:152)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
at com.sun.proxy.$Proxy82.queryAwardList(Unknown Source)
at cn.bugstack.test.infrastructure.AwardDaoTest.Test_queryAwardList(AwardDaoTest.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: com.mysql.cj.exceptions.CJException: Unknown database 'big_market'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:129)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:792)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:717)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:685)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:134)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.proceedHandshakeWithPluggableAuthentication(NativeAuthenticationProvider.java:469)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.connect(NativeAuthenticationProvider.java:174)
at com.mysql.cj.protocol.a.NativeProtocol.connect(NativeProtocol.java:1348)
at com.mysql.cj.NativeSession.connect(NativeSession.java:157)
at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:850)
... 71 common frames omitted
24-11-13.22:17:02.261 [main ] ERROR HikariPool - HikariPool-1 - Exception during pool initialization.
java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:906)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:831)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:456)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:246)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112)
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:159)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:117)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:67)
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:337)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
at com.sun.proxy.$Proxy79.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:152)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
at com.sun.proxy.$Proxy82.queryAwardList(Unknown Source)
at cn.bugstack.test.infrastructure.AwardDaoTest.Test_queryAwardList(AwardDaoTest.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: com.mysql.cj.exceptions.CJException: Unknown database 'big_market'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:129)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:792)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:717)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:685)
at com.mysql.cj.protocol.a.NativeProtocol.checkErrorMessage(NativeProtocol.java:134)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.proceedHandshakeWithPluggableAuthentication(NativeAuthenticationProvider.java:469)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.connect(NativeAuthenticationProvider.java:174)
at com.mysql.cj.protocol.a.NativeProtocol.connect(NativeProtocol.java:1348)
at com.mysql.cj.NativeSession.connect(NativeSession.java:157)
at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:850)
... 71 common frames omitted
24-11-18.19:52:29.536 [HikariPool-1 housekeeper] WARN HikariPool - HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=46s731ms900µs600ns).
24-11-18.19:56:11.941 [HikariPool-1 housekeeper] WARN HikariPool - HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=46s929ms97µs900ns).
24-11-18.20:06:26.644 [HikariPool-1 housekeeper] WARN HikariPool - HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=1m54s620ms108µs700ns).
24-11-19.13:13:59.192 [main ] INFO LogicChainTest - Starting LogicChainTest using Java 1.8.0_412 on zhaoyongfeng with PID 19480 (started by zhaoyongfeng in C:\Users\31126\Desktop\study\xfg-frame-archetype-lite\xfg-frame-archetype-lite-app)
24-11-19.13:13:59.193 [main ] INFO LogicChainTest - The following 1 profile is active: "dev"
24-11-19.13:14:00.369 [main ] INFO RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
24-11-19.13:14:00.374 [main ] INFO RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
24-11-19.13:14:00.444 [main ] INFO RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 51 ms. Found 0 Redis repository interfaces.
24-11-19.13:14:03.216 [main ] INFO Version - Redisson 3.23.4
24-11-19.13:14:04.813 [redisson-netty-2-4] INFO MasterPubSubConnectionPool - 1 connections initialized for 127.0.0.1/127.0.0.1:6379
24-11-19.13:14:04.827 [redisson-netty-2-13] INFO MasterConnectionPool - 5 connections initialized for 127.0.0.1/127.0.0.1:6379
24-11-19.13:14:07.367 [main ] INFO EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '/actuator'
24-11-19.13:14:07.463 [main ] INFO LogicChainTest - Started LogicChainTest in 8.931 seconds (JVM running for 10.393)
24-11-19.13:14:08.109 [main ] INFO HikariDataSource - HikariPool-1 - Starting...
24-11-19.13:14:08.484 [main ] INFO HikariDataSource - HikariPool-1 - Start completed.
24-11-19.13:14:08.562 [main ] INFO LogicChainTest - 测试结果:true
24-11-19.13:14:08.566 [main ] INFO RuleWeightLogicChain - 抽奖责任链-权重开始 userId: xiaofuge strategyId: 100001 ruleModel: rule_weight
24-11-19.13:14:08.596 [main ] INFO RuleWeightLogicChain - 抽奖责任链-权重接管 userId: xiaofuge strategyId: 100001 ruleModel: rule_weight awardId: 102
24-11-19.13:14:08.596 [main ] INFO LogicChainTest - 测试结果:102
24-11-19.13:14:08.639 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariPool-1 - Shutdown initiated...
24-11-19.13:14:08.648 [SpringApplicationShutdownHook] INFO HikariDataSource - HikariPool-1 - Shutdown completed.
......@@ -3,9 +3,11 @@ package cn.bugstack;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@Configurable
@EnableScheduling
public class Application {
public static void main(String[] args){
......
<?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="cn.bugstack.infrastructure.persistent.dao.IRuleTreeDao">
<resultMap id="dataMap" type="cn.bugstack.infrastructure.persistent.po.RuleTree">
<id column="id" property="id"/>
<result column="tree_id" property="treeId"/>
<result column="tree_name" property="treeName"/>
<result column="tree_desc" property="treeDesc"/>
<result column="tree_node_rule_key" property="treeRootRuleKey"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryRuleTreeByTreeId" parameterType="java.lang.String" resultMap="dataMap">
select tree_id, tree_name, tree_desc, tree_node_rule_key
from rule_tree
where tree_id = #{treeId}
</select>
</mapper>
<?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="cn.bugstack.infrastructure.persistent.dao.IRuleTreeNodeLineDao">
<resultMap id="dataMap" type="cn.bugstack.infrastructure.persistent.po.RuleTreeNodeLine">
<id column="id" property="id"/>
<result column="tree_id" property="treeId"/>
<result column="rule_node_from" property="ruleNodeFrom"/>
<result column="rule_node_to" property="ruleNodeTo"/>
<result column="rule_limit_type" property="ruleLimitType"/>
<result column="rule_limit_value" property="ruleLimitValue"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryRuleTreeNodeLineListByTreeId" parameterType="java.lang.String" resultMap="dataMap">
select tree_id, rule_node_from, rule_node_to, rule_limit_type, rule_limit_value
from rule_tree_node_line
where tree_id = #{treeId}
</select>
</mapper>
<?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="cn.bugstack.infrastructure.persistent.dao.IRuleTreeNodeDao">
<resultMap id="dataMap" type="cn.bugstack.infrastructure.persistent.po.RuleTreeNode">
<id column="id" property="id"/>
<result column="tree_id" property="treeId"/>
<result column="rule_key" property="ruleKey"/>
<result column="rule_desc" property="ruleDesc"/>
<result column="rule_value" property="ruleValue"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryRuleTreeNodeListByTreeId" parameterType="java.lang.String" resultMap="dataMap">
select tree_id, rule_key, rule_desc, rule_value
from rule_tree_node
where tree_id = #{treeId}
</select>
</mapper>
......@@ -17,6 +17,7 @@
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryStrategyAwardList" resultMap="dataMap">
select strategy_id
from strategy_award
......@@ -28,10 +29,17 @@
where strategy_id = #{strategy_id}
</select>
<select id="queryStrategyAwardRuleModels" parameterType="java.lang.Long" resultMap="dataMap">
<select id="queryStrategyAwardRuleModels" parameterType="cn.bugstack.infrastructure.persistent.po.StrategyAward" resultType="java.lang.String">
select rule_models from strategy_award
where strategy_id = #{strategyId} and award_id = #{awardId}
</select>
<update id="updateStrategyAwardStock" parameterType="cn.bugstack.infrastructure.persistent.po.StrategyAward">
update strategy_award
set award_count_surplus = award_count_surplus - 1
where strategy_id = #{strategyId} and award_id = #{awardId} and award_count_surplus > 0
</update>
</mapper>
......@@ -35,14 +35,14 @@ public class LogicChainTest {
public void setUp() {
// 策略装配 100001、100002、100003
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
// log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100002L));
// log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100003L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100002L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100003L));
}
@Test
public void test_LogicChain_rule_blacklist() {
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("user001", 100001L);
DefaultChainFactory.StrategyAwardVO awardId = logicChain.logic("user001", 100001L);
log.info("测试结果:{}", awardId);
}
......@@ -52,14 +52,14 @@ public class LogicChainTest {
ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 4900L);
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("xiaofuge", 100001L);
DefaultChainFactory.StrategyAwardVO awardId = logicChain.logic("xiaofuge", 100001L);
log.info("测试结果:{}", awardId);
}
@Test
public void test_LogicChain_rule_default() {
ILogicChain logicChain = defaultChainFactory.openLogicChain(100001L);
Integer awardId = logicChain.logic("xiaofuge", 100001L);
DefaultChainFactory.StrategyAwardVO awardId = logicChain.logic("xiaofuge", 100001L);
log.info("测试结果:{}", awardId);
}
......
package cn.bugstack.test.domain;
import cn.bugstack.domain.strategy.model.valobj.*;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import cn.bugstack.domain.strategy.service.rule.tree.factory.engine.IDecisionTreeEngine;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @ClassName: LogicTreeTest
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/20 16:58
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class LogicTreeTest {
@Resource
private DefaultTreeFactory defaultTreeFactory;
/**
* rule_lock --左--> rule_luck_award
* --右--> rule_stock --右--> rule_luck_award
*/
@Test
public void test_tree_rule() {
// 构建参数
RuleTreeNodeVO rule_lock = RuleTreeNodeVO.builder()
.treeId("100000001")
.ruleKey("rule_lock")
.ruleDesc("限定用户已完成N次抽奖后解锁")
.ruleValue("1")
.treeNodeLineVOList(new ArrayList<RuleTreeNodeLineVO>() {{
add(RuleTreeNodeLineVO.builder()
.treeId("100000001")
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_luck_award")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.TAKE_OVER)
.build());
add(RuleTreeNodeLineVO.builder()
.treeId("100000001")
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_stock")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.ALLOW)
.build());
}})
.build();
RuleTreeNodeVO rule_luck_award = RuleTreeNodeVO.builder()
.treeId("100000001")
.ruleKey("rule_luck_award")
.ruleDesc("限定用户已完成N次抽奖后解锁")
.ruleValue("1")
.treeNodeLineVOList(null)
.build();
RuleTreeNodeVO rule_stock = RuleTreeNodeVO.builder()
.treeId("100000001")
.ruleKey("rule_stock")
.ruleDesc("库存处理规则")
.ruleValue(null)
.treeNodeLineVOList(new ArrayList<RuleTreeNodeLineVO>() {{
add(RuleTreeNodeLineVO.builder()
.treeId("100000001")
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_luck_award")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.TAKE_OVER)
.build());
}})
.build();
RuleTreeVO ruleTreeVO = new RuleTreeVO();
ruleTreeVO.setTreeId("100000001");
ruleTreeVO.setTreeName("决策树规则;增加dall-e-3画图模型");
ruleTreeVO.setTreeDesc("决策树规则;增加dall-e-3画图模型");
ruleTreeVO.setTreeRootRuleNode("rule_lock");
ruleTreeVO.setTreeNodeMap(new HashMap<String, RuleTreeNodeVO>() {{
put("rule_lock", rule_lock);
put("rule_stock", rule_stock);
put("rule_luck_award", rule_luck_award);
}});
IDecisionTreeEngine treeEngine = defaultTreeFactory.openLogicTree(ruleTreeVO);
DefaultTreeFactory.StrategyAwardVO data = treeEngine.process("xiaofuge", 100001L, 100);
log.info("测试结果:{}", JSON.toJSONString(data));
}
}
......@@ -3,9 +3,11 @@ package cn.bugstack.test.domain;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.IRaffleStrategy;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
import cn.bugstack.domain.strategy.IRaffleStock;
import cn.bugstack.domain.strategy.service.armory.IStrategyArmory;
import cn.bugstack.domain.strategy.service.rule.chain.impl.DefaultLogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.impl.RuleWeightLogicChain;
import cn.bugstack.domain.strategy.service.rule.tree.impl.RuleLockLogicTreeNode;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
......@@ -16,6 +18,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName: RaffleStrategyTest
......@@ -34,33 +37,42 @@ public class RaffleStrategyTest {
@Resource
private RuleWeightLogicChain ruleWeightLogicChain;
@Resource
private DefaultLogicChain defaultLogicChain;
private RuleLockLogicTreeNode ruleLockLogicTreeNode;
@Resource
private IRaffleStock raffleStock;
@Before
public void setUp() {
// 策略装配 100001、100002、100003
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
/* log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100002L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100003L));*/
// log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100006L));
// 通过反射 mock 规则中的值
ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 40500L);
ReflectionTestUtils.setField(defaultLogicChain, "userRaffleCount", 10L);
ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 4900L);
ReflectionTestUtils.setField(ruleLockLogicTreeNode, "userRaffleCount", 10L);
}
@Test
public void test_performRaffle() {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge")
.strategyId(100001L)
.build();
public void test_performRaffle() throws InterruptedException {
for (int i = 0; i < 3; i++) {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge")
.strategyId(100006L)
.build();
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}
// 等待 UpdateAwardStockJob 消费队列
new CountDownLatch(1).await();
}
@Test
public void test_performRaffle_blacklist() {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
......@@ -92,6 +104,11 @@ public class RaffleStrategyTest {
}
@Test
public void test_takeQueueValue() throws InterruptedException {
StrategyAwardStockKeyVO strategyAwardStockKeyVO = raffleStock.takeQueueValue();
log.info("测试结果:{}", JSON.toJSONString(strategyAwardStockKeyVO));
}
}
......@@ -2,14 +2,10 @@ package cn.bugstack.domain.strategy;
import cn.bugstack.domain.strategy.model.entity.RaffleAwardEntity;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardRuleModelVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.chain.ILogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
......@@ -22,21 +18,25 @@ import org.apache.commons.lang3.StringUtils;
* @Date: 2024/11/17 16:14
*/
@Slf4j
public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
public abstract class AbstractRaffleStrategy implements IRaffleStrategy, IRaffleStock {
// 策略仓储服务 -> domain层像一个大厨,仓储层提供米面粮油
protected IStrategyRepository repository;
// 策略调度服务 -> 只负责抽奖处理,通过新增接口的方式,隔离职责,不需要使用方关心或者调用抽奖的初始化
protected IStrategyDispatch strategyDispatch;
// 抽奖的责任链 -> 从抽奖的规则中,解耦出前置规则为责任链处理
private final DefaultChainFactory defaultChainFactory;
protected final DefaultChainFactory defaultChainFactory;
// 抽奖的决策树 -> 负责抽奖中到抽奖后的规则过滤,如抽奖到A奖品ID,之后要做次数的判断和库存的扣减等。
protected final DefaultTreeFactory defaultTreeFactory;
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory) {
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory, DefaultTreeFactory defaultTreeFactory) {
this.repository = repository;
this.strategyDispatch = strategyDispatch;
this.defaultChainFactory = defaultChainFactory;
this.defaultTreeFactory = defaultTreeFactory;
}
@Override
public RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity) {
// 1. 参数校验
......@@ -46,35 +46,46 @@ public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
// 2. 获取抽奖责任链 - 前置规则的责任链处理
ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
// 3. 通过责任链获得,奖品ID
Integer awardId = logicChain.logic(userId, strategyId);
// 4. 查询奖品规则「抽奖中(拿到奖品ID时,过滤规则)、抽奖后(扣减完奖品库存后过滤,抽奖中拦截和无库存则走兜底)」
StrategyAwardRuleModelVO strategyAwardRuleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
// 5. 抽奖中 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionCenterEntity = this.doCheckRaffleCenterLogic(RaffleFactorEntity.builder()
.userId(userId)
.strategyId(strategyId)
.awardId(awardId)
.build(), strategyAwardRuleModelVO.raffleCenterRuleModelList());
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionCenterEntity.getCode())){
log.info("【临时日志】中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。");
// 2. 责任链抽奖计算【这步拿到的是初步的抽奖ID,之后需要根据ID处理抽奖】注意;黑名单、权重等非默认抽奖的直接返回抽奖结果
DefaultChainFactory.StrategyAwardVO chainStrategyAwardVO = raffleLogicChain(userId, strategyId);
log.info("抽奖策略计算-责任链 {} {} {} {}", userId, strategyId, chainStrategyAwardVO.getAwardId(), chainStrategyAwardVO.getLogicModel());
if (!DefaultChainFactory.LogicModel.RULE_DEFAULT.getCode().equals(chainStrategyAwardVO.getLogicModel())) {
return RaffleAwardEntity.builder()
.awardDesc("中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。")
.awardId(chainStrategyAwardVO.getAwardId())
.build();
}
// 3. 规则树抽奖过滤【奖品ID,会根据抽奖次数判断、库存判断、兜底兜里返回最终的可获得奖品信息】
DefaultTreeFactory.StrategyAwardVO treeStrategyAwardVO = raffleLogicTree(userId, strategyId, chainStrategyAwardVO.getAwardId());
log.info("抽奖策略计算-规则树 {} {} {} {}", userId, strategyId, treeStrategyAwardVO.getAwardId(), treeStrategyAwardVO.getAwardRuleValue());
// 4. 返回抽奖结果
return RaffleAwardEntity.builder()
.awardId(awardId)
.awardId(treeStrategyAwardVO.getAwardId())
.awardConfig(treeStrategyAwardVO.getAwardRuleValue())
.build();
}
protected abstract RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
/**
* 抽奖计算,责任链抽象方法
*
* @param userId 用户ID
* @param strategyId 策略ID
* @return 奖品ID
*/
public abstract DefaultChainFactory.StrategyAwardVO raffleLogicChain(String userId, Long strategyId);
/**
* 抽奖结果过滤,决策树抽象方法
*
* @param userId 用户ID
* @param strategyId 策略ID
* @param awardId 奖品ID
* @return 过滤结果【奖品ID,会根据抽奖次数判断、库存判断、兜底兜里返回最终的可获得奖品信息】
*/
public abstract DefaultTreeFactory.StrategyAwardVO raffleLogicTree(String userId, Long strategyId, Integer awardId);
}
package cn.bugstack.domain.strategy;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
public interface IRaffleStock {
/**
* 获取奖品库存消耗队列
*
* @return 奖品库存Key信息
* @throws InterruptedException 异常
*/
StrategyAwardStockKeyVO takeQueueValue() throws InterruptedException;
/**
* 更新奖品库存消耗记录
*
* @param strategyId 策略ID
* @param awardId 奖品ID
*/
void updateStrategyAwardStock(Long strategyId, Integer awardId);
}
package cn.bugstack.domain.strategy.model.entity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import lombok.*;
/**
* @ClassName: RuleActionEntity
* @Description: 规则动作
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:26
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleActionEntity<T extends RuleActionEntity.RaffleEntity> {
private String code = RuleLogicCheckTypeVO.ALLOW.getCode();
private String info = RuleLogicCheckTypeVO.ALLOW.getInfo();
private String ruleModel;
private T data;
static public class RaffleEntity {
}
// 抽奖之前
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleBeforeEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
// 抽奖之中
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleCenterEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
// 抽奖之后
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleAfterEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
}
......@@ -33,8 +33,9 @@ public class StrategyEntity {
public String getRuleWeight() {
String[] ruleModels = this.ruleModels();
if (null == ruleModels) return null;
for (String ruleModel : ruleModels) {
if ("rule_weight".equals(ruleModel)) return ruleModel;
if("rule_weight".equals(ruleModel)) return ruleModel;
}
return null;
}
......
package cn.bugstack.domain.strategy.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @ClassName: RuleLimitTypeVO
* @Description: 规则限定枚举值
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:37
*/
@Getter
@AllArgsConstructor
public enum RuleLimitTypeVO {
EQUAL(1, "等于"),
GT(2, "大于"),
LT(3, "小于"),
GE(4, "大于&等于"),
LE(5, "小于&等于"),
ENUM(6, "枚举"),
;
private final Integer code;
private final String info;
}
package cn.bugstack.domain.strategy.model.valobj;
import lombok.*;
/**
* @ClassName: RuleTreeNodeLineVO
* @Description: 规则树节点指向线对象。用于衔接 from->to 节点链路关系
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:37
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeNodeLineVO {
/** 规则树ID */
private String treeId;
/** 规则Key节点 From */
private String ruleNodeFrom;
/** 规则Key节点 To */
private String ruleNodeTo;
/** 限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围] */
private RuleLimitTypeVO ruleLimitType;
/** 限定值(到下个节点) */
private RuleLogicCheckTypeVO ruleLimitValue;
}
package cn.bugstack.domain.strategy.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @ClassName: RuleTreeNodeVO
* @Description: 规则树节点对象
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:37
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeNodeVO {
/** 规则树ID */
private String treeId;
/** 规则Key */
private String ruleKey;
/** 规则描述 */
private String ruleDesc;
/** 规则比值 */
private String ruleValue;
/** 规则连线 */
private List<RuleTreeNodeLineVO> treeNodeLineVOList;
}
package cn.bugstack.domain.strategy.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* @ClassName: RuleTreeVO
* @Description: 规则树对象【注意;不具有唯一ID,不需要改变数据库结果的对象,可以被定义为值对象】
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:37
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeVO {
/** 规则树ID */
private String treeId;
/** 规则树名称 */
private String treeName;
/** 规则树描述 */
private String treeDesc;
/** 规则根节点 */
private String treeRootRuleNode;
/** 规则节点 */
private Map<String, RuleTreeNodeVO> treeNodeMap;
}
package cn.bugstack.domain.strategy.model.valobj;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import cn.bugstack.types.common.Constants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
/**
* @ClassName: StrategyAwardRuleModelVO
* @Description:
* @Description: 抽奖策略规则规则值对象;值对象,没有唯一ID,仅限于从数据库查询对象
* @Author: zhaoyongfeng
* @Date: 2024/11/18 20:39
* @Date: 2024/11/21 0:05
*/
@Getter
@Builder
......@@ -23,38 +17,5 @@ import java.util.List;
@NoArgsConstructor
public class StrategyAwardRuleModelVO {
private String ruleModels;
/**
* 获取抽奖中规则;或者使用 lambda 表达式
* <p>
* List<String> ruleModelList = Arrays.stream(ruleModels.split(Constants.SPLIT))
* .filter(DefaultLogicFactory.LogicModel::isCenter)
* .collect(Collectors.toList());
* return ruleModelList;
* <p>
* List<String> collect = Arrays.stream(ruleModelValues).filter(DefaultLogicFactory.LogicModel::isCenter).collect(Collectors.toList());
*/
public String[] raffleCenterRuleModelList() {
List<String> ruleModelList = new ArrayList<>();
String[] ruleModelValues = ruleModels.split(Constants.SPLIT);
for (String ruleModelValue : ruleModelValues) {
if (DefaultLogicFactory.LogicModel.isCenter(ruleModelValue)) {
ruleModelList.add(ruleModelValue);
}
}
return ruleModelList.toArray(new String[0]);
}
public String[] raffleAfterRuleModelList() {
List<String> ruleModelList = new ArrayList<>();
String[] ruleModelValues = ruleModels.split(Constants.SPLIT);
for (String ruleModelValue : ruleModelValues) {
if (DefaultLogicFactory.LogicModel.isAfter(ruleModelValue)) {
ruleModelList.add(ruleModelValue);
}
}
return ruleModelList.toArray(new String[0]);
}
}
package cn.bugstack.domain.strategy.model.entity;
package cn.bugstack.domain.strategy.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.units.qual.A;
/**
* @ClassName: RuleMatterEntity
* @Description: 规则物料实体对象,用于过滤规则的必要参数信息
* @ClassName: StrategyAwardStockKeyVO
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:26
* @Date: 2024/11/21 13:20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleMatterEntity {
/** 用户ID */
private String userId;
/** 策略ID */
public class StrategyAwardStockKeyVO {
//策略ID
private Long strategyId;
/** 抽奖奖品ID【规则类型为策略,则不需要奖品ID】 */
// 奖品ID
private Integer awardId;
/** 抽奖规则类型【rule_random - 随机值计算、rule_lock - 抽奖几次后解锁、rule_luck_award - 幸运奖(兜底奖品)】 */
private String ruleModel;
}
......@@ -3,7 +3,9 @@ package cn.bugstack.domain.strategy.repository;
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.model.valobj.RuleTreeVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardRuleModelVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
import java.util.List;
import java.util.Map;
......@@ -29,4 +31,48 @@ public interface IStrategyRepository {
StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId);
/**
* 根据规则树ID,查询树结构信息
*
* @param treeId 规则树ID
* @return 树结构信息
*/
RuleTreeVO queryRuleTreeVOByTreeId(String treeId);
/**
* 缓存奖品库存
*
* @param cacheKey key
* @param awardCount 库存值
*/
void cacheStrategyAwardCount(String cacheKey, Integer awardCount);
/**
* 缓存key,decr 方式扣减库存
*
* @param cacheKey 缓存Key
* @return 扣减结果
*/
Boolean subtractionAwardStock(String cacheKey);
/**
* 写入奖品库存消费队列
*
* @param strategyAwardStockKeyVO 对象值对象
*/
void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO);
/**
* 获取奖品库存消费队列
*/
StrategyAwardStockKeyVO takeQueueValue() throws InterruptedException;
/**
* 更新奖品库存消耗
*
* @param strategyId 策略ID
* @param awardId 奖品ID
*/
void updateStrategyAwardStock(Long strategyId, Integer awardId);
}
package cn.bugstack.domain.strategy.service.annotation;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: LogicStrategy
* @Description: 策略自定义枚举
* @Author: zhaoyongfeng
* @Date: 2024/11/17 16:02
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {
DefaultLogicFactory.LogicModel logicMode();
}
package cn.bugstack.domain.strategy.service.armory;
/*
* @return
* @author zhaoyongfeng
* @description 策略装配库(兵工厂),负责初始化策略计算
*
* @param null
*/
public interface IStrategyArmory {
/**
......
package cn.bugstack.domain.strategy.service.armory;
/*
* @return
* @author zhaoyongfeng
* @description 策略抽奖调度
*
* @param null
*/
public interface IStrategyDispatch {
/**
* 获取抽奖策略装配的随机结果
......@@ -9,6 +15,29 @@ public interface IStrategyDispatch {
*/
Integer getRandomAwardId(Long strategyId);
/**
* 获取抽奖策略装配的随机结果
*
* @param strategyId 权重ID
* @return 抽奖结果
*/
Integer getRandomAwardId(Long strategyId, String ruleWeightValue);
/**
* 获取抽奖策略装配的随机结果
*
* @param key = strategyId + _ + ruleWeightValue;
* @return 抽奖结果
*/
Integer getRandomAwardId(String key);
/**
* 根据策略ID和奖品ID,扣减奖品缓存库存
*
* @param strategyId 策略ID
* @param awardId 奖品ID
* @return 扣减结果
*/
Boolean subtractionAwardStock(Long strategyId, Integer awardId);
}
......@@ -4,6 +4,7 @@ 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 lombok.extern.slf4j.Slf4j;
......@@ -27,32 +28,52 @@ public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch
@Resource
private IStrategyRepository repository;
private final SecureRandom secureRandom = new SecureRandom();
@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.getAwardCount();
cacheStrategyAwardCount(strategyId, awardId, awardCount);
}
// 3.1 默认装配配置【全量抽奖概率】
assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntities);
// 2. 权重策略配置 - 适用于 rule_weight 权重规则配置
// 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();
Set<String> keys = ruleWeightValueMap.keySet();
for (String key : keys) {
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("_").concat(key), strategyAwardEntitiesClone);
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. 获取最小概率值
......@@ -61,37 +82,57 @@ public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
// 2. 获取概率值总和
BigDecimal totalAwardRate = strategyAwardEntities.stream()
.map(StrategyAwardEntity::getAwardRate)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 3. 用 1 % 0.0001 获得概率范围,百分位、千分位、万分位
BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);
// 2. 循环计算找到概率范围值
BigDecimal rateRange = BigDecimal.valueOf(convert(minAwardRate.doubleValue()));
// 4. 生成策略奖品概率查找表「这里指需要在list集合中,存放上对应的奖品占位即可,占位越多等于概率越高」
// 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).setScale(0, RoundingMode.CEILING).intValue(); i++) {
for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {
strategyAwardSearchRateTables.add(awardId);
}
}
// 5. 对存储的奖品进行乱序操作
// 4. 对存储的奖品进行乱序操作
Collections.shuffle(strategyAwardSearchRateTables);
// 6. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
// 5. 生成出Map集合,key值,对应的就是后续的概率值。通过概率来获得对应的奖品ID
Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i));
}
// 7. 存放到 Redis
// 6. 存放到 Redis
repository.storeStrategyAwardSearchRateTable(key, shuffleStrategyAwardSearchRateTable.size(), shuffleStrategyAwardSearchRateTable);
}
/**
* 转换计算,只根据小数位来计算。如【0.01返回100】、【0.009返回1000】、【0.0018返回10000】
*/
private double convert(double min) {
double current = min;
double max = 1;
while (current < 1) {
current = current * 10;
max = max * 10;
}
return max;
}
/**
* 缓存奖品库存到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
public Integer getRandomAwardId(Long strategyId) {
......@@ -103,11 +144,25 @@ public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch
@Override
public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
String key = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
String key = String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(ruleWeightValue);
return getRandomAwardId(key);
}
@Override
public Integer getRandomAwardId(String key) {
// 分布式部署下,不一定为当前应用做的策略装配。也就是值不一定会保存到本应用,而是分布式应用,所以需要从 Redis 中获取。
int rateRange = repository.getRateRange(key);
// 通过生成的随机值,获取概率值奖品查找表的结果
return repository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
return repository.getStrategyAwardAssemble(key, secureRandom.nextInt(rateRange));
}
@Override
public Boolean subtractionAwardStock(Long strategyId, Integer awardId) {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
return repository.subtractionAwardStock(cacheKey);
}
......
package cn.bugstack.domain.strategy.service.raffle;
import cn.bugstack.domain.strategy.AbstractRaffleStrategy;
import cn.bugstack.domain.strategy.model.entity.RaffleFactorEntity;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.model.valobj.RuleTreeVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardRuleModelVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.chain.ILogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import cn.bugstack.domain.strategy.service.rule.tree.factory.engine.IDecisionTreeEngine;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @ClassName: DefaultRaffleStrategy
* @Description: 默认的抽奖策略实现
......@@ -30,41 +23,41 @@ import java.util.stream.Collectors;
@Service
public class DefaultRaffleStrategy extends AbstractRaffleStrategy {
@Resource
private DefaultLogicFactory logicFactory;
public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory) {
super(repository, strategyDispatch, defaultChainFactory);
public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory, DefaultTreeFactory defaultTreeFactory) {
super(repository, strategyDispatch, defaultChainFactory, defaultTreeFactory);
}
@Override
protected RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics) {
if (logics == null || 0 == logics.length) return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
public DefaultChainFactory.StrategyAwardVO raffleLogicChain(String userId, Long strategyId) {
ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
return logicChain.logic(userId, strategyId);
}
Map<String, ILogicFilter<RuleActionEntity.RaffleCenterEntity>> logicFilterGroup = logicFactory.openLogicFilter();
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionEntity = null;
for (String ruleModel : logics) {
ILogicFilter<RuleActionEntity.RaffleCenterEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(raffleFactorEntity.getAwardId());
ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId());
ruleMatterEntity.setRuleModel(ruleModel);
ruleActionEntity = logicFilter.filter(ruleMatterEntity);
// 非放行结果则顺序过滤
log.info("抽奖中规则过滤 userId: {} ruleModel: {} code: {} info: {}", raffleFactorEntity.getUserId(), ruleModel, ruleActionEntity.getCode(), ruleActionEntity.getInfo());
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) return ruleActionEntity;
@Override
public DefaultTreeFactory.StrategyAwardVO raffleLogicTree(String userId, Long strategyId, Integer awardId) {
StrategyAwardRuleModelVO strategyAwardRuleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
if (null == strategyAwardRuleModelVO) {
return DefaultTreeFactory.StrategyAwardVO.builder().awardId(awardId).build();
}
return ruleActionEntity;
RuleTreeVO ruleTreeVO = repository.queryRuleTreeVOByTreeId(strategyAwardRuleModelVO.getRuleModels());
if (null == ruleTreeVO) {
throw new RuntimeException("存在抽奖策略配置的规则模型 Key,未在库表 rule_tree、rule_tree_node、rule_tree_line 配置对应的规则树信息 " + strategyAwardRuleModelVO.getRuleModels());
}
IDecisionTreeEngine treeEngine = defaultTreeFactory.openLogicTree(ruleTreeVO);
return treeEngine.process(userId, strategyId, awardId);
}
@Override
public StrategyAwardStockKeyVO takeQueueValue() throws InterruptedException {
return repository.takeQueueValue();
}
@Override
public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
repository.updateStrategyAwardStock(strategyId, awardId);
}
}
package cn.bugstack.domain.strategy.service.rule.chain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
/*
* @return
* @author zhaoyongfeng
......@@ -15,7 +18,7 @@ public interface ILogicChain extends ILogicChainArmory{
* @param strategyId 策略ID
* @return 奖品ID
*/
Integer logic(String userId, Long strategyId);
DefaultChainFactory.StrategyAwardVO logic(String userId, Long strategyId);
}
......@@ -3,6 +3,7 @@ package cn.bugstack.domain.strategy.service.rule.chain.factory;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.rule.chain.ILogicChain;
import lombok.*;
import org.springframework.stereotype.Service;
import java.util.Map;
......@@ -50,5 +51,30 @@ public class DefaultChainFactory {
return logicChain;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class StrategyAwardVO {
/** 抽奖奖品ID - 内部流转使用 */
private Integer awardId;
/** */
private String logicModel;
}
@Getter
@AllArgsConstructor
public enum LogicModel {
RULE_DEFAULT("rule_default", "默认抽奖"),
RULE_BLACKLIST("rule_blacklist", "黑名单抽奖"),
RULE_WEIGHT("rule_weight", "权重规则"),
;
private final String code;
private final String info;
}
}
......@@ -2,6 +2,7 @@ package cn.bugstack.domain.strategy.service.rule.chain.impl;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -21,7 +22,7 @@ public class BackListLogicChain extends AbstractLogicChain {
private IStrategyRepository repository;
@Override
public Integer logic(String userId, Long strategyId) {
public DefaultChainFactory.StrategyAwardVO logic(String userId, Long strategyId) {
log.info("抽奖责任链-黑名单开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
// 查询规则值配置
......@@ -34,8 +35,12 @@ public class BackListLogicChain extends AbstractLogicChain {
for (String userBlackId : userBlackIds) {
if (userId.equals(userBlackId)) {
log.info("抽奖责任链-黑名单接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
return DefaultChainFactory.StrategyAwardVO.builder()
.awardId(awardId)
.logicModel(ruleModel())
.build();
}
}
// 过滤其他责任链
......@@ -45,8 +50,9 @@ public class BackListLogicChain extends AbstractLogicChain {
@Override
protected String ruleModel() {
return "rule_blacklist";
return DefaultChainFactory.LogicModel.RULE_BLACKLIST.getCode();
}
}
......@@ -2,6 +2,7 @@ package cn.bugstack.domain.strategy.service.rule.chain.impl;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -21,14 +22,19 @@ public class DefaultLogicChain extends AbstractLogicChain {
protected IStrategyDispatch strategyDispatch;
@Override
public Integer logic(String userId, Long strategyId) {
public DefaultChainFactory.StrategyAwardVO logic(String userId, Long strategyId) {
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
log.info("抽奖责任链-默认处理 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
return DefaultChainFactory.StrategyAwardVO.builder()
.awardId(awardId)
.logicModel(ruleModel())
.build();
}
@Override
protected String ruleModel() {
return "default";
return DefaultChainFactory.LogicModel.RULE_DEFAULT.getCode();
}
}
......@@ -3,6 +3,7 @@ package cn.bugstack.domain.strategy.service.rule.chain.impl;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.chain.AbstractLogicChain;
import cn.bugstack.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -22,11 +23,13 @@ public class RuleWeightLogicChain extends AbstractLogicChain {
@Resource
private IStrategyRepository repository;
public Long userScore = 4500L;
@Resource
protected IStrategyDispatch strategyDispatch;
// 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
public Long userScore = 0L;
// 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
......@@ -36,7 +39,7 @@ public class RuleWeightLogicChain extends AbstractLogicChain {
* 2. 解析数据格式;判断哪个范围符合用户的特定抽奖范围
*/
@Override
public Integer logic(String userId, Long strategyId) {
public DefaultChainFactory.StrategyAwardVO logic(String userId, Long strategyId) {
log.info("抽奖责任链-权重开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
String ruleValue = repository.queryStrategyRuleValue(strategyId, ruleModel());
......@@ -73,9 +76,13 @@ public class RuleWeightLogicChain extends AbstractLogicChain {
if (null != nextValue) {
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, analyticalValueGroup.get(nextValue));
log.info("抽奖责任链-权重接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
return DefaultChainFactory.StrategyAwardVO.builder()
.awardId(awardId)
.logicModel(ruleModel())
.build();
}
// 5. 过滤其他责任链
log.info("抽奖责任链-权重放行 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
return next().logic(userId, strategyId);
......@@ -83,9 +90,10 @@ public class RuleWeightLogicChain extends AbstractLogicChain {
@Override
protected String ruleModel() {
return "rule_weight";
return DefaultChainFactory.LogicModel.RULE_WEIGHT.getCode();
}
private Map<Long, String> getAnalyticalValue(String ruleValue) {
String[] ruleValueGroups = ruleValue.split(Constants.SPACE);
Map<Long, String> ruleValueMap = new HashMap<>();
......
package cn.bugstack.domain.strategy.service.rule.filter;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
/*
* @return
* @author zhaoyongfeng
* @description 抽奖规则过滤接口
*
* @param null
*/
public interface ILogicFilter<T extends RuleActionEntity.RaffleEntity> {
RuleActionEntity<T> filter(RuleMatterEntity ruleMatterEntity);
}
package cn.bugstack.domain.strategy.service.rule.filter.factory;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.service.annotation.LogicStrategy;
import cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: DefaultLogicFactory
* @Description: 规则工厂
* @Author: zhaoyongfeng
* @Date: 2024/11/18 22:27
*/
@Service
public class DefaultLogicFactory {
public Map<String, ILogicFilter<?>> logicFilterMap = new ConcurrentHashMap<>();
public DefaultLogicFactory(List<ILogicFilter<?>> logicFilters) {
logicFilters.forEach(logic -> {
LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class);
if (null != strategy) {
logicFilterMap.put(strategy.logicMode().getCode(), logic);
}
});
}
public <T extends RuleActionEntity.RaffleEntity> Map<String, ILogicFilter<T>> openLogicFilter() {
return (Map<String, ILogicFilter<T>>) (Map<?, ?>) logicFilterMap;
}
@Getter
@AllArgsConstructor
public enum LogicModel {
RULE_LOCK("rule_lock", "【抽奖中规则】抽奖n次后,对应奖品可解锁抽奖", "center"),
RULE_LUCK_AWARD("rule_luck_award", "【抽奖后规则】抽奖n次后,对应奖品可解锁抽奖", "after"),
;
private final String code;
private final String info;
private final String type;
public static boolean isCenter(String code){
return "center".equals(LogicModel.valueOf(code.toUpperCase()).type);
}
public static boolean isAfter(String code){
return "after".equals(LogicModel.valueOf(code.toUpperCase()).type);
}
}
}
package cn.bugstack.domain.strategy.service.rule.filter.impl;
import cn.bugstack.domain.strategy.model.entity.RuleActionEntity;
import cn.bugstack.domain.strategy.model.entity.RuleMatterEntity;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.annotation.LogicStrategy;
import cn.bugstack.domain.strategy.service.rule.filter.ILogicFilter;
import cn.bugstack.domain.strategy.service.rule.filter.factory.DefaultLogicFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: RuleLockLogicFilter
* @Description: 用户抽奖n次后,对应奖品可解锁抽奖
* @Author: zhaoyongfeng
* @Date: 2024/11/18 22:27
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_LOCK)
public class RuleLockLogicFilter implements ILogicFilter<RuleActionEntity.RaffleCenterEntity> {
@Resource
private IStrategyRepository repository;
// 用户抽奖次数,后续完成这部分流程开发的时候,从数据库/Redis中读取
private Long userRaffleCount = 0L;
@Override
public RuleActionEntity<RuleActionEntity.RaffleCenterEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-次数锁 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
// 查询规则值配置;当前奖品ID,抽奖中规则对应的校验值。如;1、2、6
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
long raffleCount = Long.parseLong(ruleValue);
// 用户抽奖次数大于规则限定值,规则放行
if (userRaffleCount>= raffleCount) {
return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
// 用户抽奖次数小于规则限定值,规则拦截
return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
}
package cn.bugstack.domain.strategy.service.rule.tree;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
/*
* @return
* @author zhaoyongfeng
* @description 规则数接口
*
* @param null
*/
public interface ILogicTreeNode {
DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId, String ruleValue);
}
package cn.bugstack.domain.strategy.service.rule.tree.factory;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.model.valobj.RuleTreeVO;
import cn.bugstack.domain.strategy.service.rule.tree.ILogicTreeNode;
import cn.bugstack.domain.strategy.service.rule.tree.factory.engine.IDecisionTreeEngine;
import cn.bugstack.domain.strategy.service.rule.tree.factory.engine.impl.DecisionTreeEngine;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @ClassName: DefaultTreeFactory
* @Description: 规则树工厂
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:03
*/
@Service
public class DefaultTreeFactory {
private final Map<String, ILogicTreeNode> logicTreeNodeGroup;
public DefaultTreeFactory(Map<String, ILogicTreeNode> logicTreeNodeGroup) {
this.logicTreeNodeGroup = logicTreeNodeGroup;
}
public IDecisionTreeEngine openLogicTree(RuleTreeVO ruleTreeVO){
return new DecisionTreeEngine(logicTreeNodeGroup,ruleTreeVO);
}
/**
* 决策树动作
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TreeActionEntity {
private RuleLogicCheckTypeVO ruleLogicCheckType;
private StrategyAwardVO strategyAwardVO;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class StrategyAwardVO {
/** 抽奖奖品ID - 内部流转使用 */
private Integer awardId;
/** 抽奖奖品规则 */
private String awardRuleValue;
}
}
package cn.bugstack.domain.strategy.service.rule.tree.factory.engine;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
public interface IDecisionTreeEngine {
DefaultTreeFactory.StrategyAwardVO process(String userId, Long strategyId, Integer awardId);
}
package cn.bugstack.domain.strategy.service.rule.tree.factory.engine.impl;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.model.valobj.RuleTreeNodeLineVO;
import cn.bugstack.domain.strategy.model.valobj.RuleTreeNodeVO;
import cn.bugstack.domain.strategy.model.valobj.RuleTreeVO;
import cn.bugstack.domain.strategy.service.rule.tree.ILogicTreeNode;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import cn.bugstack.domain.strategy.service.rule.tree.factory.engine.IDecisionTreeEngine;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* @ClassName: DecisionTreeEngine
* @Description: 决策树引擎
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:22
*/
@Slf4j
public class DecisionTreeEngine implements IDecisionTreeEngine {
private final Map<String, ILogicTreeNode> logicTreeNodeGroup;
private final RuleTreeVO ruleTreeVO;
public DecisionTreeEngine(Map<String, ILogicTreeNode> logicTreeNodeGroup, RuleTreeVO ruleTreeVO) {
this.logicTreeNodeGroup = logicTreeNodeGroup;
this.ruleTreeVO = ruleTreeVO;
}
@Override
public DefaultTreeFactory.StrategyAwardVO process(String userId, Long strategyId, Integer awardId) {
DefaultTreeFactory.StrategyAwardVO strategyAwardData = null;
// 获取基础信息
String nextNode = ruleTreeVO.getTreeRootRuleNode();
Map<String, RuleTreeNodeVO> treeNodeMap = ruleTreeVO.getTreeNodeMap();
// 获取起始节点「根节点记录了第一个要执行的规则」
RuleTreeNodeVO ruleTreeNode = treeNodeMap.get(nextNode);
while (null != nextNode) {
// 获取决策节点
ILogicTreeNode logicTreeNode = logicTreeNodeGroup.get(ruleTreeNode.getRuleKey());
String ruleValue = ruleTreeNode.getRuleValue();
// 决策节点计算
DefaultTreeFactory.TreeActionEntity logicEntity = logicTreeNode.logic(userId, strategyId, awardId, ruleValue);
RuleLogicCheckTypeVO ruleLogicCheckTypeVO = logicEntity.getRuleLogicCheckType();
strategyAwardData = logicEntity.getStrategyAwardVO();
log.info("决策树引擎【{}】treeId:{} node:{} code:{}", ruleTreeVO.getTreeName(), ruleTreeVO.getTreeId(), nextNode, ruleLogicCheckTypeVO.getCode());
// 获取下个节点
nextNode = nextNode(ruleLogicCheckTypeVO.getCode(), ruleTreeNode.getTreeNodeLineVOList());
ruleTreeNode = treeNodeMap.get(nextNode);
}
// 返回最终结果
return strategyAwardData;
}
public String nextNode(String matterValue, List<RuleTreeNodeLineVO> treeNodeLineVOList) {
if (null == treeNodeLineVOList || treeNodeLineVOList.isEmpty()) return null;
for (RuleTreeNodeLineVO nodeLine : treeNodeLineVOList) {
if (decisionLogic(matterValue, nodeLine)) {
return nodeLine.getRuleNodeTo();
}
}
throw new RuntimeException("决策树引擎,nextNode 计算失败,未找到可执行节点!");
}
public boolean decisionLogic(String matterValue, RuleTreeNodeLineVO nodeLine) {
switch (nodeLine.getRuleLimitType()) {
case EQUAL:
return matterValue.equals(nodeLine.getRuleLimitValue().getCode());
// 以下规则暂时不需要实现
case GT:
case LT:
case GE:
case LE:
default:
return false;
}
}
}
package cn.bugstack.domain.strategy.service.rule.tree.impl;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.service.rule.tree.ILogicTreeNode;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @ClassName: RuleLockLogicTreeNode
* @Description: 次数锁节点
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:16
*/
@Slf4j
@Component("rule_lock")
public class RuleLockLogicTreeNode implements ILogicTreeNode {
// 用户抽奖次数,后续完成这部分流程开发的时候,从数据库/Redis中读取
private Long userRaffleCount = 10L;
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId, String ruleValue) {
log.info("规则过滤-次数锁 userId:{} strategyId:{} awardId:{}", userId, strategyId, awardId);
long raffleCount = 0L;
try {
raffleCount = Long.parseLong(ruleValue);
} catch (Exception e) {
throw new RuntimeException("规则过滤-次数锁异常 ruleValue: " + ruleValue + " 配置不正确");
}
// 用户抽奖次数大于规则限定值,规则放行
if (userRaffleCount >= raffleCount) {
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.ALLOW)
.build();
}
// 用户抽奖次数小于规则限定值,规则拦截
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.TAKE_OVER)
.build();
}
}
package cn.bugstack.domain.strategy.service.rule.tree.impl;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.service.rule.tree.ILogicTreeNode;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @ClassName: RuleLuckAwardLogic
* @Description: 兜底奖励节点
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:16
*/
@Slf4j
@Component("rule_luck_award")
public class RuleLuckAwardLogicTreeNode implements ILogicTreeNode {
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId, String ruleValue) {
log.info("规则过滤-兜底奖品 userId:{} strategyId:{} awardId:{} ruleValue:{}", userId, strategyId, awardId, ruleValue);
String[] split = ruleValue.split(Constants.COLON);
if (split.length == 0) {
log.error("规则过滤-兜底奖品,兜底奖品未配置告警 userId:{} strategyId:{} awardId:{}", userId, strategyId, awardId);
throw new RuntimeException("兜底奖品未配置 " + ruleValue);
}
// 兜底奖励配置
Integer luckAwardId = Integer.valueOf(split[0]);
String awardRuleValue = split.length > 1 ? split[1] : "";
// 返回兜底奖品
log.info("规则过滤-兜底奖品 userId:{} strategyId:{} awardId:{} awardRuleValue:{}", userId, strategyId, luckAwardId, awardRuleValue);
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.TAKE_OVER)
.strategyAwardVO(DefaultTreeFactory.StrategyAwardVO.builder()
.awardId(luckAwardId)
.awardRuleValue(awardRuleValue)
.build())
.build();
}
}
package cn.bugstack.domain.strategy.service.rule.tree.impl;
import cn.bugstack.domain.strategy.model.valobj.RuleLogicCheckTypeVO;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.domain.strategy.service.armory.IStrategyDispatch;
import cn.bugstack.domain.strategy.service.rule.tree.ILogicTreeNode;
import cn.bugstack.domain.strategy.service.rule.tree.factory.DefaultTreeFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: RuleStockLogicTreeNode
* @Description: 库存扣减节点sda
* @Author: zhaoyongfeng
* @Date: 2024/11/20 12:16
*/
@Slf4j
@Component("rule_stock")
public class RuleStockLogicTreeNode implements ILogicTreeNode {
@Resource
private IStrategyDispatch strategyDispatch;
@Resource
private IStrategyRepository strategyRepository;
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId, String ruleValue) {
log.info("规则过滤-库存扣减 userId:{} strategyId:{} awardId:{}", userId, strategyId, awardId);
// 扣减库存
Boolean status = strategyDispatch.subtractionAwardStock(strategyId, awardId);
// true;库存扣减成功,TAKE_OVER 规则节点接管,返回奖品ID,奖品规则配置
if(status){
log.info("规则过滤-库存扣减-成功 userId:{} strategyId:{} awardId:{}", userId, strategyId, awardId);
// 写入延迟队列,延迟消费更新数据库记录。【在trigger的job;UpdateAwardStockJob 下消费队列,更新数据库记录】
strategyRepository.awardStockConsumeSendQueue(StrategyAwardStockKeyVO.builder()
.strategyId(strategyId)
.awardId(awardId)
.build());
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.TAKE_OVER)
.strategyAwardVO(DefaultTreeFactory.StrategyAwardVO.builder()
.awardId(awardId)
.awardRuleValue(ruleValue)
.build())
.build();
}
// 如果库存不足,则直接返回放行
log.warn("规则过滤-库存扣减-告警,库存不足。userId:{} strategyId:{} awardId:{}", userId, strategyId, awardId);
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.ALLOW)
.build();
}
}
package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.RuleTree;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IRuleTreeDao {
RuleTree queryRuleTreeByTreeId(String treeId);
}
package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.RuleTreeNode;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface IRuleTreeNodeDao {
List<RuleTreeNode> queryRuleTreeNodeListByTreeId(String treeId);
}
package cn.bugstack.infrastructure.persistent.dao;
import cn.bugstack.infrastructure.persistent.po.RuleTreeNodeLine;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface IRuleTreeNodeLineDao {
List<RuleTreeNodeLine> queryRuleTreeNodeLineListByTreeId(String treeId);
}
......@@ -11,6 +11,7 @@ public interface IStrategyAwardDao {
List<StrategyAward> queryStrategyAwardListByStrategyId(Long strategyId);
String queryStrategyAwardRuleModels(StrategyAward strategyAward);
void updateStrategyAwardStock(StrategyAward strategyAward);
}
package cn.bugstack.infrastructure.persistent.po;
import lombok.Data;
import java.util.Date;
/**
* @ClassName: RuleTree
* @Description: 规则树
* @Author: zhaoyongfeng
* @Date: 2024/11/20 13:29
*/
@Data
public class RuleTree {
/** 自增ID */
private Long id;
/** 规则树ID */
private String treeId;
/** 规则树名称 */
private String treeName;
/** 规则树描述 */
private String treeDesc;
/** 规则根节点 */
private String treeRootRuleKey;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
}
package cn.bugstack.infrastructure.persistent.po;
import lombok.Data;
import java.util.Date;
/**
* @ClassName: RuleTreeNode
* @Description: 规则树节点
* @Author: zhaoyongfeng
* @Date: 2024/11/20 13:30
*/
@Data
public class RuleTreeNode {
/** 自增ID */
private Long id;
/** 规则树ID */
private String treeId;
/** 规则Key */
private String ruleKey;
/** 规则描述 */
private String ruleDesc;
/** 规则比值 */
private String ruleValue;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
}
package cn.bugstack.infrastructure.persistent.po;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @ClassName: RuleTreeNodeList
* @Description:
* @Author: zhaoyongfeng
* @Date: 2024/11/20 13:30
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeNodeLine {
/** 自增ID */
private Long id;
/** 规则树ID */
private String treeId;
/** 规则Key节点 From */
private String ruleNodeFrom;
/** 规则Key节点 To */
private String ruleNodeTo;
/** 限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围] */
private String ruleLimitType;
/** 限定值(到下个节点) */
private String ruleLimitValue;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
}
......@@ -83,7 +83,7 @@ public interface IRedisService {
* 自减 Key 的值;1、2、3、4
*
* @param key 键
* @return 自后的值
* @return 自后的值
*/
long decr(String key);
......@@ -91,7 +91,7 @@ public interface IRedisService {
* 指定值,自增 Key 的值;1、2、3、4
*
* @param key 键
* @return 自后的值
* @return 自后的值
*/
long decrBy(String key, long delta);
......@@ -247,5 +247,22 @@ public interface IRedisService {
* @return 返回结果
*/
<T> RBloomFilter<T> getBloomFilter(String key);
/**
* 设置值
*
* @param key key 键
* @param value 值
*/
void setAtomicLong(String key, long value);
/**
* 获取值
*
* @param key key 键
*/
Long getAtomicLong(String key);
Boolean setNx(String key);
}
......@@ -157,5 +157,20 @@ public class RedissonService implements IRedisService {
return redissonClient.getBloomFilter(key);
}
@Override
public void setAtomicLong(String key, long value) {
redissonClient.getAtomicLong(key).set(value);
}
@Override
public Long getAtomicLong(String key) {
return redissonClient.getAtomicLong(key).get();
}
@Override
public Boolean setNx(String key) {
return redissonClient.getBucket(key).trySet("lock");
}
}
......@@ -4,22 +4,23 @@ package cn.bugstack.infrastructure.persistent.repository;
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.model.valobj.StrategyAwardRuleModelVO;
import cn.bugstack.domain.strategy.model.valobj.*;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.infrastructure.persistent.dao.IStrategyAwardDao;
import cn.bugstack.infrastructure.persistent.dao.IStrategyDao;
import cn.bugstack.infrastructure.persistent.dao.IStrategyRuleDao;
import cn.bugstack.infrastructure.persistent.po.Strategy;
import cn.bugstack.infrastructure.persistent.po.StrategyAward;
import cn.bugstack.infrastructure.persistent.po.StrategyRule;
import cn.bugstack.infrastructure.persistent.dao.*;
import cn.bugstack.infrastructure.persistent.po.*;
import cn.bugstack.infrastructure.persistent.redis.IRedisService;
import cn.bugstack.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
......@@ -28,6 +29,7 @@ import java.util.Map;
* @Author: zhaoyongfeng
* @Date: 2024/11/14 19:27
*/
@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {
......@@ -39,6 +41,13 @@ public class StrategyRepository implements IStrategyRepository {
private IStrategyAwardDao strategyAwardDao;
@Resource
private IRedisService redisService;
@Resource
private IRuleTreeDao ruleTreeDao;
@Resource
private IRuleTreeNodeDao ruleTreeNodeDao;
@Resource
private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
@Override
public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
......@@ -140,10 +149,116 @@ public class StrategyRepository implements IStrategyRepository {
strategyAward.setStrategyId(strategyId);
strategyAward.setAwardId(awardId);
String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
if (null == ruleModels) return null;
return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();
}
@Override
public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {
// 优先从缓存获取
String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
RuleTreeVO ruleTreeVOCache = redisService.getValue(cacheKey);
if (null != ruleTreeVOCache) return ruleTreeVOCache;
// 从数据库获取
RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);
// 1. tree node line 转换Map结构
Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = new HashMap<>();
for (RuleTreeNodeLine ruleTreeNodeLine : ruleTreeNodeLines) {
RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
.treeId(ruleTreeNodeLine.getTreeId())
.ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())
.ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())
.ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))
.ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))
.build();
List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList = ruleTreeNodeLineMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(), k -> new ArrayList<>());
ruleTreeNodeLineVOList.add(ruleTreeNodeLineVO);
}
// 2. tree node 转换为Map结构
Map<String, RuleTreeNodeVO> treeNodeMap = new HashMap<>();
for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
.treeId(ruleTreeNode.getTreeId())
.ruleKey(ruleTreeNode.getRuleKey())
.ruleDesc(ruleTreeNode.getRuleDesc())
.ruleValue(ruleTreeNode.getRuleValue())
.treeNodeLineVOList(ruleTreeNodeLineMap.get(ruleTreeNode.getRuleKey()))
.build();
treeNodeMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
}
// 3. 构建 Rule Tree
RuleTreeVO ruleTreeVODB = RuleTreeVO.builder()
.treeId(ruleTree.getTreeId())
.treeName(ruleTree.getTreeName())
.treeDesc(ruleTree.getTreeDesc())
.treeRootRuleNode(ruleTree.getTreeRootRuleKey())
.treeNodeMap(treeNodeMap)
.build();
redisService.setValue(cacheKey, ruleTreeVODB);
return ruleTreeVODB;
}
@Override
public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
if (redisService.isExists(cacheKey)) return;
redisService.setAtomicLong(cacheKey, awardCount);
}
@Override
public Boolean subtractionAwardStock(String cacheKey) {
long surplus = redisService.decr(cacheKey);
if (surplus < 0) {
// 库存小于0,恢复为0个
redisService.setValue(cacheKey, 0);
return false;
}
// 1. 按照cacheKey decr 后的值,如 99、98、97 和 key 组成为库存锁的key进行使用。
// 2. 加锁为了兜底,如果后续有恢复库存,手动处理等,也不会超卖。因为所有的可用库存key,都被加锁了。
String lockKey = cacheKey + Constants.UNDERLINE + surplus;
Boolean lock = redisService.setNx(lockKey);
if (!lock) {
log.info("策略奖品库存加锁失败 {}", lockKey);
}
return lock;
}
@Override
public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);
}
@Override
public StrategyAwardStockKeyVO takeQueueValue() throws InterruptedException {
String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
RBlockingQueue<StrategyAwardStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
return destinationQueue.poll();
}
@Override
public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
StrategyAward strategyAward = new StrategyAward();
strategyAward.setStrategyId(strategyId);
strategyAward.setAwardId(awardId);
strategyAwardDao.updateStrategyAwardStock(strategyAward);
}
}
package cn.bugstack.trigger.job;
import cn.bugstack.domain.strategy.model.valobj.StrategyAwardStockKeyVO;
import cn.bugstack.domain.strategy.IRaffleStock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @ClassName: UpdateAwardStockJob
* @Description: 更新奖品库存任务;为了不让更新库存的压力打到数据库中,这里采用了redis更新缓存库存,异步队列更新数据库,数据库表最终一致即可。
* @Author: zhaoyongfeng
* @Date: 2024/11/21 13:22
*/
@Slf4j
@Component()
public class UpdateAwardStockJob {
@Resource
private IRaffleStock raffleStock;
@Scheduled(cron = "0/5 * * * * ?")
public void exec() {
try {
log.info("定时任务,更新奖品消耗库存【延迟队列获取,降低对数据库的更新频次,不要产生竞争】");
StrategyAwardStockKeyVO strategyAwardStockKeyVO = raffleStock.takeQueueValue();
if (null == strategyAwardStockKeyVO) return;
log.info("定时任务,更新奖品消耗库存 strategyId:{} awardId:{}", strategyAwardStockKeyVO.getStrategyId(), strategyAwardStockKeyVO.getAwardId());
raffleStock.updateStrategyAwardStock(strategyAwardStockKeyVO.getStrategyId(), strategyAwardStockKeyVO.getAwardId());
} catch (Exception e) {
log.error("定时任务,更新奖品消耗库存失败", e);
}
}
}
......@@ -5,6 +5,7 @@ public class Constants {
public final static String SPLIT = ",";
public final static String SPACE = " ";
public final static String COLON = ":";
public final static String UNDERLINE = "_";
public static class RedisKey {
......@@ -12,6 +13,10 @@ public class Constants {
public static String STRATEGY_AWARD_KEY = "big_market_strategy_award_key_";
public static String STRATEGY_RATE_TABLE_KEY = "big_market_strategy_rate_table_key_";
public static String STRATEGY_RATE_RANGE_KEY = "big_market_strategy_rate_range_key_";
public static String RULE_TREE_VO_KEY = "rule_tree_vo_key_";
public static String STRATEGY_AWARD_COUNT_KEY = "strategy_award_count_key_";
public static String STRATEGY_AWARD_COUNT_QUERY_KEY = "strategy_award_count_query_key";
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册