Initial commit
This commit is contained in:
1
skins-service/game-wheel-service/doc/data.sql
Normal file
1
skins-service/game-wheel-service/doc/data.sql
Normal file
@@ -0,0 +1 @@
|
||||
todo
|
||||
60
skins-service/game-wheel-service/doc/init.sql
Normal file
60
skins-service/game-wheel-service/doc/init.sql
Normal file
@@ -0,0 +1,60 @@
|
||||
CREATE TABLE `game_wheel_round` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一自增id',
|
||||
`date` varchar(10) NOT NULL COMMENT '所属日期',
|
||||
`round_id` bigint NOT NULL COMMENT 'round id',
|
||||
`start_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '开始时间',
|
||||
`end_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '结束时间',
|
||||
`phase_times` varchar(128) NOT NULL DEFAULT '' COMMENT '每个阶段的时间点',
|
||||
`total_bet` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '本期总下注数',
|
||||
`total_win` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '本期玩家总赢数',
|
||||
`symbols` varchar(64) NOT NULL DEFAULT '' COMMENT '转盘上的符号',
|
||||
`result` varchar(64) NOT NULL COMMENT '开出的奖的集合',
|
||||
`is_manual` int NOT NULL DEFAULT 0 COMMENT '是否后台开奖',
|
||||
`status` varchar(16) NOT NULL DEFAULT '' COMMENT '状态',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_date_round_id` (`date`, `round_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘每期表';
|
||||
|
||||
CREATE TABLE `game_wheel_round_bet` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一自增id',
|
||||
`round_id` bigint NOT NULL COMMENT 'round id',
|
||||
`user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户id',
|
||||
`bet` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '下注数',
|
||||
`bet_symbol` varchar(4) NOT NULL DEFAULT '' COMMENT '压注目标',
|
||||
`win` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '赔付结果',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_round_user_symbol` (`round_id`, `user_id`, `bet_symbol`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘下注表';
|
||||
|
||||
CREATE TABLE `game_wheel_user` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`user_id` bigint NOT NULL COMMENT 'user表的id',
|
||||
`total_bet` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用户总下注额',
|
||||
`total_win` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用户总赢得金额',
|
||||
`count` int NOT NULL DEFAULT '0' COMMENT '总下注次数',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘用户表';
|
||||
|
||||
|
||||
ALTER TABLE sys_config MODIFY COLUMN config_value VARCHAR(1024);
|
||||
|
||||
游戏配置数据:
|
||||
```
|
||||
{
|
||||
// 核心配置
|
||||
"symbols": [1,2,3,4,5,6,7,8,7,6,5,4,3,2,1], // 转盘格子配置
|
||||
"buffer_ratio": 0.9, // 一次旋转,最多可用的奖池比例
|
||||
"rtp": 0.85, // 一次旋转最多返回给玩家的总投注比例
|
||||
// 下面是体验相关,不影响利润
|
||||
"special_effects": [{"name": "系统赢", "ratio": 0.7},{"name": "免费旋转1次", "ratio": 0.2},{"name": "免费旋转2次", "ratio": 0.1}]
|
||||
}
|
||||
```
|
||||
|
||||
44
skins-service/game-wheel-service/pom.xml
Normal file
44
skins-service/game-wheel-service/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>skins-service</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</parent>
|
||||
|
||||
<description>
|
||||
幸运转盘
|
||||
</description>
|
||||
|
||||
<artifactId>game-wheel-service</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>service-admin</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>service-user</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.game.wheel.cache;
|
||||
import com.ruoyi.game.wheel.model.cache.WheelRewardPoolCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static com.ruoyi.admin.config.RedisConstants.WHEEL_GAME_REWARD_POOL_CACHE;
|
||||
@Component
|
||||
public class WheelRewardPoolCacheRepository {
|
||||
@Autowired
|
||||
public RedisTemplate redisTemplate;
|
||||
|
||||
/** 封盘时将本局总下注全额注入奖池统计 */
|
||||
public void incrementalBet(BigDecimal totalBet) {
|
||||
redisTemplate.opsForValue().increment(WHEEL_GAME_REWARD_POOL_CACHE + ":bet", totalBet.doubleValue());
|
||||
}
|
||||
|
||||
/** 开奖后将本局实际总返奖全额记录到奖池统计 */
|
||||
public void incrementalWin(BigDecimal totalWin) {
|
||||
redisTemplate.opsForValue().increment(WHEEL_GAME_REWARD_POOL_CACHE + ":win", totalWin.doubleValue());
|
||||
}
|
||||
|
||||
/** 读取奖池当前累计数据 */
|
||||
public WheelRewardPoolCache getCache() {
|
||||
var totalBetS = redisTemplate.opsForValue().get(WHEEL_GAME_REWARD_POOL_CACHE + ":bet");
|
||||
var totalWinS = redisTemplate.opsForValue().get(WHEEL_GAME_REWARD_POOL_CACHE + ":win");
|
||||
WheelRewardPoolCache cache = new WheelRewardPoolCache();
|
||||
cache.setTotalBet(totalBetS == null ? BigDecimal.ZERO : new BigDecimal(totalBetS.toString()));
|
||||
cache.setTotalWin(totalWinS == null ? BigDecimal.ZERO : new BigDecimal(totalWinS.toString()));
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.game.wheel.cache;
|
||||
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.json.SnakeCaseJsonUtils;
|
||||
import com.ruoyi.game.wheel.constants.CacheConstants;
|
||||
import com.ruoyi.game.wheel.model.cache.WheelRoundCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class WheelRoundCacheRepository {
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
public WheelRoundCache getCache() {
|
||||
String s = redisCache.getCacheObject(CacheConstants.WHEEL_ROUND);
|
||||
return SnakeCaseJsonUtils.fromSnakeCaseJson(s, WheelRoundCache.class);
|
||||
}
|
||||
|
||||
public void setCache(WheelRoundCache cache) {
|
||||
if (cache == null) {
|
||||
redisCache.setCacheObject(CacheConstants.WHEEL_ROUND, null, 1, TimeUnit.DAYS);
|
||||
} else {
|
||||
String json = SnakeCaseJsonUtils.toSnakeCaseJson(cache);
|
||||
redisCache.setCacheObject(CacheConstants.WHEEL_ROUND, json, 1, TimeUnit.DAYS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.wheel.constants;
|
||||
|
||||
public class CacheConstants {
|
||||
private static final String PREFIX = "ruoyi:wheel:";
|
||||
|
||||
public static final String WHEEL_ROUND = PREFIX + "round";
|
||||
|
||||
public static final String WHEEL_LOCK = PREFIX + "lock";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.game.wheel.contract.admin.response;
|
||||
|
||||
import com.ruoyi.game.wheel.domain.GameWheelGameConfig;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryWheelGameConfig {
|
||||
private GameWheelGameConfig config;
|
||||
private double totalBet;
|
||||
private double totalWin;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.wheel.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DoBetRequest {
|
||||
private Integer bet;
|
||||
private String symbol;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.wheel.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetHistoryBetRequest {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.wheel.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetRoundHistoryRequest {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.game.wheel.contract.api.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class DoBetResponse {
|
||||
private Long roundId;
|
||||
private String cost;
|
||||
private String balance;
|
||||
private String credits;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.game.wheel.contract.api.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GetGameConfigResponse {
|
||||
private String symbols;
|
||||
private List<SymbolScore> symbolScores;
|
||||
private List<Integer> bets;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class SymbolScore {
|
||||
private String symbol;
|
||||
private Integer score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.game.wheel.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GetHistoryBetResponse {
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
private List<RoundItem> rows;
|
||||
|
||||
@Data
|
||||
public static class RoundItem {
|
||||
private Long roundId;
|
||||
private String totalBet;
|
||||
private String endTime;
|
||||
private List<Item> items;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Item {
|
||||
private String bet;
|
||||
// 下注信息
|
||||
private String betSymbol;
|
||||
private String win;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.game.wheel.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GetRoundHistoryResponse {
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
private List<Item> rows;
|
||||
|
||||
@Data
|
||||
public static class Item {
|
||||
private Integer roundId;
|
||||
private List<String> resultSymbols;
|
||||
private String endTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.game.wheel.contract.api.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GetRoundInfoResponse {
|
||||
private Long roundId;
|
||||
private Long serverTime;
|
||||
// 本局转盘开始时间
|
||||
private Long startTime;
|
||||
// 本句转盘下注结束时间
|
||||
private Long bettedTime;
|
||||
// 开始旋转时间。目前bettedTime=spinTime
|
||||
private Long spinTime;
|
||||
// 结束时间
|
||||
private Long endTime;
|
||||
/**
|
||||
* @see com.ruoyi.game.wheel.enums.WheelRoundStatusEnum
|
||||
*/
|
||||
private String status;
|
||||
private String result;
|
||||
|
||||
// 总返奖
|
||||
private String totalWin;
|
||||
// 每个符号的下注统计
|
||||
private List<SymbolBet> symbolBets;
|
||||
// 请求用户的下注信息和开奖结果
|
||||
private List<UserResult> userResults;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class SymbolBet {
|
||||
private String symbol;
|
||||
private String totalBet;
|
||||
private String userCount;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class UserResult {
|
||||
private String bet;
|
||||
private String betSymbol;
|
||||
private String win;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.game.wheel.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelGameConfig;
|
||||
import com.ruoyi.game.wheel.service.AdminWheelService;
|
||||
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.*;
|
||||
|
||||
@Api(tags = "幸运转盘")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/wheel")
|
||||
public class AdminWheelController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private AdminWheelService adminWheelService;
|
||||
|
||||
@ApiOperation("获取糖果游戏配置")
|
||||
@GetMapping("/gameconfig")
|
||||
public AjaxResult getGameConfig() {
|
||||
return adminWheelService.getGameConfig();
|
||||
}
|
||||
|
||||
@ApiOperation("更新糖果游戏配置")
|
||||
@PostMapping("/gameconfig")
|
||||
public AjaxResult updateGameConfig(@RequestBody GameWheelGameConfig request) {
|
||||
return adminWheelService.updateGameConfig(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ruoyi.game.wheel.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.game.wheel.contract.api.request.DoBetRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.request.GetHistoryBetRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.request.GetRoundHistoryRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.response.DoBetResponse;
|
||||
import com.ruoyi.game.wheel.contract.api.response.GetGameConfigResponse;
|
||||
import com.ruoyi.game.wheel.contract.api.response.GetHistoryBetResponse;
|
||||
import com.ruoyi.game.wheel.contract.api.response.GetRoundHistoryResponse;
|
||||
import com.ruoyi.game.wheel.contract.api.response.GetRoundInfoResponse;
|
||||
import com.ruoyi.game.wheel.service.ApiWheelService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.user.service.ApiUserService;
|
||||
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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Api(tags = "幸运转盘")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/wheel")
|
||||
public class ApiWheelController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiWheelService apiWheelService;
|
||||
|
||||
@Autowired
|
||||
private ApiUserService userService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
public Long checkLogin() {
|
||||
Long userId;
|
||||
try {
|
||||
userId = getUserId();
|
||||
if (ObjectUtil.isEmpty(userId)) return 0L;
|
||||
return userId;
|
||||
} catch (Exception e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("获取游戏配置")
|
||||
@GetMapping("/game_config")
|
||||
public R<GetGameConfigResponse> getGameConfig() {
|
||||
return apiWheelService.getGameConfig();
|
||||
}
|
||||
|
||||
@ApiOperation("投注")
|
||||
@GetMapping("/dobet")
|
||||
@UpdateUserCache
|
||||
public R<DoBetResponse> doBet(DoBetRequest request) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
Long userId = checkLogin();
|
||||
if (userId == 0L) return R.fail("登录过期,请重新登录。");
|
||||
|
||||
var r = userService.checkRecharge(userId.intValue());
|
||||
if (R.isError(r)) {
|
||||
return R.fail(r.getMsg());
|
||||
}
|
||||
return apiWheelService.doBet(request, userId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取最新一期游戏状态")
|
||||
@GetMapping("/last_round_info")
|
||||
public R<GetRoundInfoResponse> getLastRoundInfo() {
|
||||
Long userId = checkLogin();
|
||||
if (userId == 0L) return R.fail("登录过期,请重新登录。");
|
||||
return apiWheelService.getLastRoundInfo(userId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取玩家的游戏历史记录")
|
||||
@GetMapping("/history_bet_info")
|
||||
public R<GetHistoryBetResponse> getHistoryBetInfo(GetHistoryBetRequest request) {
|
||||
Long userId = checkLogin();
|
||||
if (userId == 0L) return R.fail("登录过期,请重新登录。");
|
||||
return apiWheelService.getHistoryBetInfo(request, userId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取转盘游戏历史记录")
|
||||
@GetMapping("/round_history_bet_info")
|
||||
public R<GetRoundHistoryResponse> getRoundHistoryBetInfo(GetRoundHistoryRequest request) {
|
||||
return apiWheelService.getRoundHistoryInfo(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ruoyi.game.wheel.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GameWheelGameConfig {
|
||||
// 玩法配置
|
||||
// 是否关闭游戏 false
|
||||
private boolean switchOff;
|
||||
// 可选的下注数
|
||||
private List<Integer> bets;
|
||||
// 每个符号的赔率设置
|
||||
private List<SymbolScore> symbolScores;
|
||||
// ABCDEFGFEDCBA 转盘格子配置
|
||||
private String symbols;
|
||||
|
||||
// 下注期55分钟
|
||||
private int bettingDuration = 10;
|
||||
// 封盘期5秒
|
||||
private int bettedDuration = 0;
|
||||
// 开奖期15秒。这是最少要求,如果命中免费多次开奖,则时间进行累加
|
||||
private int animationDuration = 5;
|
||||
|
||||
// 盈利配置
|
||||
// 一次旋转,最多可用的奖池比例 0.9 total_bet * buff_ratio = reward_pool
|
||||
private double bufferRatio;
|
||||
// 一次旋转最多返回给玩家的总投注比例 0.85 reward_pool < total_bet * rtp
|
||||
private double rtp;
|
||||
|
||||
// 下面是体验相关,不影响盈利
|
||||
// 命中特殊效果的概率。
|
||||
private double specialEffectRatio;
|
||||
// 每种特殊效果的概率。
|
||||
// [{"name": "系统赢", "ratio": 0.7},{"name": "免费旋转1次", "ratio": 0.2},{"name": "免费旋转2次", "ratio": 0.1}]
|
||||
private List<SpecialEffect> specialEffects;
|
||||
|
||||
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class SpecialEffect {
|
||||
private String name;
|
||||
private double weight;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class SymbolScore {
|
||||
private String symbol;
|
||||
// 符号赔率
|
||||
private Integer score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ruoyi.game.wheel.domain;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.game.wheel.domain.handler.WheelRoundPhaseTimesTypeHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@TableName(value = "game_wheel_round", autoResultMap = true)
|
||||
public class GameWheelRound implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private String date;
|
||||
private Long roundId;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
@TableField(typeHandler = WheelRoundPhaseTimesTypeHandler.class)
|
||||
private List<Long> phaseTimes;
|
||||
private String symbols;
|
||||
// 下面的字段只做统计用,不参与业务逻辑
|
||||
private BigDecimal totalBet;
|
||||
private BigDecimal totalWin;
|
||||
// 旋转的结果,包含中间旋转结果,每个结果用`,`分割。如S2,6,9,表示命中额外抽奖两次,6,9,表示命中额外抽奖一次
|
||||
private String result;
|
||||
private Integer isManual;
|
||||
private String status;
|
||||
|
||||
private LocalDateTime create_time;
|
||||
private LocalDateTime update_time;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.game.wheel.domain;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@TableName(value = "game_wheel_round_bet")
|
||||
public class GameWheelRoundBet implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private Long roundId;
|
||||
private Long userId;
|
||||
private BigDecimal bet;
|
||||
private String betSymbol;
|
||||
private BigDecimal win;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.game.wheel.domain;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@TableName(value = "game_wheel_user")
|
||||
public class GameWheelUser implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private BigDecimal totalBet;
|
||||
private BigDecimal totalWin;
|
||||
private Integer count;
|
||||
|
||||
private LocalDateTime create_time;
|
||||
private LocalDateTime update_time;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.game.wheel.domain.handler;
|
||||
|
||||
import com.ruoyi.common.typehandler.JsonObjectHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||
@MappedTypes(List.class)
|
||||
public class WheelRoundPhaseTimesTypeHandler extends JsonObjectHandler<List<Long>> {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.game.wheel.enums;
|
||||
|
||||
|
||||
public enum SpecialEffectEnum {
|
||||
SystemWin("系统赢"),
|
||||
FreeSpin1("免费旋转1次"),
|
||||
FreeSpin2("免费旋转2次"),
|
||||
FreeSpin3("免费旋转3次");
|
||||
|
||||
private String desc;
|
||||
|
||||
SpecialEffectEnum(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(String other) {
|
||||
return this.desc.equals(other);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.game.wheel.enums;
|
||||
|
||||
|
||||
public enum WheelRoundStatusEnum {
|
||||
BETTING,
|
||||
@Deprecated
|
||||
BETED,
|
||||
SPINED;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.game.wheel.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRoundBet;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameWheelRoundBetMapper extends BaseMapper<GameWheelRoundBet> {
|
||||
@Select("select * from game_wheel_round_bet where round_id = #{roundId}")
|
||||
List<GameWheelRoundBet> selectByRoundId(@Param("roundId") Long roundId);
|
||||
|
||||
@Select("select * from game_wheel_round_bet where round_id = #{roundId} and bet_symbol = #{symbol}")
|
||||
List<GameWheelRoundBet> selectByRoundIdAndTarget(@Param("roundId") Long roundId, @Param("symbol") String symbol);
|
||||
|
||||
@Select("select * from game_wheel_round_bet where round_id = #{roundId} and user_id = #{uid} limit 1")
|
||||
GameWheelRoundBet selectByRoundIdAndUid(@Param("roundId") Long roundId, @Param("uid") Long uid);
|
||||
|
||||
@Select(
|
||||
"SELECT rbt.round_id " +
|
||||
"FROM game_wheel_round_bet rbt " +
|
||||
"LEFT JOIN game_wheel_round rt ON rbt.round_id = rt.round_id " +
|
||||
"WHERE rbt.user_id = #{userId} AND rt.date = #{date} " +
|
||||
"GROUP BY rbt.round_id " +
|
||||
"ORDER BY rbt.round_id DESC " +
|
||||
"LIMIT #{offset}, #{pageSize}")
|
||||
List<Integer> selectHistoryBetRound(@Param("userId") Long userId, @Param("date") String date, @Param("offset") Integer offset, @Param("pageSize") Integer pageSize);
|
||||
|
||||
@Select("<script>" +
|
||||
"select * from game_wheel_round_bet " +
|
||||
"where user_id = #{userId} " +
|
||||
"and round_id in " +
|
||||
"<foreach item='item' index='index' collection='roundIds' open='(' separator=',' close=')'>" +
|
||||
"#{item}" +
|
||||
"</foreach>" + "</script>"
|
||||
)
|
||||
List<GameWheelRoundBet> selectHistoryBetInfo(@Param("userId") Long userId, @Param("roundIds") List<Integer> roundIds);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.game.wheel.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRound;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameWheelRoundMapper extends BaseMapper<GameWheelRound> {
|
||||
@Select("SELECT MAX(round_id) FROM game_wheel_round where `date` = #{today}")
|
||||
public Long getTodayLastRoundId(@Param("today") String today);
|
||||
|
||||
default GameWheelRound selectByRoundId(@Param("roundId") Long roundId) {
|
||||
LambdaQueryWrapper<GameWheelRound> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(GameWheelRound::getRoundId, roundId);
|
||||
return selectOne(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.game.wheel.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelUser;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameWheelUserMapper extends BaseMapper<GameWheelUser> {
|
||||
@Select("select * from game_wheel_user where user_id = #{userId}")
|
||||
GameWheelUser getByUserId(@Param("userId") Long userId);
|
||||
|
||||
@Update("update game_wheel_user set total_bet = total_bet + #{totalBet}, total_win = total_win + #{totalWin}, count = count + #{count} where user_id = #{userId}")
|
||||
int updateIncrement(@Param("totalBet") BigDecimal totalBet,
|
||||
@Param("totalWin") BigDecimal totalWin,
|
||||
@Param("count") Integer count, @Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.game.wheel.model.cache;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class WheelRewardPoolCache {
|
||||
/**
|
||||
* 疯狂转盘奖池缓存
|
||||
* totalBet:累计总投注
|
||||
* totalWin:累计总返奖
|
||||
*/
|
||||
private BigDecimal totalBet;
|
||||
private BigDecimal totalWin;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.game.wheel.model.cache;
|
||||
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRound;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRoundBet;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class WheelRoundCache implements Serializable {
|
||||
private Long lastRoundId;
|
||||
private GameWheelRound round;
|
||||
|
||||
private Map<String, GameWheelRoundBet> betMap;
|
||||
|
||||
public static String betKey(Long roundId, Long userId, String symbol) {
|
||||
return String.format("%s_%s_%s", roundId, userId, symbol.toLowerCase());
|
||||
}
|
||||
|
||||
public static String betKey(GameWheelRoundBet bet) {
|
||||
if (bet == null) {
|
||||
return "";
|
||||
}
|
||||
return String.format("%s_%s_%s", bet.getRoundId(), bet.getUserId(), bet.getBetSymbol().toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.game.wheel.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.game.wheel.cache.WheelRewardPoolCacheRepository;
|
||||
import com.ruoyi.game.wheel.contract.admin.response.QueryWheelGameConfig;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelGameConfig;
|
||||
import com.ruoyi.game.wheel.model.cache.WheelRewardPoolCache;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminWheelService {
|
||||
|
||||
private static final String EVENT_TAG = "幸运转盘";
|
||||
|
||||
|
||||
@Autowired
|
||||
private CommonWheelService commonWheelService;
|
||||
|
||||
@Autowired
|
||||
private WheelRewardPoolCacheRepository wheelRewardPoolCacheRepository;
|
||||
|
||||
@ApiOperation("获取糖果游戏配置")
|
||||
@GetMapping("/gameconfig")
|
||||
public AjaxResult getGameConfig() {
|
||||
GameWheelGameConfig config = commonWheelService.getGameConfig();
|
||||
|
||||
QueryWheelGameConfig result = new QueryWheelGameConfig();
|
||||
result.setConfig(config);
|
||||
|
||||
// 读取奖池 Redis 数据,与极速永恒保持一致
|
||||
WheelRewardPoolCache cache = wheelRewardPoolCacheRepository.getCache();
|
||||
result.setTotalBet(cache.getTotalBet().doubleValue());
|
||||
result.setTotalWin(cache.getTotalWin().doubleValue());
|
||||
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
@ApiOperation("更新糖果游戏配置")
|
||||
@PostMapping("/gameconfig")
|
||||
public AjaxResult updateGameConfig(@RequestBody GameWheelGameConfig params) {
|
||||
commonWheelService.updateGameConfig(params);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,745 @@
|
||||
package com.ruoyi.game.wheel.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.model.UpdateUserAccountBo;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.common.utils.DateTimeUtils;
|
||||
import com.ruoyi.common.utils.MoneyUtil;
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.framework.manager.AsyncManager;
|
||||
import com.ruoyi.game.wheel.cache.WheelRoundCacheRepository;
|
||||
import com.ruoyi.game.wheel.constants.CacheConstants;
|
||||
import com.ruoyi.game.wheel.contract.api.request.DoBetRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.request.GetHistoryBetRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.request.GetRoundHistoryRequest;
|
||||
import com.ruoyi.game.wheel.contract.api.response.*;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelGameConfig;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRound;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelRoundBet;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelUser;
|
||||
import com.ruoyi.game.wheel.enums.SpecialEffectEnum;
|
||||
import com.ruoyi.game.wheel.enums.WheelRoundStatusEnum;
|
||||
import com.ruoyi.game.wheel.mapper.ApiGameWheelRoundBetMapper;
|
||||
import com.ruoyi.game.wheel.mapper.ApiGameWheelRoundMapper;
|
||||
import com.ruoyi.game.wheel.mapper.ApiGameWheelUserMapper;
|
||||
import com.ruoyi.game.wheel.model.cache.WheelRoundCache;
|
||||
import com.ruoyi.game.wheel.cache.WheelRewardPoolCacheRepository;
|
||||
import com.ruoyi.game.wheel.model.cache.WheelRewardPoolCache;
|
||||
import com.ruoyi.thirdparty.common.service.ApiNoticeService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiWheelService {
|
||||
|
||||
private static final String EVENT_TAG = "幸运转盘";
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
private static GameWheelGameConfig gameConfig;
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@Autowired
|
||||
private WheelRoundCacheRepository wheelRoundCacheRepository;
|
||||
|
||||
@Autowired
|
||||
private CommonWheelService commonWheelService;
|
||||
@Autowired
|
||||
private ApiGameWheelRoundMapper wheelRoundMapper;
|
||||
@Autowired
|
||||
private ApiGameWheelRoundBetMapper wheelRoundBetMapper;
|
||||
@Autowired
|
||||
private ApiGameWheelUserMapper wheelUserMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private WheelRewardPoolCacheRepository wheelRewardPoolCacheRepository;
|
||||
|
||||
@Autowired
|
||||
private ApiNoticeService apiNoticeService;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 创建单线程的定时任务执行器
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
// 开启一个线程执行开始新一局的逻辑
|
||||
AsyncManager.me().run(() -> {
|
||||
newRound();
|
||||
});
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getLock() {
|
||||
return redisLock.tryLock(CacheConstants.WHEEL_LOCK, 3, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void newRound() {
|
||||
log.info("{} 开始新的转盘游戏", EVENT_TAG);
|
||||
|
||||
if (!getLock()) {
|
||||
log.info("{} newRound,获取锁失败,{} 秒后重试", EVENT_TAG, gameConfig.getBettingDuration());
|
||||
scheduler.schedule(this::newRound, gameConfig.getBettingDuration(), TimeUnit.SECONDS);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
while (true) {
|
||||
// 新的一局,检查更新游戏配置
|
||||
gameConfig = commonWheelService.getGameConfig();
|
||||
if (gameConfig.isSwitchOff()) {
|
||||
log.debug("{} 游戏已关闭,{} 秒后重新开始", EVENT_TAG, 10);
|
||||
wheelRoundCacheRepository.setCache(null);
|
||||
Thread.sleep(Duration.ofSeconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
String today = DateTimeUtils.dateTime();
|
||||
WheelRoundCache cache = wheelRoundCacheRepository.getCache();
|
||||
if (cache == null) {
|
||||
cache = initCache(today);
|
||||
} else {
|
||||
// 检查是否跨天
|
||||
if (cache.getRound() != null && !today.equals(cache.getRound().getDate())) {
|
||||
cache.setLastRoundId(0L);
|
||||
}
|
||||
}
|
||||
|
||||
cache.setBetMap(new HashMap<>());
|
||||
cache.setLastRoundId(cache.getLastRoundId() + 1);
|
||||
GameWheelRound round = createGameWheelRound(cache.getLastRoundId(), today);
|
||||
if (round == null) {
|
||||
continue;
|
||||
}
|
||||
cache.setRound(round);
|
||||
wheelRoundCacheRepository.setCache(cache);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("newRound错误", e);
|
||||
} finally {
|
||||
// 等待进入封盘期
|
||||
scheduler.schedule(this::drawReward, gameConfig.getBettingDuration(), TimeUnit.SECONDS);
|
||||
redisLock.unlock(CacheConstants.WHEEL_LOCK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private WheelRoundCache initCache(String today) {
|
||||
WheelRoundCache cache = new WheelRoundCache();
|
||||
Long roundId = wheelRoundMapper.getTodayLastRoundId(today);
|
||||
if (roundId == null) {
|
||||
roundId = 0L;
|
||||
}
|
||||
cache.setLastRoundId(roundId);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private Collection<GameWheelRoundBet> getAllBets(WheelRoundCache cache) {
|
||||
return cache.getBetMap().values();
|
||||
}
|
||||
|
||||
// 进入开奖期
|
||||
private void drawReward() {
|
||||
if (!getLock()) {
|
||||
log.info("{} drawReward,获取锁失败,{} 秒后重试", EVENT_TAG, gameConfig.getBettedDuration());
|
||||
scheduler.schedule(this::drawReward, gameConfig.getBettedDuration(), TimeUnit.SECONDS);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
WheelRoundCache cache = wheelRoundCacheRepository.getCache();
|
||||
cache.getRound().setStatus(WheelRoundStatusEnum.SPINED.toString());
|
||||
|
||||
// 开奖
|
||||
Collection<GameWheelRoundBet> bets = getAllBets(cache);
|
||||
BigDecimal totalBet = calculateTotalBet(bets);
|
||||
cache.getRound().setTotalBet(totalBet);
|
||||
/*List<String> result = this.doSpin(cache.getRound(), bets, totalBet);
|
||||
|
||||
cache.getRound().setEndTime(LocalDateTime.now().plusSeconds(
|
||||
(long) gameConfig.getAnimationDuration() * result.size()));
|
||||
cache.getRound().setResult(String.join(",", result));*/
|
||||
// 封盘后将本局总下注全额注入奖池统计
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) > 0) {
|
||||
wheelRewardPoolCacheRepository.incrementalBet(totalBet);
|
||||
}
|
||||
List<String> result = this.doSpin(cache.getRound(), bets, totalBet);
|
||||
|
||||
cache.getRound().setEndTime(LocalDateTime.now().plusSeconds(
|
||||
(long) gameConfig.getAnimationDuration() * result.size()));
|
||||
cache.getRound().setResult(String.join(",", result));
|
||||
|
||||
// 进行赔付
|
||||
this.processResult(cache, result);
|
||||
|
||||
wheelRoundCacheRepository.setCache(cache);
|
||||
wheelRoundMapper.updateById(cache.getRound());
|
||||
} catch (Exception e) {
|
||||
log.error("drawReward错误", e);
|
||||
} finally {
|
||||
redisLock.unlock(CacheConstants.WHEEL_LOCK);
|
||||
// 等待一个前端播放动画时间, 开启下一局
|
||||
scheduler.schedule(this::newRound, gameConfig.getAnimationDuration(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private List<GameWheelRoundBet> selectBySymbol(WheelRoundCache cache, String symbol) {
|
||||
return getAllBets(cache).stream().
|
||||
filter(v -> v.getBetSymbol().equals(symbol)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void processResult(WheelRoundCache cache, List<String> result) {
|
||||
GameWheelRound round = cache.getRound();
|
||||
|
||||
for (String spinResult : result) {
|
||||
int target = NumberUtils.toInt(spinResult, -1);
|
||||
// 特殊效果不处理
|
||||
if (target < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String symbol = String.valueOf(round.getSymbols().charAt(target));
|
||||
GameWheelGameConfig.SymbolScore score = gameConfig.getSymbolScores().stream()
|
||||
.filter(s -> s.getSymbol().equals(symbol)).findFirst().orElse(null);
|
||||
if (score == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double s = score.getScore();
|
||||
List<GameWheelRoundBet> betsOnTarget = selectBySymbol(cache, symbol);
|
||||
if (CollectionUtils.isEmpty(betsOnTarget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (GameWheelRoundBet bet : betsOnTarget) {
|
||||
BigDecimal amount = bet.getBet().multiply(BigDecimal.valueOf(s));
|
||||
bet.setWin(amount);
|
||||
}
|
||||
}
|
||||
|
||||
// 存储
|
||||
BigDecimal min = new BigDecimal("0.01");
|
||||
BigDecimal totalWin = BigDecimal.ZERO;
|
||||
for (GameWheelRoundBet bet : getAllBets(cache)) {
|
||||
BigDecimal amount = bet.getWin();
|
||||
if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
totalWin = totalWin.add(amount);
|
||||
userService.updateUserAccount(bet.getUserId().intValue(), amount,
|
||||
TtAccountRecordSource.LUCKLY_WHEEL_REWARD);
|
||||
} else {
|
||||
bet.setWin(min);
|
||||
userService.updateUserAccount(bet.getUserId().intValue(), min,
|
||||
TtAccountRecordSource.LUCKLY_WHEEL_REWARD);
|
||||
}
|
||||
wheelRoundBetMapper.insert(bet);
|
||||
wheelUserMapper.updateIncrement(BigDecimal.ZERO, amount, 0, bet.getUserId());
|
||||
}
|
||||
round.setTotalWin(totalWin);
|
||||
// 将本局实际总返奖写入奖池统计
|
||||
if (totalWin.compareTo(BigDecimal.ZERO) > 0) {
|
||||
wheelRewardPoolCacheRepository.incrementalWin(totalWin);
|
||||
}
|
||||
|
||||
// ===== 新增:疯狂转盘结果通知(过滤机器人用户)=====
|
||||
AsyncManager.me().run(() -> sendWheelResultNotice(cache));
|
||||
// ===== 新增疯狂转盘结果通知结束 =====
|
||||
}
|
||||
|
||||
/**
|
||||
* 疯狂转盘结果通知(过滤机器人用户)
|
||||
* 示例:张三 疯狂转盘本局结果:投入 50 元,获得回报 120 元。
|
||||
*/
|
||||
private void sendWheelResultNotice(WheelRoundCache cache) {
|
||||
try {
|
||||
Collection<GameWheelRoundBet> allBets = getAllBets(cache);
|
||||
if (allBets.isEmpty()) return;
|
||||
|
||||
// 获取所有下注用户ID,过滤机器人
|
||||
List<Long> betUserIds = allBets.stream()
|
||||
.map(GameWheelRoundBet::getUserId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
List<TtUser> realUsers = new LambdaQueryChainWrapper<>(ttUserMapper)
|
||||
.in(TtUser::getUserId, betUserIds)
|
||||
.ne(TtUser::getUserType, UserType.ROBOT_USER.getCode())
|
||||
.list();
|
||||
|
||||
// 合并同一用户的多次下注(bet 和 win 分别累加)
|
||||
Map<Long, GameWheelRoundBet> betByUser = new HashMap<>();
|
||||
for (GameWheelRoundBet bet : allBets) {
|
||||
if (bet.getUserId() == null) continue;
|
||||
betByUser.merge(bet.getUserId(), bet, (a, b) -> {
|
||||
a.setBet(a.getBet().add(b.getBet()));
|
||||
a.setWin(a.getWin().add(b.getWin()));
|
||||
return a;
|
||||
});
|
||||
}
|
||||
|
||||
for (TtUser realUser : realUsers) {
|
||||
Long userId = Long.valueOf(realUser.getUserId());
|
||||
GameWheelRoundBet userBet = betByUser.get(userId);
|
||||
if (userBet == null) continue;
|
||||
BigDecimal betAmount = userBet.getBet() != null ? userBet.getBet() : BigDecimal.ZERO;
|
||||
BigDecimal winAmount = userBet.getWin() != null ? userBet.getWin() : BigDecimal.ZERO;
|
||||
String content = realUser.getNickName() + " 疯狂转盘本局结果:投入 " + betAmount + " 元,获得回报 " + winAmount + " 元。";
|
||||
apiNoticeService.sendNotice(userId, "疯狂转盘结果通知", content);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("疯狂转盘通知推送失败,error={}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal calculateTotalBet(Collection<GameWheelRoundBet> bets) {
|
||||
return bets.stream()
|
||||
.map(GameWheelRoundBet::getBet)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
private BigDecimal payoutIf(Collection<GameWheelRoundBet> bets, String symbol) {
|
||||
BigDecimal totalBet = bets.stream().filter(bet -> Objects.equals(bet.getBetSymbol(), symbol))
|
||||
.map(GameWheelRoundBet::getBet)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return gameConfig.getSymbolScores().stream()
|
||||
.filter(s -> s.getSymbol().equals(String.valueOf(symbol))).map(GameWheelGameConfig.SymbolScore::getScore)
|
||||
.map(score -> totalBet.multiply(BigDecimal.valueOf(score)))
|
||||
.findFirst().orElse(BigDecimal.valueOf(1_0000_0000));
|
||||
}
|
||||
|
||||
private Integer getRandomScotterIndex(String symbol) {
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
for (int i = 0; i < symbol.length(); i++) {
|
||||
if (symbol.charAt(i) != 'S') indices.add(i);
|
||||
}
|
||||
return RandomUtil.choice(indices);
|
||||
}
|
||||
|
||||
private List<String> doSpin(GameWheelRound round, Collection<GameWheelRoundBet> bets, BigDecimal totalBet) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
// 无人下注,踢出特殊符号,随机开奖
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) == 0) {
|
||||
List<Integer> validTargets = IntStream.range(0, round.getSymbols().length())
|
||||
.filter(target -> round.getSymbols().charAt(target) != 'S')
|
||||
.boxed()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int target = RandomUtil.choice(validTargets);
|
||||
return Collections.singletonList(String.valueOf(target));
|
||||
}
|
||||
|
||||
// 计算每个选区的净利润
|
||||
List<Pair<Integer, BigDecimal>> candidates = new ArrayList<>();
|
||||
for (int num = 0; num < gameConfig.getSymbols().length(); num++) {
|
||||
char symbol = gameConfig.getSymbols().charAt(num);
|
||||
// 特殊符号不计算赔付
|
||||
if (symbol == 'S') {
|
||||
continue;
|
||||
}
|
||||
BigDecimal payout = payoutIf(bets, String.valueOf(symbol));
|
||||
BigDecimal profit = totalBet.subtract(payout);
|
||||
candidates.add(Pair.of(num, profit));
|
||||
}
|
||||
|
||||
// 判断是否能命中特殊效果
|
||||
String specialEffectName = "";
|
||||
if (RandomUtil.random() < gameConfig.getSpecialEffectRatio()) {
|
||||
// 命中特殊效果
|
||||
double[] weights = gameConfig.getSpecialEffects().stream().mapToDouble(GameWheelGameConfig.SpecialEffect::getWeight).toArray();
|
||||
GameWheelGameConfig.SpecialEffect specialEffect = RandomUtil.choices(gameConfig.getSpecialEffects(), weights, 1).get(0);
|
||||
specialEffectName = specialEffect.getName();
|
||||
}
|
||||
|
||||
if (SpecialEffectEnum.SystemWin.equals(specialEffectName)) {
|
||||
Integer index = getRandomScotterIndex(round.getSymbols());
|
||||
result.add(index + ":S0");
|
||||
return result;
|
||||
}
|
||||
int spinCount = 1;
|
||||
if (SpecialEffectEnum.FreeSpin1.equals(specialEffectName)) {
|
||||
Integer index = getRandomScotterIndex(round.getSymbols());
|
||||
result.add(index + ":S1");
|
||||
spinCount = 1;
|
||||
}
|
||||
if (SpecialEffectEnum.FreeSpin2.equals(specialEffectName)) {
|
||||
Integer index = getRandomScotterIndex(round.getSymbols());
|
||||
result.add(index + ":S2");
|
||||
spinCount = 2;
|
||||
}
|
||||
if (SpecialEffectEnum.FreeSpin3.equals(specialEffectName)) {
|
||||
Integer index = getRandomScotterIndex(round.getSymbols());
|
||||
result.add(index + ":S3");
|
||||
spinCount = 3;
|
||||
}
|
||||
|
||||
/*for (int i = 0; i < spinCount; i++) {
|
||||
List<Pair<Integer, BigDecimal>> profitableCandidates = candidates.stream()
|
||||
.filter(v -> v.getRight().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
|
||||
if (profitableCandidates.isEmpty()) {
|
||||
// 没有盈利的选区, 选择一个亏本最小的开
|
||||
Pair<Integer, BigDecimal> minLoss = candidates.stream()
|
||||
.min(Comparator.comparing(Pair::getRight)).orElse(null);
|
||||
assert minLoss != null;
|
||||
result.add(minLoss.getLeft() + "");
|
||||
}
|
||||
|
||||
// 有盈利的选区, 随机选择一个开
|
||||
result.add(RandomUtil.choice(profitableCandidates).getLeft() + "");
|
||||
}*/
|
||||
/*for (int i = 0; i < spinCount; i++) {
|
||||
// 筛选出赔付率 < 0.8 的格子(即赔付 < 总下注 * 0.8,对系统盈利)
|
||||
BigDecimal rtpCap = totalBet.multiply(BigDecimal.valueOf(0.8));//BigDecimal.valueOf(gameConfig.getRtp())系统配置0.85暂时不改
|
||||
List<Pair<Integer, BigDecimal>> rtpSafeCandidates = candidates.stream()
|
||||
.filter(v -> {
|
||||
// 计算赔付金额
|
||||
BigDecimal payout = totalBet.subtract(v.getRight());
|
||||
// 判断赔付金额是否小于 rtpCap(rpt上限 总下注 * 0.8)
|
||||
return payout.compareTo(rtpCap) < 0;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!rtpSafeCandidates.isEmpty()) {
|
||||
// 有多个赔率 < 0.8 的格子,随机选一个(不一定选赔付最小的)
|
||||
result.add(RandomUtil.choice(rtpSafeCandidates).getLeft() + "");
|
||||
} else {
|
||||
// 所有格子赔付都 >= 0.8,选赔付最小的(亏损最少)
|
||||
Pair<Integer, BigDecimal> minLoss = candidates.stream()
|
||||
.max(Comparator.comparing(Pair::getRight)).orElse(null);
|
||||
assert minLoss != null;
|
||||
result.add(minLoss.getLeft() + "");
|
||||
}
|
||||
}*/
|
||||
// ===== 奖池 RTP 逻辑=====
|
||||
// 读取全局奖池累计数据
|
||||
WheelRewardPoolCache poolCache = wheelRewardPoolCacheRepository.getCache();
|
||||
BigDecimal poolTotalBet = poolCache.getTotalBet();
|
||||
BigDecimal poolTotalWin = poolCache.getTotalWin();
|
||||
|
||||
// 奖池余额 = 累计总投注 - 累计总返奖
|
||||
BigDecimal jackpotBalance = poolTotalBet.subtract(poolTotalWin);
|
||||
if (jackpotBalance.compareTo(BigDecimal.ZERO) < 0) {
|
||||
jackpotBalance = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 本局赔付上限 = 奖池余额 * 0.8
|
||||
BigDecimal payoutCap = jackpotBalance.multiply(BigDecimal.valueOf(0.8));
|
||||
|
||||
for (int i = 0; i < spinCount; i++) {
|
||||
// 筛选赔付 <= 上限
|
||||
List<Pair<Integer, BigDecimal>> safeCandidates = candidates.stream()
|
||||
.filter(v -> {
|
||||
// v.getRight() 是 profit = totalBet - payout,所以 payout = totalBet - profit
|
||||
BigDecimal payout = totalBet.subtract(v.getRight());
|
||||
return payout.compareTo(payoutCap) <= 0;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!safeCandidates.isEmpty()) {
|
||||
// 有满足条件的格子,随机选一个
|
||||
result.add(RandomUtil.choice(safeCandidates).getLeft() + "");
|
||||
} else {
|
||||
// 全部格子赔付都超过 payoutCap,选赔率最小的(profit 最大 = 系统亏损最少)
|
||||
Pair<Integer, BigDecimal> minPayout = candidates.stream()
|
||||
.max(Comparator.comparing(Pair::getRight)).orElse(null);
|
||||
assert minPayout != null;
|
||||
result.add(minPayout.getLeft() + "");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private GameWheelRound createGameWheelRound(long roundId, String today) {
|
||||
|
||||
GameWheelRound round = new GameWheelRound();
|
||||
round.setRoundId(roundId);
|
||||
round.setDate(today);
|
||||
round.setStartTime(LocalDateTime.now());
|
||||
round.setPhaseTimes(Arrays.asList(
|
||||
DateTimeUtils.now() + gameConfig.getBettingDuration(), // 开始封盘
|
||||
DateTimeUtils.now() + gameConfig.getBettingDuration() + gameConfig.getBettedDuration()) // 开始开奖
|
||||
);
|
||||
round.setEndTime(LocalDateTime.now().plusSeconds(gameConfig.getBettingDuration()
|
||||
+ gameConfig.getBettedDuration()
|
||||
+ gameConfig.getAnimationDuration()
|
||||
));
|
||||
round.setSymbols(gameConfig.getSymbols());
|
||||
round.setTotalBet(BigDecimal.ZERO);
|
||||
round.setTotalWin(BigDecimal.ZERO);
|
||||
round.setResult("");
|
||||
round.setIsManual(0);
|
||||
round.setStatus(WheelRoundStatusEnum.BETTING.toString());
|
||||
|
||||
try {
|
||||
wheelRoundMapper.insert(round);
|
||||
} catch (Exception e) {
|
||||
log.error("{} 创建新局失败", EVENT_TAG, e);
|
||||
wheelRoundCacheRepository.setCache(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
public R<GetGameConfigResponse> getGameConfig() {
|
||||
GameWheelGameConfig config = commonWheelService.getGameConfig();
|
||||
GetGameConfigResponse resp = new GetGameConfigResponse();
|
||||
resp.setSymbols(config.getSymbols());
|
||||
resp.setBets(config.getBets());
|
||||
resp.setSymbolScores(config.getSymbolScores().stream()
|
||||
.map(s -> new GetGameConfigResponse.SymbolScore(s.getSymbol(), s.getScore()))
|
||||
.collect(Collectors.toList()));
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
// 实时更新ttUser|流水和wheelUser信息
|
||||
public R<DoBetResponse> doBet(DoBetRequest request, Long uid) {
|
||||
if (request.getBet() == null || request.getSymbol() == null) {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
if (!gameConfig.getBets().contains(request.getBet())) {
|
||||
return R.fail("下注金额错误");
|
||||
}
|
||||
if (!gameConfig.getSymbols().contains(request.getSymbol())) {
|
||||
return R.fail("下注符号错误");
|
||||
}
|
||||
|
||||
WheelRoundCache cache = wheelRoundCacheRepository.getCache();
|
||||
if (cache == null || cache.getRound() == null) {
|
||||
return R.fail("游戏未开始");
|
||||
}
|
||||
if (!cache.getRound().getStatus().equals(WheelRoundStatusEnum.BETTING.toString())) {
|
||||
return R.fail("游戏不在投注期");
|
||||
}
|
||||
|
||||
Boolean lock = getLock();
|
||||
if (!Boolean.TRUE.equals(lock)) {
|
||||
return R.fail("请求频繁,加锁失败");
|
||||
}
|
||||
try {
|
||||
R<UpdateUserAccountBo> updateResult = userService.updateUserAccount(uid.intValue(),
|
||||
BigDecimal.valueOf(request.getBet()).negate(),
|
||||
TtAccountRecordSource.LUCKLY_WHEEL_SPIN);
|
||||
if (R.isError(updateResult)) {
|
||||
return R.fail(updateResult.getMsg());
|
||||
}
|
||||
|
||||
GameWheelRoundBet bet = cache.getBetMap().get(WheelRoundCache.betKey(cache.getRound().getRoundId(),
|
||||
uid, request.getSymbol()));
|
||||
if (bet != null) {
|
||||
// 追加下注
|
||||
bet.setBet(bet.getBet().add(BigDecimal.valueOf(request.getBet())));
|
||||
} else {
|
||||
bet = new GameWheelRoundBet();
|
||||
bet.setRoundId(cache.getRound().getRoundId());
|
||||
bet.setUserId(uid);
|
||||
bet.setBet(BigDecimal.valueOf(request.getBet()));
|
||||
bet.setBetSymbol(request.getSymbol());
|
||||
bet.setWin(BigDecimal.ZERO);
|
||||
}
|
||||
cache.getBetMap().put(WheelRoundCache.betKey(bet), bet);
|
||||
wheelRoundCacheRepository.setCache(cache);
|
||||
|
||||
if (wheelUserMapper.getByUserId(uid) == null) {
|
||||
initWheelUser(uid);
|
||||
}
|
||||
wheelUserMapper.updateIncrement(bet.getBet(), BigDecimal.ZERO, 1, uid);
|
||||
|
||||
return R.ok(new DoBetResponse(cache.getRound().getRoundId(), request.getBet().toString(),
|
||||
MoneyUtil.toStr(updateResult.getData().getAccountAmount()),
|
||||
MoneyUtil.toStr(updateResult.getData().getAccountCredits()))
|
||||
);
|
||||
} catch (Exception e) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
log.error("错误", e);
|
||||
return R.fail("内部错误,请联系管理员");
|
||||
} finally {
|
||||
redisLock.unlock(CacheConstants.WHEEL_LOCK);
|
||||
}
|
||||
}
|
||||
|
||||
private void initWheelUser(Long uid) {
|
||||
GameWheelUser user = new GameWheelUser();
|
||||
user.setUserId(uid);
|
||||
user.setTotalBet(BigDecimal.ZERO);
|
||||
user.setTotalWin(BigDecimal.ZERO);
|
||||
user.setCount(0);
|
||||
wheelUserMapper.insert(user);
|
||||
}
|
||||
|
||||
public R<GetRoundInfoResponse> getLastRoundInfo(Long uid) {
|
||||
WheelRoundCache cache = wheelRoundCacheRepository.getCache();
|
||||
if (cache == null || cache.getRound() == null) {
|
||||
return R.fail("游戏未开始");
|
||||
}
|
||||
return R.ok(buildRoundResp(cache, cache.getRound(), uid));
|
||||
}
|
||||
|
||||
private GetRoundInfoResponse buildRoundResp(WheelRoundCache cache, GameWheelRound round, Long uid) {
|
||||
GetRoundInfoResponse resp = new GetRoundInfoResponse();
|
||||
resp.setRoundId(round.getRoundId());
|
||||
resp.setServerTime(DateTimeUtils.now());
|
||||
resp.setStartTime(DateTimeUtils.toSeconds(round.getStartTime()));
|
||||
resp.setEndTime(DateTimeUtils.toSeconds(round.getEndTime()));
|
||||
resp.setBettedTime(round.getPhaseTimes().get(0));
|
||||
resp.setSpinTime(round.getPhaseTimes().get(1));
|
||||
resp.setStatus(round.getStatus());
|
||||
resp.setResult(round.getResult());
|
||||
resp.setTotalWin(MoneyUtil.toStr(round.getTotalWin()));
|
||||
|
||||
// 统计每个符号的下注数据
|
||||
Map<String, List<GameWheelRoundBet>> symbolMap = cache.getBetMap().values().stream().collect(Collectors.groupingBy(GameWheelRoundBet::getBetSymbol));
|
||||
List<GetRoundInfoResponse.SymbolBet> symbolBets = new ArrayList<>();
|
||||
symbolMap.forEach((symbol, bets) -> {
|
||||
String totalBet = MoneyUtil.toStr(bets.stream().map(GameWheelRoundBet::getBet).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
symbolBets.add(new GetRoundInfoResponse.SymbolBet(symbol, totalBet, String.valueOf(bets.size())));
|
||||
});
|
||||
resp.setSymbolBets(symbolBets);
|
||||
|
||||
List<GetRoundInfoResponse.UserResult> bets = cache.getBetMap().values().stream().filter(v -> v.getUserId().equals(uid))
|
||||
.map(bet -> {
|
||||
return new GetRoundInfoResponse.UserResult(
|
||||
MoneyUtil.toStr(bet.getBet()),
|
||||
bet.getBetSymbol(),
|
||||
MoneyUtil.toStr(bet.getWin()));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
resp.setUserResults(bets);
|
||||
return resp;
|
||||
}
|
||||
|
||||
public R<GetHistoryBetResponse> getHistoryBetInfo(GetHistoryBetRequest request, Long userId) {
|
||||
if (request.getPageNum() == null || request.getPageNum() <= 0) {
|
||||
request.setPageNum(1);
|
||||
}
|
||||
if (request.getPageSize() == null || request.getPageSize() <= 0) {
|
||||
request.setPageSize(10);
|
||||
}
|
||||
List<Integer> roundIds = wheelRoundBetMapper.selectHistoryBetRound(userId, DateTimeUtils.dateTime(), (request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
|
||||
|
||||
if (CollectionUtils.isEmpty(roundIds)) {
|
||||
GetHistoryBetResponse resp = new GetHistoryBetResponse();
|
||||
resp.setRows(new ArrayList<>());
|
||||
resp.setTotal(0);
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
List<GameWheelRoundBet> rounds = wheelRoundBetMapper.selectHistoryBetInfo(userId, roundIds);
|
||||
Map<Long, List<GameWheelRoundBet>> roundMap = rounds.stream().collect(Collectors.groupingBy(GameWheelRoundBet::getRoundId));
|
||||
|
||||
GetHistoryBetResponse resp = new GetHistoryBetResponse();
|
||||
resp.setTotal(rounds.size() + 1);
|
||||
resp.setRows(new ArrayList<>());
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss");
|
||||
for (Integer roundId : roundIds) {
|
||||
GetHistoryBetResponse.RoundItem item = new GetHistoryBetResponse.RoundItem();
|
||||
item.setRoundId(roundId.longValue());
|
||||
item.setEndTime(roundMap.get(roundId.longValue()).get(0).getUpdateTime().format(formatter));
|
||||
|
||||
List<GetHistoryBetResponse.Item> betitems = new ArrayList<>();
|
||||
BigDecimal totalBet = BigDecimal.ZERO;
|
||||
for (GameWheelRoundBet betItem : roundMap.get(roundId.longValue())) {
|
||||
GetHistoryBetResponse.Item i = new GetHistoryBetResponse.Item();
|
||||
i.setBet(MoneyUtil.toStr(betItem.getBet()));
|
||||
totalBet = totalBet.add(betItem.getBet());
|
||||
i.setBetSymbol(betItem.getBetSymbol());
|
||||
i.setWin(MoneyUtil.toStr(betItem.getWin()));
|
||||
betitems.add(i);
|
||||
}
|
||||
item.setTotalBet(MoneyUtil.toStr(totalBet));
|
||||
item.setItems(betitems);
|
||||
resp.getRows().add(item);
|
||||
}
|
||||
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
public R<GetRoundHistoryResponse> getRoundHistoryInfo(GetRoundHistoryRequest request) {
|
||||
if (request.getPageNum() == null || request.getPageNum() <= 0) {
|
||||
request.setPageNum(1);
|
||||
}
|
||||
if (request.getPageSize() == null || request.getPageSize() <= 0) {
|
||||
request.setPageSize(10);
|
||||
}
|
||||
|
||||
Page<GameWheelRound> page = new Page<>(request.getPageNum(), request.getPageSize());
|
||||
page.setOptimizeCountSql(false);
|
||||
LambdaQueryWrapper<GameWheelRound> wrapper = Wrappers.lambdaQuery();
|
||||
// 只获取今天的游戏回合数据
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDateTime startOfDay = today.atStartOfDay();
|
||||
LocalDateTime endOfDay = today.plusDays(1).atStartOfDay().minusSeconds(1);
|
||||
wrapper.between(GameWheelRound::getStartTime, startOfDay, endOfDay);
|
||||
wrapper.orderByDesc(GameWheelRound::getStartTime);
|
||||
IPage<GameWheelRound> listPage = wheelRoundMapper.selectPage(page, wrapper);
|
||||
List<GameWheelRound> userList = listPage.getRecords();
|
||||
|
||||
|
||||
GetRoundHistoryResponse response = new GetRoundHistoryResponse();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss");
|
||||
response.setRows(userList.stream().map(v -> {
|
||||
GetRoundHistoryResponse.Item item = new GetRoundHistoryResponse.Item();
|
||||
item.setRoundId(v.getRoundId().intValue());
|
||||
item.setEndTime(v.getEndTime().format(formatter));
|
||||
item.setResultSymbols(new ArrayList<>());
|
||||
if (StringUtils.isNotBlank(v.getResult())) {
|
||||
for (String i : v.getResult().split(",")) {
|
||||
int index = NumberUtils.toInt(i.split(":")[0], 0);
|
||||
item.getResultSymbols().add(String.valueOf(v.getSymbols().charAt(index)));
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}).collect(Collectors.toList()));
|
||||
response.setTotal(listPage.getTotal());
|
||||
|
||||
return R.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ruoyi.game.wheel.service;
|
||||
|
||||
|
||||
import com.ruoyi.common.utils.json.SnakeCaseJsonUtils;
|
||||
import com.ruoyi.game.wheel.domain.GameWheelGameConfig;
|
||||
import com.ruoyi.system.domain.SysConfig;
|
||||
import com.ruoyi.system.mapper.SysConfigMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CommonWheelService {
|
||||
@Autowired
|
||||
private SysConfigMapper sysConfigMapper;
|
||||
|
||||
public GameWheelGameConfig getGameConfig() {
|
||||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("game.wheel.setting");
|
||||
if (sysConfig == null) {
|
||||
GameWheelGameConfig config = new GameWheelGameConfig();
|
||||
config.setSwitchOff(false);
|
||||
config.setBets(Arrays.asList(5, 10, 20, 50, 100, 200, 500));
|
||||
config.setSymbolScores(Arrays.asList(
|
||||
new GameWheelGameConfig.SymbolScore("S", 0),
|
||||
new GameWheelGameConfig.SymbolScore("A", 10),
|
||||
new GameWheelGameConfig.SymbolScore("B", 10),
|
||||
new GameWheelGameConfig.SymbolScore("C", 10),
|
||||
new GameWheelGameConfig.SymbolScore("D", 10),
|
||||
new GameWheelGameConfig.SymbolScore("E", 10),
|
||||
new GameWheelGameConfig.SymbolScore("F", 10),
|
||||
new GameWheelGameConfig.SymbolScore("G", 10)
|
||||
));
|
||||
config.setSymbols("SSAABBCCDDEEFFGG");
|
||||
config.setBufferRatio(0.9);
|
||||
config.setRtp(0.85);
|
||||
|
||||
config.setSpecialEffectRatio(0.05);
|
||||
config.setSpecialEffects(Arrays.asList(
|
||||
new GameWheelGameConfig.SpecialEffect("系统赢", 0.5),
|
||||
new GameWheelGameConfig.SpecialEffect("免费旋转1次", 0.25),
|
||||
new GameWheelGameConfig.SpecialEffect("免费旋转2次", 0.15),
|
||||
new GameWheelGameConfig.SpecialEffect("免费旋转3次", 0.10)
|
||||
));
|
||||
|
||||
|
||||
sysConfig = new SysConfig();
|
||||
sysConfig.setConfigName("幸运转盘游戏配置");
|
||||
sysConfig.setConfigKey("game.wheel.setting");
|
||||
sysConfig.setConfigValue(SnakeCaseJsonUtils.toSnakeCaseJson(config));
|
||||
sysConfig.setConfigType("N");
|
||||
|
||||
sysConfigMapper.insertConfig(sysConfig);
|
||||
return config;
|
||||
}
|
||||
return SnakeCaseJsonUtils.fromSnakeCaseJson(sysConfig.getConfigValue(), GameWheelGameConfig.class);
|
||||
}
|
||||
|
||||
public void updateGameConfig(GameWheelGameConfig config) {
|
||||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("game.wheel.setting");
|
||||
if (sysConfig == null) {
|
||||
sysConfig = new SysConfig();
|
||||
sysConfig.setConfigName("幸运转盘游戏配置");
|
||||
sysConfig.setConfigKey("game.wheel.setting");
|
||||
sysConfig.setConfigType("N");
|
||||
sysConfig.setConfigValue(SnakeCaseJsonUtils.toSnakeCaseJson(config));
|
||||
sysConfigMapper.insertConfig(sysConfig);
|
||||
} else {
|
||||
sysConfig.setConfigValue(SnakeCaseJsonUtils.toSnakeCaseJson(config));
|
||||
sysConfigMapper.updateConfig(sysConfig);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user