first commit

This commit is contained in:
2026-09-21 19:21:35 +08:00
commit 6c944c4ffd
2733 changed files with 296956 additions and 0 deletions
@@ -0,0 +1,12 @@
package com.ruoyi.reward.contract.admin.request;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class AdjustRewardPoolRequest {
// tighten(抽水: bet→profit) | loosen(放水: profit→bet)
private String direction;
private BigDecimal amount;
}
@@ -0,0 +1,19 @@
package com.ruoyi.reward.contract.admin.response;
import com.ruoyi.reward.model.GloalRewardConfig;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class QueryGlobalRewardConfig {
private GloalRewardConfig config;
// 全局奖池数据
private BigDecimal totalWin;
private BigDecimal totalBet;
private BigDecimal netProfit;
private BigDecimal availableQuota;
private BigDecimal originalTotalBet;
private double rtp;
}
@@ -0,0 +1,68 @@
package com.ruoyi.reward.controller;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.reward.contract.admin.request.AdjustRewardPoolRequest;
import com.ruoyi.reward.contract.admin.response.QueryGlobalRewardConfig;
import com.ruoyi.reward.model.GloalRewardConfig;
import com.ruoyi.reward.service.GlobalRewardPoolService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
@Api(tags = "全局奖池配置")
@Slf4j
@RestController
@RequestMapping("/admin/globalrewardpool")
public class AdminGlobalRewardPoolController {
@Autowired
private GlobalRewardPoolService globalRewardPoolService;
@ApiOperation("获取奖池配置和状态")
@GetMapping("/config")
public R<QueryGlobalRewardConfig> getGameConfig() {
var config = globalRewardPoolService.getGameConfig();
var cache = globalRewardPoolService.getCache();
QueryGlobalRewardConfig result = new QueryGlobalRewardConfig();
result.setConfig(config);
result.setTotalWin(cache.getTotalWin());
result.setTotalBet(cache.getTotalBet());
result.setNetProfit(cache.getNetProfit());
result.setAvailableQuota(globalRewardPoolService.availableQuota());
result.setOriginalTotalBet(cache.getTotalBet().add(cache.getNetProfit()));
result.setRtp(globalRewardPoolService.rtp());
return R.ok(result);
}
@ApiOperation("更新奖池配置")
@PostMapping("/config")
public R<Void> updateGameConfig(@RequestBody GloalRewardConfig params) {
String msg = globalRewardPoolService.updateGameConfig(params);
if (msg != null && !msg.isEmpty())
return R.fail(msg);
return R.ok();
}
@ApiOperation("重置奖池缓存")
@PostMapping("/reset")
public R<?> resetRewardPool() {
globalRewardPoolService.resetCache();
return R.ok(null, "奖池缓存已清空");
}
@ApiOperation("调整奖池(抽水/放水)")
@PostMapping("/adjust")
public R<?> adjustRewardPool(@RequestBody AdjustRewardPoolRequest req) {
if (req.getAmount() == null || req.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
return R.fail("金额必须大于0");
}
if (!"tighten".equals(req.getDirection()) && !"loosen".equals(req.getDirection())) {
return R.fail("方向必须为 tighten 或 loosen");
}
globalRewardPoolService.adjust(req.getDirection(), req.getAmount());
return R.ok(null, "调整成功");
}
}
@@ -0,0 +1,9 @@
package com.ruoyi.reward.model;
import lombok.Data;
@Data
public class GloalRewardConfig {
// 利润率.抽水比例。 如0.3表示下注额的0.3倍将会存入网站,只有下注额的0.7倍才会放入奖池中
private double profitRate;
}
@@ -0,0 +1,12 @@
package com.ruoyi.reward.model.cache;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class GlobalRewardPoolCache {
private BigDecimal totalBet;
private BigDecimal totalWin;
private BigDecimal netProfit;
}
@@ -0,0 +1,56 @@
package com.ruoyi.reward.repository;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.reward.model.cache.GlobalRewardPoolCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Component
public class GlobalRewardPoolCacheRepository {
@Autowired
public RedisTemplate redisTemplate;
private final static String GLOBAL_REWARD_POOL_CACHE = "ruoyi:global:reward:pool";
public void incrementalProfit(BigDecimal profit) {
AsyncManager.me().run(()->{
redisTemplate.opsForValue().increment(GLOBAL_REWARD_POOL_CACHE + ":profit", profit.doubleValue());
});
}
public void incrementalBet(BigDecimal totalBet) {
redisTemplate.opsForValue().increment(GLOBAL_REWARD_POOL_CACHE + ":bet", totalBet.doubleValue());
}
public void incrementalWin(BigDecimal totalWin) {
redisTemplate.opsForValue().increment(GLOBAL_REWARD_POOL_CACHE + ":win", totalWin.doubleValue());
}
public GlobalRewardPoolCache getCache() {
var totalBetS = redisTemplate.opsForValue().get(GLOBAL_REWARD_POOL_CACHE + ":bet");
var totalWinS = redisTemplate.opsForValue().get(GLOBAL_REWARD_POOL_CACHE + ":win");
var totalProfitS = redisTemplate.opsForValue().get(GLOBAL_REWARD_POOL_CACHE + ":profit");
var cache = new GlobalRewardPoolCache();
cache.setTotalBet(totalBetS == null ? BigDecimal.ZERO : new BigDecimal(totalBetS.toString()));
cache.setTotalWin(totalWinS == null ? BigDecimal.ZERO : new BigDecimal(totalWinS.toString()));
cache.setNetProfit(totalProfitS == null ? BigDecimal.ZERO : new BigDecimal(totalProfitS.toString()));
return cache;
}
// 同步调整 bet 和 profit(允许变负)
public void adjustPool(BigDecimal betDelta, BigDecimal profitDelta) {
redisTemplate.opsForValue().increment(GLOBAL_REWARD_POOL_CACHE + ":bet", betDelta.doubleValue());
redisTemplate.opsForValue().increment(GLOBAL_REWARD_POOL_CACHE + ":profit", profitDelta.doubleValue());
}
// 清空奖池缓存(总下注、总返奖、累计抽水归零)
public void resetCache() {
redisTemplate.delete(GLOBAL_REWARD_POOL_CACHE + ":bet");
redisTemplate.delete(GLOBAL_REWARD_POOL_CACHE + ":win");
redisTemplate.delete(GLOBAL_REWARD_POOL_CACHE + ":profit");
}
}
@@ -0,0 +1,106 @@
package com.ruoyi.reward.service;
import com.ruoyi.common.utils.json.JsonUtils;
import com.ruoyi.reward.model.GloalRewardConfig;
import com.ruoyi.reward.model.cache.GlobalRewardPoolCache;
import com.ruoyi.reward.repository.GlobalRewardPoolCacheRepository;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
public class GlobalRewardPoolService {
private static final String CONFIG_KEY = "global.reward.setting";
@Autowired
private SysConfigMapper sysConfigMapper;
@Autowired
private GlobalRewardPoolCacheRepository poolCacheRepository;
public void incrementalBet(BigDecimal totalBet) {
BigDecimal profit = totalBet.abs().multiply(BigDecimal.valueOf(getGameConfig().getProfitRate()));
poolCacheRepository.incrementalProfit(profit);
poolCacheRepository.incrementalBet(totalBet.abs().subtract(profit));
}
public void incrementalWin(BigDecimal totalWin) {
poolCacheRepository.incrementalWin(totalWin.abs());
}
public GlobalRewardPoolCache getCache() {
return poolCacheRepository.getCache();
}
// 清空奖池缓存(总下注、总返奖归零)
public void resetCache() {
poolCacheRepository.resetCache();
}
// 手动调整奖池:tighten(抽水 bet→profit) | loosen(放水 profit→bet),允许变负
public void adjust(String direction, BigDecimal amount) {
if ("tighten".equals(direction)) {
poolCacheRepository.adjustPool(amount.negate(), amount);
} else {
poolCacheRepository.adjustPool(amount, amount.negate());
}
}
// 奖池净利润
public BigDecimal netProfits() {
var cache = poolCacheRepository.getCache();
return cache.getNetProfit();
}
// 奖池可用额度 totalBet - totalWin
public BigDecimal availableQuota() {
var cache = poolCacheRepository.getCache();
return cache.getTotalBet().subtract(cache.getTotalWin());
}
// 奖池rtp totalWin/totalBet
public double rtp() {
var cache = poolCacheRepository.getCache();
BigDecimal divisor = cache.getTotalBet().add(cache.getNetProfit());
if (divisor.compareTo(BigDecimal.ZERO) == 0) {
return 0;
}
return cache.getTotalWin().divide(divisor, 4, RoundingMode.HALF_UP)
.doubleValue();
}
public GloalRewardConfig getGameConfig() {
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique(CONFIG_KEY);
if (sysConfig == null) {
GloalRewardConfig config = new GloalRewardConfig();
config.setProfitRate(0.1);
return config;
}
return JsonUtils.fromSnakeCaseJson(sysConfig.getConfigValue(), GloalRewardConfig.class);
}
public String updateGameConfig(GloalRewardConfig params) {
if (params.getProfitRate() < 0 || params.getProfitRate() > 1) {
return "profitRate必须在[0,1]之间";
}
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique(CONFIG_KEY);
if (sysConfig == null) {
sysConfig = new SysConfig() {{
setConfigName("全局奖池配置");
setConfigKey(CONFIG_KEY);
setConfigValue(JsonUtils.toSnakeCaseJson(params));
}};
sysConfigMapper.insertConfig(sysConfig);
} else {
sysConfig.setConfigValue(JsonUtils.toSnakeCaseJson(params));
sysConfigMapper.updateConfig(sysConfig);
}
return "";
}
}