Initial commit
This commit is contained in:
38
skins-service/.gitignore
vendored
Normal file
38
skins-service/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
16
skins-service/game-sugar-service/README.md
Normal file
16
skins-service/game-sugar-service/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
api: Spring项目入口模块,如果是web项目则包含controller定义
|
||||
|
||||
common: 项目通用类模块
|
||||
|
||||
contract: 协议、数据模型定义,比如dubbo、grpc的interface等,发布到maven仓库,给其他业务方使用。
|
||||
|
||||
repository:与数据库交互接口,包含dao层定义和mapper定义
|
||||
|
||||
service:服务接口定义和实现
|
||||
|
||||
domain: 领域模型模块,包含实体类、值对象等
|
||||
|
||||
enums
|
||||
|
||||
model
|
||||
|
||||
1
skins-service/game-sugar-service/doc/data.sql
Normal file
1
skins-service/game-sugar-service/doc/data.sql
Normal file
@@ -0,0 +1 @@
|
||||
todo
|
||||
134
skins-service/game-sugar-service/doc/init.sql
Normal file
134
skins-service/game-sugar-service/doc/init.sql
Normal file
@@ -0,0 +1,134 @@
|
||||
CREATE TABLE `game_sugar_spin` (
|
||||
`gid` bigint NOT NULL AUTO_INCREMENT COMMENT '一次spin的唯一标识符。连续的free_spin算一次spin',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '总得分',
|
||||
`count` int NOT NULL DEFAULT '0' COMMENT 'step总数',
|
||||
`feature` varchar(20) NOT NULL DEFAULT 'normal' COMMENT 'normal:普通spin free:购买的免费旋转 super_free:购买的超级免费旋转',
|
||||
`extra_free` int NOT NULL DEFAULT '0' COMMENT '本次spin中额外触发的免费spin数量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`gid`),
|
||||
KEY `idx_feature` (`feature`),
|
||||
KEY `idx_score` (`score`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏spin表';
|
||||
|
||||
|
||||
CREATE TABLE `game_sugar_step_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一自增id',
|
||||
`gid` bigint NOT NULL COMMENT '所属的spin id',
|
||||
`free_spin_id` int NOT NULL COMMENT '所属的free spin id',
|
||||
`aes` int NOT NULL COMMENT 'step在本次spin中的step序号',
|
||||
`multipler` varchar(512) DEFAULT '' COMMENT '开始前的倍数状态,偶数个,一对的前面是一维坐标,后面是倍数',
|
||||
`grid` varchar(128) DEFAULT '' COMMENT '开始前的网格状态',
|
||||
`symbol_links` varchar(1024) DEFAULT '' COMMENT '符号消除的集群信息,JSON格式',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '本次step的总倍数。基础倍数 * 级联翻倍',
|
||||
`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_gid_aes` (`gid`, `aes`) COMMENT 'gid+aes: 唯一标识一次step',
|
||||
KEY `idx_gid` (`gid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏旋转步骤详情表';
|
||||
|
||||
CREATE TABLE `game_sugar_spin_free` (
|
||||
`gid` bigint NOT NULL AUTO_INCREMENT COMMENT '一次spin的唯一标识符。连续的free_spin算一次spin',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '总得分',
|
||||
`count` int NOT NULL DEFAULT '0' COMMENT 'step总数',
|
||||
`feature` varchar(20) NOT NULL DEFAULT 'normal' COMMENT 'normal:普通spin free:购买的免费旋转 super_free:购买的超级免费旋转',
|
||||
`extra_free` int NOT NULL DEFAULT '0' COMMENT '本次spin中额外触发的免费spin数量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`gid`),
|
||||
KEY `idx_feature` (`feature`),
|
||||
KEY `idx_score` (`score`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏spin表';
|
||||
|
||||
|
||||
CREATE TABLE `game_sugar_step_info_free` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一自增id',
|
||||
`gid` bigint NOT NULL COMMENT '所属的spin id',
|
||||
`free_spin_id` int NOT NULL COMMENT '所属的free spin id',
|
||||
`aes` int NOT NULL COMMENT 'step在本次spin中的step序号',
|
||||
`multipler` varchar(512) DEFAULT '' COMMENT '开始前的倍数状态,偶数个,一对的前面是一维坐标,后面是倍数',
|
||||
`grid` varchar(128) DEFAULT '' COMMENT '开始前的网格状态',
|
||||
`symbol_links` varchar(1024) DEFAULT '' COMMENT '符号消除的集群信息,JSON格式',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '本次step的总倍数。基础倍数 * 级联翻倍',
|
||||
`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_gid_aes` (`gid`, `aes`) COMMENT 'gid+aes: 唯一标识一次step',
|
||||
KEY `idx_gid` (`gid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏旋转步骤详情表';
|
||||
|
||||
CREATE TABLE `game_sugar_spin_super` (
|
||||
`gid` bigint NOT NULL AUTO_INCREMENT COMMENT '一次spin的唯一标识符。连续的free_spin算一次spin',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '总得分',
|
||||
`count` int NOT NULL DEFAULT '0' COMMENT 'step总数',
|
||||
`feature` varchar(20) NOT NULL DEFAULT 'normal' COMMENT 'normal:普通spin free:购买的免费旋转 super_free:购买的超级免费旋转',
|
||||
`extra_free` int NOT NULL DEFAULT '0' COMMENT '本次spin中额外触发的免费spin数量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
||||
PRIMARY KEY (`gid`),
|
||||
KEY `idx_feature` (`feature`),
|
||||
KEY `idx_score` (`score`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏spin表';
|
||||
|
||||
|
||||
CREATE TABLE `game_sugar_step_info_super` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一自增id',
|
||||
`gid` bigint NOT NULL COMMENT '所属的spin id',
|
||||
`free_spin_id` int NOT NULL COMMENT '所属的free spin id',
|
||||
`aes` int NOT NULL COMMENT 'step在本次spin中的step序号',
|
||||
`multipler` varchar(512) DEFAULT '' COMMENT '开始前的倍数状态,偶数个,一对的前面是一维坐标,后面是倍数',
|
||||
`grid` varchar(128) DEFAULT '' COMMENT '开始前的网格状态',
|
||||
`symbol_links` varchar(1024) DEFAULT '' COMMENT '符号消除的集群信息,JSON格式',
|
||||
`score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '本次step的总倍数。基础倍数 * 级联翻倍',
|
||||
`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_gid_aes` (`gid`, `aes`) COMMENT 'gid+aes: 唯一标识一次step',
|
||||
KEY `idx_gid` (`gid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏旋转步骤详情表';
|
||||
|
||||
CREATE TABLE `game_sugar_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 '总spin次数',
|
||||
`free_count` int NOT NULL DEFAULT '0' COMMENT '总购买free spin次数',
|
||||
`super_free_count` int NOT NULL DEFAULT '0' COMMENT '总购买super_free spin次数',
|
||||
`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='糖果游戏用户表';
|
||||
|
||||
CREATE TABLE `game_sugar_win` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`date` datetime NOT NULL COMMENT '日期',
|
||||
`init_bet` decimal(10,2) NOT NULL COMMENT '系统初始下注额',
|
||||
`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 '总spin次数',
|
||||
`free_count` int NOT NULL DEFAULT '0' COMMENT '总购买free spin次数',
|
||||
`super_free_count` int NOT NULL DEFAULT '0' COMMENT '总购买super_free spin次数',
|
||||
`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_date` (`date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏每日统计表';
|
||||
|
||||
CREATE TABLE `game_sugar_record` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`user_id` bigint NOT NULL COMMENT 'user表的id',
|
||||
`bet` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用户下注额',
|
||||
`win` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用户赢得金额',
|
||||
`multiplier` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '倍率',
|
||||
`kind` varchar(12) NOT NULL DEFAULT '' COMMENT '游戏模式',
|
||||
`status` int NOT NULL DEFAULT 0 COMMENT '状态.0:进行中,1:完成',
|
||||
`extra_free` int NOT NULL DEFAULT '0' COMMENT '本次spin中额外触发的免费spin数量',
|
||||
`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`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='糖果游戏用户记录表';
|
||||
40
skins-service/game-sugar-service/pom.xml
Normal file
40
skins-service/game-sugar-service/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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>
|
||||
|
||||
<artifactId>game-sugar-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,10 @@
|
||||
package com.ruoyi.game.sugar.contract.admin.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AdminSugarUserListRequest {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private Long userId;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.game.sugar.contract.admin.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryUserRecordListRequest {
|
||||
private Integer uid;
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.game.sugar.contract.admin.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UpdateMockResultListRequest {
|
||||
private Integer uid;
|
||||
private List<Integer> normalScores;
|
||||
private List<Integer> freeScores;
|
||||
private List<Integer> superScores;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.game.sugar.contract.admin.response;
|
||||
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryGameConfig {
|
||||
private GameSugarConfig config;
|
||||
|
||||
// 全局奖池数据
|
||||
private double totalWin;
|
||||
private double totalBet;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.game.sugar.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryRankRequest {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
// "real": 实时记录 "multipler": 倍数全服记录 "win": 赢奖全服记录排行
|
||||
private String rankType;
|
||||
private String kind;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.game.sugar.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryRecordRequest {
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private String kind;
|
||||
private String rankType;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.game.sugar.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiSugarBuySpinResponse {
|
||||
private String credits;
|
||||
private String balance;
|
||||
private String cost;
|
||||
private Boolean isSuperFree;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.game.sugar.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiSugarHasFreeSpinResponse {
|
||||
private Boolean hasFreeSpin;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.game.sugar.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ApiSugarSpinResultResponse {
|
||||
private String balance;
|
||||
private String credits;
|
||||
private Integer index;
|
||||
private Integer remainingFreeSpins;
|
||||
private Integer extraFreeSpinCount;
|
||||
// normal:普通旋转模式 standard:购买的免费旋转状态 super:购买的超级免费旋转状态
|
||||
private String kind;
|
||||
private List<Map<String, Object>> data;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ruoyi.game.sugar.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class QueryRankResponse {
|
||||
private Integer total;
|
||||
private List<Record> rows;
|
||||
|
||||
@Data
|
||||
public static class Record {
|
||||
private String avatar;
|
||||
private String nickName;
|
||||
private String cost;
|
||||
private String kind;
|
||||
private String status;
|
||||
private String reward;
|
||||
private String multiplier;
|
||||
private String createTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.game.sugar.contract.api.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class QueryRecordResponse {
|
||||
private Integer total;
|
||||
private List<Record> rows;
|
||||
|
||||
@Data
|
||||
public static class Record {
|
||||
private String avatar;
|
||||
private String nickName;
|
||||
private String cost;
|
||||
private String kind;
|
||||
private String status;
|
||||
private String reward;
|
||||
private String createTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.ruoyi.game.sugar.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateTimeUtils;
|
||||
import com.ruoyi.game.sugar.contract.admin.request.AdminSugarUserListRequest;
|
||||
import com.ruoyi.game.sugar.contract.admin.request.QueryUserRecordListRequest;
|
||||
import com.ruoyi.game.sugar.contract.admin.request.UpdateMockResultListRequest;
|
||||
import com.ruoyi.game.sugar.contract.admin.response.QueryGameConfig;
|
||||
import com.ruoyi.game.sugar.domain.GameSugarRecord;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarRecordMapper;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.repository.SugarRewardPoolCacheRepository;
|
||||
import com.ruoyi.game.sugar.service.AdminSugarService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Api(tags = "极速永恒/Sugar Rush 1000")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/sugar")
|
||||
public class AdminSugarController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private AdminSugarService adminSugarService;
|
||||
@Autowired
|
||||
private ApiGameSugarRecordMapper gameSugarRecordMapper;
|
||||
@Autowired
|
||||
private SugarRewardPoolCacheRepository sugarRewardPoolCacheRepository;
|
||||
|
||||
// 方法参数是一个具体的类型,则不用加@RequestParam
|
||||
// 方法参数是一个Map,则需要加@RequestParam
|
||||
@ApiOperation("糖果用户信息列表")
|
||||
@GetMapping("/user/list")
|
||||
public PageDataInfo queryUserList(AdminSugarUserListRequest params) {
|
||||
// pageNum 从1开始
|
||||
Pair<Integer, List<Map<String, Object>>> list = adminSugarService.queryUserList(params);
|
||||
PageDataInfo<Map<String, Object>> pageDataInfo = getPageData(list.getValue());
|
||||
pageDataInfo.setTotal(list.getKey());
|
||||
return pageDataInfo;
|
||||
}
|
||||
|
||||
@ApiOperation("获取糖果游戏配置")
|
||||
@GetMapping("/gameconfig")
|
||||
public R<QueryGameConfig> getGameConfig() {
|
||||
GameSugarConfig config = adminSugarService.getGameConfig();
|
||||
QueryGameConfig result = new QueryGameConfig();
|
||||
result.setConfig(config);
|
||||
|
||||
var cache = sugarRewardPoolCacheRepository.getCache();
|
||||
result.setTotalWin(cache.getTotalWin().doubleValue());
|
||||
result.setTotalBet(cache.getTotalBet().doubleValue());
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
@ApiOperation("更新糖果游戏配置")
|
||||
@PostMapping("/gameconfig")
|
||||
public AjaxResult updateGameConfig(@RequestBody GameSugarConfig params) {
|
||||
String msg = adminSugarService.updateGameConfig(params);
|
||||
if (msg != null && !msg.isEmpty())
|
||||
return AjaxResult.error(msg);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@ApiOperation("糖果每日奖池信息列表")
|
||||
@GetMapping("/dailywin/list")
|
||||
public PageDataInfo queryDailyWinList(@RequestParam Map<String, Object> params) {
|
||||
Pair<Integer, List<Map<String, Object>>> list = adminSugarService.queryDailyWinList(params);
|
||||
PageDataInfo<Map<String, Object>> pageDataInfo = getPageData(list.getValue());
|
||||
pageDataInfo.setTotal(list.getKey());
|
||||
return pageDataInfo;
|
||||
}
|
||||
|
||||
@ApiOperation("糖果主播临时结果列表")
|
||||
@GetMapping("/mockresult/list")
|
||||
public R<Map<String, List<Integer>>> getMockResultList(@RequestParam("uid") Integer uid) {
|
||||
return R.ok(adminSugarService.getMockResultList(uid));
|
||||
}
|
||||
|
||||
@ApiOperation("糖果主播临时结果列表")
|
||||
@PostMapping("/mockresult/list")
|
||||
public R<Map<String, List<Integer>>> updateMockResultList(@RequestBody UpdateMockResultListRequest request) {
|
||||
var r = adminSugarService.updateMockResultList(request);
|
||||
return R.ok(r);
|
||||
}
|
||||
|
||||
@ApiOperation("游戏记录")
|
||||
@GetMapping("/user/record/list")
|
||||
public PageDataInfo queryUserRecordList(QueryUserRecordListRequest params) {
|
||||
if (params.getPageNum() == null) {
|
||||
params.setPageNum(1);
|
||||
}
|
||||
if (params.getPageSize() == null) {
|
||||
params.setPageSize(10);
|
||||
}
|
||||
|
||||
Page<GameSugarRecord> page = new Page<>(params.getPageNum(), params.getPageSize());
|
||||
var result = new LambdaQueryChainWrapper<>(gameSugarRecordMapper)
|
||||
.eq(params.getUid() != null, GameSugarRecord::getUserId, params.getUid())
|
||||
.ge(params.getStartTime() != null, GameSugarRecord::getCreateTime, params.getStartTime())
|
||||
.le(params.getEndTime() != null, GameSugarRecord::getCreateTime, params.getEndTime())
|
||||
.orderByDesc(GameSugarRecord::getUpdateTime)
|
||||
.page(page);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(result.getRecords())) {
|
||||
result.getRecords().forEach(item -> {
|
||||
item.setCreateTime(DateTimeUtils.utcToZone8(item.getCreateTime()));
|
||||
item.setUpdateTime(DateTimeUtils.utcToZone8(item.getUpdateTime()));
|
||||
});
|
||||
}
|
||||
|
||||
PageDataInfo<GameSugarRecord> pageDataInfo = new PageDataInfo<>();
|
||||
pageDataInfo.setTotal(result.getTotal());
|
||||
pageDataInfo.setRows(result.getRecords());
|
||||
return pageDataInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package com.ruoyi.game.sugar.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
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.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.utils.DateTimeUtils;
|
||||
import com.ruoyi.common.utils.MoneyUtil;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.game.sugar.contract.api.request.QueryRankRequest;
|
||||
import com.ruoyi.game.sugar.contract.api.request.QueryRecordRequest;
|
||||
import com.ruoyi.game.sugar.contract.api.response.*;
|
||||
import com.ruoyi.game.sugar.domain.GameSugarRecord;
|
||||
import com.ruoyi.game.sugar.enums.GameSugarRecordStatus;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarRecordMapper;
|
||||
import com.ruoyi.game.sugar.repository.SugarRewardPoolCacheRepository;
|
||||
import com.ruoyi.game.sugar.service.ApiSugarService;
|
||||
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.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Api(tags = "极速永恒/Sugar Rush 1000")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/sugar")
|
||||
public class ApiSugarController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiSugarService apiSugarService;
|
||||
@Autowired
|
||||
private ApiUserService userService;
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Autowired
|
||||
private ApiGameSugarRecordMapper recordMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
@Autowired
|
||||
private SugarRewardPoolCacheRepository sugarRewardPoolCacheRepository;
|
||||
|
||||
public R checkLogin() {
|
||||
Long userId;
|
||||
try {
|
||||
userId = getUserId();
|
||||
if (ObjectUtil.isEmpty(userId)) return R.fail(401, "登录过期,请重新登录。");
|
||||
return R.ok(userId);
|
||||
} catch (Exception e) {
|
||||
return R.fail("登录过期,请重新登录。");
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("进行一次旋转")
|
||||
@UpdateUserCache
|
||||
@GetMapping("/dospin")
|
||||
public R<ApiSugarSpinResultResponse> dospin(@RequestParam("bet") Long bet) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
|
||||
Long userId = getUserId();
|
||||
var r = userService.checkRecharge(userId.intValue());
|
||||
if (R.isError(r)) {
|
||||
return R.fail(r.getMsg());
|
||||
}
|
||||
return apiSugarService.doSpin(userId, BigDecimal.valueOf(bet));
|
||||
}
|
||||
|
||||
@ApiOperation("购买免费旋转")
|
||||
@UpdateUserCache
|
||||
@GetMapping("/buy_free_spins")
|
||||
public R<ApiSugarBuySpinResponse> buyFreeSpins(@RequestParam("kind") String kind, @RequestParam("bet") Long bet) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
|
||||
Long userId = getUserId();
|
||||
var r = userService.checkRecharge(userId.intValue());
|
||||
if (R.isError(r)) {
|
||||
return R.fail(r.getMsg());
|
||||
}
|
||||
return apiSugarService.buyFreeSpins(userId, kind, BigDecimal.valueOf(bet));
|
||||
}
|
||||
|
||||
@ApiOperation("检查是否有免费旋转")
|
||||
@GetMapping("/has_free_spin")
|
||||
public R<ApiSugarHasFreeSpinResponse> hasFreeSpin() {
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
|
||||
Long userId = getUserId();
|
||||
return apiSugarService.hasFreeSpin(userId);
|
||||
}
|
||||
|
||||
@ApiOperation("查询记录")
|
||||
@GetMapping("/records")
|
||||
public R<QueryRecordResponse> queryRecords(QueryRecordRequest request) {
|
||||
if (request.getPageNum() == null || request.getPageNum() <= 0) {
|
||||
request.setPageNum(1);
|
||||
}
|
||||
if (request.getPageSize() == null || request.getPageSize() <= 0) {
|
||||
request.setPageSize(10);
|
||||
}
|
||||
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
|
||||
Long userId = getUserId();
|
||||
|
||||
|
||||
Page<GameSugarRecord> page = new Page<>(request.getPageNum(), request.getPageSize());
|
||||
var query = new LambdaQueryChainWrapper<>(recordMapper).eq(GameSugarRecord::getUserId, userId);
|
||||
if (StringUtils.isNotBlank(request.getKind())) {
|
||||
String t = switch (request.getKind()) {
|
||||
case "normal" -> "";
|
||||
case "free" -> "standard";
|
||||
case "super" -> "super";
|
||||
default -> null;
|
||||
};
|
||||
if (t != null) {
|
||||
query.eq(GameSugarRecord::getKind, t);
|
||||
}
|
||||
}
|
||||
|
||||
var data = query.orderByDesc(GameSugarRecord::getWin).page(page);
|
||||
|
||||
// 个人记录只查当前用户,直接单条查询用户信息
|
||||
TtUser currentUser = ttUserMapper.selectTtUserById((long) userId.intValue());
|
||||
|
||||
var rows = data.getRecords().stream()
|
||||
.map(v -> {
|
||||
QueryRecordResponse.Record item = new QueryRecordResponse.Record();
|
||||
if (currentUser != null) {
|
||||
item.setAvatar(currentUser.getAvatar());
|
||||
item.setNickName(currentUser.getNickName());
|
||||
}
|
||||
item.setCost(MoneyUtil.toStr(v.getBet()));
|
||||
item.setReward(MoneyUtil.toStr(v.getWin()));
|
||||
item.setKind(switch (v.getKind()) {
|
||||
case "standard" -> "free";
|
||||
case "super" -> "super";
|
||||
default -> "normal";
|
||||
});
|
||||
item.setCreateTime(DateTimeUtils.dateTime(v.getCreateTime(), "MM-dd HH:mm:ss"));
|
||||
var status = GameSugarRecordStatus.parseCode(v.getStatus());
|
||||
if (status == null) {
|
||||
item.setStatus("未知");
|
||||
} else {
|
||||
item.setStatus(status.getDesc());
|
||||
}
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
QueryRecordResponse resp = new QueryRecordResponse();
|
||||
resp.setTotal((int) data.getTotal());
|
||||
resp.setRows(rows);
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
@ApiOperation("查询全服排行记录")
|
||||
@GetMapping("/rank_records")
|
||||
public R<QueryRankResponse> queryRankRecords(QueryRankRequest request) {
|
||||
if (request.getPageNum() == null || request.getPageNum() <= 0) {
|
||||
request.setPageNum(1);
|
||||
}
|
||||
if (request.getPageSize() == null || request.getPageSize() <= 0) {
|
||||
request.setPageSize(10);
|
||||
}
|
||||
|
||||
if (request.getPageNum() * request.getPageSize() > 100) {
|
||||
QueryRankResponse resp = new QueryRankResponse();
|
||||
resp.setRows(Collections.emptyList());
|
||||
resp.setTotal(0);
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
|
||||
Page<GameSugarRecord> page = new Page<>(request.getPageNum(), request.getPageSize());
|
||||
var query = new LambdaQueryChainWrapper<>(recordMapper);
|
||||
if (StringUtils.isNotBlank(request.getKind())) {
|
||||
String t = switch (request.getKind()) {
|
||||
case "normal" -> "";
|
||||
case "free" -> "standard";
|
||||
case "super" -> "super";
|
||||
default -> null;
|
||||
};
|
||||
if (t != null) {
|
||||
query.eq(GameSugarRecord::getKind, t);
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.equals(request.getRankType(), "multipler")) {
|
||||
query.orderByDesc(GameSugarRecord::getMultiplier);
|
||||
} else if (Objects.equals(request.getRankType(), "real")) {
|
||||
query.orderByDesc(GameSugarRecord::getCreateTime);
|
||||
} else if (Objects.equals(request.getRankType(), "win")) {
|
||||
query.orderByDesc(GameSugarRecord::getWin);
|
||||
}
|
||||
|
||||
var data = query.page(page);
|
||||
if (CollectionUtils.isEmpty(data.getRecords())) {
|
||||
QueryRankResponse resp = new QueryRankResponse();
|
||||
resp.setTotal((int) data.getTotal());
|
||||
resp.setRows(Collections.emptyList());
|
||||
return R.ok(resp);
|
||||
}
|
||||
|
||||
//var userIds = data.getRecords().stream().map(GameSugarRecord::getUserId).collect(Collectors.toList());
|
||||
var userIds = data.getRecords().stream().map(v -> v.getUserId().intValue()).distinct().collect(Collectors.toList());
|
||||
var userMap = ttUserMapper.selectByIds(userIds).stream()
|
||||
.collect(Collectors.toMap(v -> v.getUserId().longValue(), Function.identity()));
|
||||
|
||||
var rows = data.getRecords().stream()
|
||||
.filter(v -> {
|
||||
// 过滤机器人(03),不允许上榜
|
||||
var user = userMap.get(v.getUserId());
|
||||
return user != null && !"03".equals(user.getUserType());
|
||||
})
|
||||
.map(v -> {
|
||||
QueryRankResponse.Record item = new QueryRankResponse.Record();
|
||||
if (userMap.containsKey(v.getUserId())) {
|
||||
item.setAvatar(userMap.get(v.getUserId()).getAvatar());
|
||||
item.setNickName(userMap.get(v.getUserId()).getNickName());
|
||||
}
|
||||
item.setCost(MoneyUtil.toStr(v.getBet()));
|
||||
item.setCost(MoneyUtil.toStr(v.getBet()));
|
||||
item.setReward(MoneyUtil.toStr(v.getWin()));
|
||||
item.setKind(switch (v.getKind()) {
|
||||
case "standard" -> "free";
|
||||
case "super" -> "super";
|
||||
default -> "normal";
|
||||
});
|
||||
item.setMultiplier(MoneyUtil.toStr(v.getMultiplier()));
|
||||
item.setCreateTime(DateTimeUtils.dateTime(v.getCreateTime(), "MM-dd HH:mm:ss"));
|
||||
var status = GameSugarRecordStatus.parseCode(v.getStatus());
|
||||
if (status == null) {
|
||||
item.setStatus("未知");
|
||||
} else {
|
||||
item.setStatus(status.getDesc());
|
||||
}
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
QueryRankResponse resp = new QueryRankResponse();
|
||||
resp.setTotal((int) data.getTotal());
|
||||
resp.setRows(rows);
|
||||
return R.ok(resp);
|
||||
}
|
||||
@ApiOperation("清空极速永恒奖池缓存(总下注/总返奖)归零")
|
||||
@PostMapping("/rewardpool/reset")
|
||||
public R resetRewardPool() {
|
||||
sugarRewardPoolCacheRepository.resetCache();
|
||||
return R.ok(null, "奖池缓存已清空");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ruoyi.game.sugar.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 糖果游戏用户记录表
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@TableName(value = "game_sugar_record")
|
||||
public class GameSugarRecord {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private BigDecimal bet;
|
||||
private BigDecimal win;
|
||||
private BigDecimal multiplier;
|
||||
private String kind;
|
||||
private Integer extraFree;
|
||||
/**
|
||||
* @see com.ruoyi.game.sugar.enums.GameSugarRecordStatus
|
||||
*/
|
||||
private Integer status;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.game.sugar.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum GameSugarRecordStatus {
|
||||
RUNING(0, "进行中"),
|
||||
COMPLETE(1, "完成");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
|
||||
GameSugarRecordStatus(Integer code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static GameSugarRecordStatus parseCode(Integer code) {
|
||||
for (GameSugarRecordStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface AdminGameSugarSpinMapper {
|
||||
|
||||
@Select("SELECT max(score) FROM game_sugar_spin${type}")
|
||||
Integer select(@Param("type") String type);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface AdminGameSugarUserMapper extends BaseMapper<GameSugarUser> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.GameSugarWin;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface AdminGameSugarWinMapper extends BaseMapper<GameSugarWin> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.game.sugar.domain.GameSugarRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameSugarRecordMapper extends BaseMapper<GameSugarRecord> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarSpin;
|
||||
import com.ruoyi.domain.entity.GameSugarStepInfo;
|
||||
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 ApiGameSugarSpinMapper {
|
||||
|
||||
@Select("SELECT * FROM game_sugar_spin${type} where ${where} ORDER BY score LIMIT 1")
|
||||
GameSugarSpin selectMinOne(@Param("type") String type, @Param("where")String whereSql);
|
||||
|
||||
|
||||
@Select("SELECT * FROM game_sugar_spin${type} where ${where} ORDER BY score LIMIT 5")
|
||||
List<GameSugarSpin> selectMinFive(@Param("type") String type, @Param("where")String whereSql);
|
||||
|
||||
@Select("SELECT * FROM game_sugar_spin${type} where ${where} ORDER BY RAND() LIMIT 1")
|
||||
GameSugarSpin selectRandomOne(@Param("type") String type, @Param("where")String whereSql);
|
||||
|
||||
@Select("SELECT * FROM game_sugar_spin${type} WHERE gid = #{gid} LIMIT 1")
|
||||
GameSugarSpin selectByGid(@Param("gid") Long gid, @Param("type") String type);
|
||||
|
||||
@Select("SELECT * FROM game_sugar_step_info${type} WHERE gid = #{gid}")
|
||||
List<GameSugarStepInfo> selectStepsByGid(@Param("gid") Long gid, @Param("type") String type);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameSugarUserMapper {
|
||||
|
||||
|
||||
@Select("SELECT * FROM game_sugar_user where user_id = #{uid}")
|
||||
GameSugarUser select(@Param("uid") Integer uid);
|
||||
|
||||
/**
|
||||
* 插入用户数据,如果 user_id 已存在则更新
|
||||
*/
|
||||
@Insert("INSERT INTO game_sugar_user " +
|
||||
"(user_id, total_bet, total_win, count, free_count, super_free_count) " +
|
||||
"VALUES " +
|
||||
"(#{user.userId}, #{user.totalBet}, #{user.totalWin}, #{user.count}, #{user.freeCount}, #{user.superFreeCount}) " +
|
||||
"ON DUPLICATE KEY UPDATE " +
|
||||
"total_bet = VALUES(total_bet), " +
|
||||
"total_win = VALUES(total_win), " +
|
||||
"count = VALUES(count), " +
|
||||
"free_count = VALUES(free_count), " +
|
||||
"super_free_count = VALUES(super_free_count), " +
|
||||
"update_time = NOW()")
|
||||
long insertOrUpdate(@Param("user") GameSugarUser user);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.game.sugar.mapper;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarWin;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
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;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface ApiGameSugarWinMapper {
|
||||
|
||||
@Select("SELECT * FROM game_sugar_win where date = #{date} LIMIT 1")
|
||||
GameSugarWin select(@Param("date") LocalDateTime dateTime);
|
||||
|
||||
|
||||
@Insert("INSERT INTO game_sugar_win " +
|
||||
"(date, init_bet, total_bet, total_win, count, free_count, super_free_count) " +
|
||||
"VALUES " +
|
||||
"(#{record.date}, #{record.initBet}, #{record.totalBet}, #{record.totalWin}, #{record.count}, #{record.freeCount}, #{record.superFreeCount})")
|
||||
long insert(@Param("record") GameSugarWin record);
|
||||
|
||||
/**
|
||||
* 通用更新方法:根据传入的字段动态累加
|
||||
* 如果某个字段为 null,则不更新该字段
|
||||
*/
|
||||
@Update("UPDATE game_sugar_win " +
|
||||
"SET count = count + #{count}, " +
|
||||
"free_count = #{freeCount} + free_count, " +
|
||||
"super_free_count = #{superFreeCount} + super_free_count " +
|
||||
"WHERE date = #{date}")
|
||||
void updateCount(@Param("date") LocalDateTime date,
|
||||
@Param("count") Integer count,
|
||||
@Param("freeCount") Integer freeCount,
|
||||
@Param("superFreeCount") Integer superFreeCount);
|
||||
|
||||
@Update("UPDATE game_sugar_win " +
|
||||
"SET total_bet = total_bet + #{bet}, " +
|
||||
"total_win = total_win + #{win} " +
|
||||
"WHERE date = #{date}")
|
||||
void updateAmount(@Param("date") LocalDateTime date,
|
||||
@Param("bet") BigDecimal bet,
|
||||
@Param("win") BigDecimal win);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.game.sugar.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GameSugarConfig {
|
||||
private double defaultAwardPool;
|
||||
private int[] jpPercent;
|
||||
private double rtp;
|
||||
private double minProfit;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.game.sugar.model.cache;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarStepInfo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SugarGameCache implements Serializable {
|
||||
private BigDecimal bet;
|
||||
|
||||
private BigDecimal accountAmount;
|
||||
private BigDecimal accountCredits;
|
||||
|
||||
private String kind;
|
||||
private double globalRtp; // 本局开始时的奖池RTP,用于判断是否触发免费旋转
|
||||
private Integer totalFreeSpins;
|
||||
private Integer freeSpinIndex;
|
||||
private BigDecimal totalWin;
|
||||
private GameSpin gameData;
|
||||
private Long recordId;
|
||||
|
||||
@Data
|
||||
public static class GameSpin implements Serializable {
|
||||
private Long gid;
|
||||
private BigDecimal score;
|
||||
private List<GameSugarStepInfo> steps;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.ruoyi.game.sugar.model.cache;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SugarGameConfigCache {
|
||||
private double globalRtp;
|
||||
private double normalWinBoundary; // 1
|
||||
private double freeWinBoundary; // 50
|
||||
private double superWinBoundary; // 200
|
||||
|
||||
// RTP切换阈值(0.6)
|
||||
private double rtpSwitchThreshold;
|
||||
|
||||
// 奖池 globalRtp >= 0.6
|
||||
private List<AwardItem> normalAwardItems;
|
||||
private List<AwardItem> freeAwardItems;
|
||||
private List<AwardItem> superAwardItems;
|
||||
|
||||
// globalRtp < 0.6
|
||||
private List<AwardItem> normalAwardItems2;
|
||||
private List<AwardItem> freeAwardItems2;
|
||||
private List<AwardItem> superAwardItems2;
|
||||
|
||||
@Data
|
||||
public static class AwardItem {
|
||||
private double lowRewardRound;
|
||||
private double highRewardRound;
|
||||
private double weight;
|
||||
}
|
||||
|
||||
public static SugarGameConfigCache init() {
|
||||
SugarGameConfigCache config = new SugarGameConfigCache();
|
||||
|
||||
// 初始化全局rtp
|
||||
config.setGlobalRtp(0);
|
||||
|
||||
// 初始化边界值
|
||||
config.setNormalWinBoundary(1);
|
||||
config.setFreeWinBoundary(50);
|
||||
config.setSuperWinBoundary(200);
|
||||
|
||||
// RTP 切换阈值
|
||||
config.setRtpSwitchThreshold(0.6);
|
||||
|
||||
// 配置(globalRtp >= 0.6)
|
||||
config.setNormalAwardItems(initNormalAwardItems());
|
||||
config.setFreeAwardItems(initFreeAwardItems());
|
||||
config.setSuperAwardItems(initSuperAwardItems());
|
||||
|
||||
// 配置(globalRtp < 0.6)
|
||||
config.setNormalAwardItems2(initNormalAwardItems2());
|
||||
config.setFreeAwardItems2(initFreeAwardItems2());
|
||||
config.setSuperAwardItems2(initSuperAwardItems2());
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// 普通旋转(globalRtp >= 0.6)
|
||||
private static List<AwardItem> initNormalAwardItems() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(0, 0, 60)); // 0 倍 60%
|
||||
awardItems.add(makeItem(0, 0.9, 20)); // 0-0.9 倍 20%
|
||||
awardItems.add(makeItem(1, 3, 10)); // 1-3 倍 10%
|
||||
awardItems.add(makeItem(3.1, 5, 4)); // 3.1-5 倍 4%
|
||||
awardItems.add(makeItem(5.1, 8, 3)); // 5.1-8 倍 3%
|
||||
awardItems.add(makeItem(8.1, 12, 2)); // 8.1-12 倍 2%
|
||||
awardItems.add(makeItem(12.1, 16, 1)); // 12.1-16 倍 1%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
// 免费旋转(globalRtp >= 0.6)
|
||||
private static List<AwardItem> initFreeAwardItems() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(0, 10, 50)); // 0-10 倍 50%
|
||||
awardItems.add(makeItem(25, 30, 30)); // 25-30 倍 30%
|
||||
awardItems.add(makeItem(40, 79, 9)); // 40-79 倍 9%
|
||||
awardItems.add(makeItem(80, 150, 6)); // 80-150 倍 6%
|
||||
awardItems.add(makeItem(200, 500, 4)); // 200-500 倍 4%
|
||||
awardItems.add(makeItem(600, 1500, 1)); // 600-1500 倍 1%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
// 超级旋转(globalRtp >= 0.6)
|
||||
private static List<AwardItem> initSuperAwardItems() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(10, 40, 25)); // 10-40 倍 25%
|
||||
awardItems.add(makeItem(61, 90, 25)); // 61-90 倍 25%
|
||||
awardItems.add(makeItem(110, 180, 10)); // 110-180 倍 10%
|
||||
awardItems.add(makeItem(210, 260, 15)); // 210-260 倍 15%
|
||||
awardItems.add(makeItem(285, 310, 10)); // 285-310 倍 10%
|
||||
awardItems.add(makeItem(380, 420, 10)); // 380-420 倍 10%
|
||||
awardItems.add(makeItem(460, 560, 3)); // 460-560 倍 3%
|
||||
awardItems.add(makeItem(700, 1000, 1)); // 700-1000 倍 1%
|
||||
awardItems.add(makeItem(1500, 3000, 1)); // 1500-3000 倍 1%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
// 普通旋转(globalRtp < 0.6)
|
||||
private static List<AwardItem> initNormalAwardItems2() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(0, 0, 50)); // 0 倍 50%
|
||||
awardItems.add(makeItem(0, 0.9, 20)); // 0-0.9 倍 20%
|
||||
awardItems.add(makeItem(1, 3, 15)); // 1-3 倍 15%
|
||||
awardItems.add(makeItem(4, 5, 6)); // 4-5 倍 6%
|
||||
awardItems.add(makeItem(6, 8, 4)); // 6-8 倍 4%
|
||||
awardItems.add(makeItem(10, 12, 3)); // 10-12 倍 3%
|
||||
awardItems.add(makeItem(13, 15, 1.5));// 13-15 倍 1.5%
|
||||
awardItems.add(makeItem(30, 40, 0.3));// 30-40 倍 0.3%
|
||||
awardItems.add(makeItem(50, 60, 0.1));// 50-60 倍 0.1%
|
||||
awardItems.add(makeItem(60, 80, 0.1));// 60-80 倍 0.1%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
// 免费旋转(globalRtp < 0.6)
|
||||
private static List<AwardItem> initFreeAwardItems2() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(0, 10, 42)); // 0-10 倍 42%
|
||||
awardItems.add(makeItem(31, 39, 30)); // 31-39 倍 30%
|
||||
awardItems.add(makeItem(50, 81, 11)); // 50-81 倍 11%
|
||||
awardItems.add(makeItem(101, 151, 8)); // 101-151 倍 8%
|
||||
awardItems.add(makeItem(201, 460, 6)); // 201-460 倍 6%
|
||||
awardItems.add(makeItem(500, 600, 2)); // 500-600 倍 2%
|
||||
awardItems.add(makeItem(600, 1100, 1)); // 600-1100 倍 1%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
// 超级旋转(globalRtp < 0.6)
|
||||
private static List<AwardItem> initSuperAwardItems2() {
|
||||
List<AwardItem> awardItems = new ArrayList<>();
|
||||
awardItems.add(makeItem(10, 40, 20)); // 10-40 倍 20% 5
|
||||
awardItems.add(makeItem(61, 90, 20)); // 61-90 倍 20% 15.1
|
||||
awardItems.add(makeItem(110, 180, 10)); // 110-180 倍 10% 14
|
||||
awardItems.add(makeItem(210, 260, 15)); // 210-260 倍 15% 35
|
||||
awardItems.add(makeItem(315, 320, 10)); // 315-320 倍 10% 31
|
||||
awardItems.add(makeItem(360, 400, 10)); // 360-400 倍 10% 38
|
||||
awardItems.add(makeItem(460, 560, 10)); // 460-560 倍 10% 51
|
||||
awardItems.add(makeItem(600, 700, 5)); // 600-700 倍 5% 32
|
||||
awardItems.add(makeItem(800, 1200, 3)); // 800-1200 倍 3% 30
|
||||
awardItems.add(makeItem(1300, 2000, 2)); // 1300-2000 倍 2%
|
||||
return awardItems;
|
||||
}
|
||||
|
||||
private static AwardItem makeItem(double low, double high, double weight) {
|
||||
AwardItem item = new AwardItem();
|
||||
item.setLowRewardRound(low);
|
||||
item.setHighRewardRound(high);
|
||||
item.setWeight(weight);
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.game.sugar.model.cache;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class SugarGameMockCache implements Serializable {
|
||||
private List<MockItem> normalScores;
|
||||
private List<MockItem> freeScores;
|
||||
private List<MockItem> superScores;
|
||||
|
||||
@Data
|
||||
public static class MockItem {
|
||||
private int score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.game.sugar.model.cache;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class SugarRewardPoolCache {
|
||||
private BigDecimal totalBet;
|
||||
private BigDecimal totalWin;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.game.sugar.model.cache;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarSpin;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SugarUserGameRecordCache {
|
||||
|
||||
List<Record> spinRecords;
|
||||
|
||||
@Data
|
||||
public static class Record implements Serializable {
|
||||
private GameSugarSpin spin;
|
||||
private BigDecimal reward;
|
||||
private BigDecimal cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.game.sugar.repository;
|
||||
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.ruoyi.admin.config.RedisConstants.SUGAR_GAME_CONFIG_CACHE;
|
||||
|
||||
@Component
|
||||
public class GameConfigCacheRepository {
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
public void setCache(SugarGameConfigCache value) {
|
||||
redisCache.setCacheObject(SUGAR_GAME_CONFIG_CACHE, value, 7, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
public SugarGameConfigCache getCache() {
|
||||
return redisCache.getCacheObject(SUGAR_GAME_CONFIG_CACHE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.game.sugar.repository;
|
||||
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameMockCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.ruoyi.admin.config.RedisConstants.SUGAR_GAME_CACHE;
|
||||
import static com.ruoyi.admin.config.RedisConstants.SUGAR_GAME_MOCK_QUEUE;
|
||||
|
||||
@Component
|
||||
public class SugarCacheRepository {
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
public void setCache(Long uid, SugarGameCache value) {
|
||||
String cacheKey = SUGAR_GAME_CACHE + uid;
|
||||
redisCache.setCacheObject(cacheKey, value, 7, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
public SugarGameCache getCache(Long uid) {
|
||||
String cacheKey = SUGAR_GAME_CACHE + uid;
|
||||
return redisCache.getCacheObject(cacheKey);
|
||||
}
|
||||
|
||||
public void setMockResultCache(Integer uid, SugarGameMockCache value) {
|
||||
String cacheKey = SUGAR_GAME_MOCK_QUEUE + uid;
|
||||
redisCache.setCacheObject(cacheKey, value, 7, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
public SugarGameMockCache getMockResultCache(Integer uid) {
|
||||
String cacheKey = SUGAR_GAME_MOCK_QUEUE + uid;
|
||||
return redisCache.getCacheObject(cacheKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ruoyi.game.sugar.repository;
|
||||
|
||||
import com.ruoyi.game.sugar.model.cache.SugarRewardPoolCache;
|
||||
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.SUGAR_GAME_REWARD_POOL_CACHE;
|
||||
|
||||
@Component
|
||||
public class SugarRewardPoolCacheRepository {
|
||||
|
||||
@Autowired
|
||||
public RedisTemplate redisTemplate;
|
||||
|
||||
public void incrementalBet(BigDecimal totalBet) {
|
||||
redisTemplate.opsForValue().increment(SUGAR_GAME_REWARD_POOL_CACHE + ":bet", totalBet.doubleValue());
|
||||
}
|
||||
|
||||
public void incrementalWin(BigDecimal totalWin) {
|
||||
redisTemplate.opsForValue().increment(SUGAR_GAME_REWARD_POOL_CACHE + ":win", totalWin.doubleValue());
|
||||
}
|
||||
|
||||
public SugarRewardPoolCache getCache() {
|
||||
var totalBetS = redisTemplate.opsForValue().get(SUGAR_GAME_REWARD_POOL_CACHE + ":bet");
|
||||
var totalWinS = redisTemplate.opsForValue().get(SUGAR_GAME_REWARD_POOL_CACHE + ":win");
|
||||
SugarRewardPoolCache cache = new SugarRewardPoolCache();
|
||||
cache.setTotalBet(totalBetS == null ? BigDecimal.ZERO : new BigDecimal(totalBetS.toString()));
|
||||
cache.setTotalWin(totalWinS == null ? BigDecimal.ZERO : new BigDecimal(totalWinS.toString()));
|
||||
return cache;
|
||||
}
|
||||
// 清空奖池缓存(总下注、总返奖归零)
|
||||
public void resetCache() {
|
||||
redisTemplate.delete(SUGAR_GAME_REWARD_POOL_CACHE + ":bet");
|
||||
redisTemplate.delete(SUGAR_GAME_REWARD_POOL_CACHE + ":win");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.game.sugar.repository;
|
||||
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarUserGameRecordCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.ruoyi.admin.config.RedisConstants.SUGAR_GAME_USER_RECORD_CACHE;
|
||||
|
||||
@Component
|
||||
public class UserRecordCacheRepository {
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
public void setCache(Long uid, SugarUserGameRecordCache value) {
|
||||
String cacheKey = SUGAR_GAME_USER_RECORD_CACHE + uid;
|
||||
redisCache.setCacheObject(cacheKey, value, 7, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
public SugarUserGameRecordCache getCache(Long uid) {
|
||||
String cacheKey = SUGAR_GAME_USER_RECORD_CACHE + uid;
|
||||
return redisCache.getCacheObject(cacheKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package com.ruoyi.game.sugar.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.plugins.pagination.Page;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.common.utils.json.SnakeCaseJsonUtils;
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import com.ruoyi.domain.entity.GameSugarWin;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.game.sugar.contract.admin.request.AdminSugarUserListRequest;
|
||||
import com.ruoyi.game.sugar.contract.admin.request.UpdateMockResultListRequest;
|
||||
import com.ruoyi.game.sugar.mapper.AdminGameSugarSpinMapper;
|
||||
import com.ruoyi.game.sugar.mapper.AdminGameSugarUserMapper;
|
||||
import com.ruoyi.game.sugar.mapper.AdminGameSugarWinMapper;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameMockCache;
|
||||
import com.ruoyi.game.sugar.repository.SugarCacheRepository;
|
||||
import com.ruoyi.system.domain.SysConfig;
|
||||
import com.ruoyi.system.mapper.SysConfigMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminSugarService {
|
||||
|
||||
private static final String EVENT_TAG = "糖果冲刺1000";
|
||||
|
||||
@Autowired
|
||||
private AdminGameSugarUserMapper adminGameSugarUserMapper;
|
||||
|
||||
@Autowired
|
||||
private AdminGameSugarWinMapper adminGameSugarWinMapper;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
@Autowired
|
||||
private SysConfigMapper sysConfigMapper;
|
||||
|
||||
@Autowired
|
||||
private SugarCacheRepository cacheRepository;
|
||||
|
||||
@Autowired
|
||||
private AdminGameSugarSpinMapper adminGameSugarSpinMapper;
|
||||
|
||||
public Pair<Integer, List<Map<String, Object>>> queryUserList(AdminSugarUserListRequest params) {
|
||||
Integer pageNum = params.getPageNum();
|
||||
Integer pageSize = params.getPageSize();
|
||||
if (pageNum == null || pageNum <= 0) pageNum = 1;
|
||||
if (pageSize == null || pageSize <= 0) pageSize = 10;
|
||||
|
||||
Page<GameSugarUser> page = new Page<>(pageNum, pageSize);
|
||||
page.setOptimizeCountSql(false);
|
||||
LambdaQueryWrapper<GameSugarUser> wrapper = Wrappers.lambdaQuery();
|
||||
if (params.getUserId() != null) {
|
||||
wrapper.eq(GameSugarUser::getUserId, params.getUserId());
|
||||
}
|
||||
|
||||
adminGameSugarUserMapper.selectList(wrapper);
|
||||
IPage<GameSugarUser> userListPage = adminGameSugarUserMapper.selectPage(page, wrapper);
|
||||
List<GameSugarUser> userList = userListPage.getRecords();
|
||||
|
||||
List<Map<String, Object>> r = userList.stream().map(v -> {
|
||||
Map<String, Object> user = SnakeCaseJsonUtils.toSnakeCaseMap(v);
|
||||
user.put("rtp", (v.getTotalBet().compareTo(BigDecimal.ZERO) > 0) ? v.getTotalWin().divide(v.getTotalBet(), 2, RoundingMode.HALF_UP) : 0);
|
||||
user.put("profit", v.getTotalWin().subtract(v.getTotalBet()));
|
||||
|
||||
Long userId = v.getUserId().longValue();
|
||||
TtUser ttUser = ttUserMapper.selectById(userId);
|
||||
if (ttUser != null) {
|
||||
user.put("nickname", ttUser.getNickName());
|
||||
user.put("avatar", ttUser.getAvatar());
|
||||
user.put("account_amount", ttUser.getAccountAmount());
|
||||
user.put("account_credits", ttUser.getAccountCredits());
|
||||
}
|
||||
return user;
|
||||
}).collect(Collectors.toList());
|
||||
Integer total = (int) userListPage.getTotal();
|
||||
return new Pair<>(total, r);
|
||||
}
|
||||
|
||||
public GameSugarConfig getGameConfig() {
|
||||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("game.sugar.setting");
|
||||
if (sysConfig == null) {
|
||||
GameSugarConfig config = new GameSugarConfig();
|
||||
config.setRtp(0.85);
|
||||
config.setJpPercent(new int[]{10, 100});
|
||||
config.setDefaultAwardPool(0);
|
||||
config.setMinProfit(0.3);
|
||||
return config;
|
||||
}
|
||||
return SnakeCaseJsonUtils.fromSnakeCaseJson(sysConfig.getConfigValue(), GameSugarConfig.class);
|
||||
}
|
||||
|
||||
public String updateGameConfig(GameSugarConfig params) {
|
||||
if (params.getJpPercent().length != 2) {
|
||||
return "jpPercent长度必须为2";
|
||||
}
|
||||
if (params.getRtp() < 0 || params.getRtp() > 1) {
|
||||
return "rtp必须在[0,1]之间";
|
||||
}
|
||||
if (params.getMinProfit() < 0 || params.getMinProfit() > 1) {
|
||||
return "minProfit必须在[0,1]之间";
|
||||
}
|
||||
|
||||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("game.sugar.setting");
|
||||
if (sysConfig == null) {
|
||||
sysConfig = new SysConfig() {{
|
||||
setConfigName("甜蜜糖果1000游戏配置");
|
||||
setConfigKey("game.sugar.setting");
|
||||
setConfigValue(SnakeCaseJsonUtils.toSnakeCaseJson(params));
|
||||
}};
|
||||
sysConfigMapper.insertConfig(sysConfig);
|
||||
} else {
|
||||
sysConfig.setConfigValue(SnakeCaseJsonUtils.toSnakeCaseJson(params));
|
||||
sysConfigMapper.updateConfig(sysConfig);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public Pair<Integer, List<Map<String, Object>>> queryDailyWinList(Map<String, Object> params) {
|
||||
int pageNum = NumberUtils.toInt((String) params.get("pageNum"), 1);
|
||||
int pageSize = NumberUtils.toInt((String) params.get("pageSize"), 10);
|
||||
|
||||
Page<GameSugarWin> page = new Page<>(pageNum, pageSize);
|
||||
page.setOptimizeCountSql(false);
|
||||
LambdaQueryWrapper<GameSugarWin> wrapper = Wrappers.lambdaQuery();
|
||||
if (params.get("dateRange[0]") != null) {
|
||||
wrapper.ge(GameSugarWin::getDate, LocalDateTime.parse(params.get("dateRange[0]").toString()));
|
||||
}
|
||||
if (params.get("dateRange[1]") != null) {
|
||||
wrapper.le(GameSugarWin::getDate, LocalDateTime.parse(params.get("dateRange[1]").toString()));
|
||||
}
|
||||
wrapper.orderByDesc(GameSugarWin::getUpdateTime);
|
||||
|
||||
IPage<GameSugarWin> userListPage = adminGameSugarWinMapper.selectPage(page, wrapper);
|
||||
List<GameSugarWin> userList = userListPage.getRecords();
|
||||
|
||||
List<Map<String, Object>> r = userList.stream().map(v -> {
|
||||
Map<String, Object> user = SnakeCaseJsonUtils.toSnakeCaseMap(v);
|
||||
user.put("date", v.getDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
user.put("rtp", (v.getTotalBet().compareTo(BigDecimal.ZERO) > 0) ? v.getTotalWin().divide(v.getTotalBet(), 2, RoundingMode.HALF_UP) : 0);
|
||||
user.put("profit", v.getTotalBet().subtract(v.getTotalWin()).subtract(v.getInitBet()));
|
||||
return user;
|
||||
}).collect(Collectors.toList());
|
||||
Integer total = (int) userListPage.getTotal();
|
||||
return new Pair<>(total, r);
|
||||
}
|
||||
|
||||
public Map<String, List<Integer>> getMockResultList(Integer uid) {
|
||||
var cache = cacheRepository.getMockResultCache(uid);
|
||||
if (cache == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
var result = new HashMap<String, List<Integer>>();
|
||||
if (CollectionUtils.isNotEmpty(cache.getNormalScores()))
|
||||
result.put("", cache.getNormalScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
if (CollectionUtils.isNotEmpty(cache.getFreeScores()))
|
||||
result.put("_free", cache.getFreeScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
if (CollectionUtils.isNotEmpty(cache.getSuperScores()))
|
||||
result.put("_super", cache.getSuperScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, List<Integer>> updateMockResultList(UpdateMockResultListRequest request) {
|
||||
List<String> types = Arrays.asList("", "_free", "_super");
|
||||
List<Integer> maxScores = types.stream().map(type -> {
|
||||
return adminGameSugarSpinMapper.select(type);
|
||||
}).toList();
|
||||
|
||||
var cache = cacheRepository.getMockResultCache(request.getUid());
|
||||
if (cache == null) {
|
||||
cache = new SugarGameMockCache();
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(request.getNormalScores())) {
|
||||
List<Integer> newScores = new ArrayList<>();
|
||||
Integer maxScore = maxScores.get(0);
|
||||
for (Integer score : request.getNormalScores()) {
|
||||
if (score > maxScore) {
|
||||
score = maxScore;
|
||||
}
|
||||
newScores.add(score);
|
||||
}
|
||||
var r = newScores.stream().map(v -> {
|
||||
SugarGameMockCache.MockItem item = new SugarGameMockCache.MockItem();
|
||||
item.setScore(v);
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
cache.setNormalScores(r);
|
||||
} else {
|
||||
cache.setNormalScores(new ArrayList<>());
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(request.getFreeScores())) {
|
||||
List<Integer> newScores = new ArrayList<>();
|
||||
Integer maxScore = maxScores.get(1);
|
||||
for (Integer score : request.getFreeScores()) {
|
||||
if (score > maxScore) {
|
||||
score = maxScore;
|
||||
}
|
||||
newScores.add(score);
|
||||
}
|
||||
var r = newScores.stream().map(v -> {
|
||||
SugarGameMockCache.MockItem item = new SugarGameMockCache.MockItem();
|
||||
item.setScore(v);
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
cache.setFreeScores(r);
|
||||
} else {
|
||||
cache.setFreeScores(new ArrayList<>());
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(request.getSuperScores())) {
|
||||
List<Integer> newScores = new ArrayList<>();
|
||||
Integer maxScore = maxScores.get(2);
|
||||
for (Integer score : request.getSuperScores()) {
|
||||
if (score > maxScore) {
|
||||
score = maxScore;
|
||||
}
|
||||
newScores.add(score);
|
||||
}
|
||||
var r = newScores.stream().map(v -> {
|
||||
SugarGameMockCache.MockItem item = new SugarGameMockCache.MockItem();
|
||||
item.setScore(v);
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
cache.setSuperScores(r);
|
||||
} else {
|
||||
cache.setSuperScores(new ArrayList<>());
|
||||
}
|
||||
|
||||
cacheRepository.setMockResultCache(request.getUid(), cache);
|
||||
|
||||
|
||||
var result = new HashMap<String, List<Integer>>();
|
||||
if (CollectionUtils.isNotEmpty(cache.getNormalScores()))
|
||||
result.put("", cache.getNormalScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
if (CollectionUtils.isNotEmpty(cache.getFreeScores()))
|
||||
result.put("_free", cache.getFreeScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
if (CollectionUtils.isNotEmpty(cache.getSuperScores()))
|
||||
result.put("_super", cache.getSuperScores().stream().map(SugarGameMockCache.MockItem::getScore).collect(Collectors.toList()));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,670 @@
|
||||
package com.ruoyi.game.sugar.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
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.DateUtils;
|
||||
import com.ruoyi.common.utils.MoneyUtil;
|
||||
import com.ruoyi.common.utils.json.SnakeCaseJsonUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.entity.GameSugarSpin;
|
||||
import com.ruoyi.domain.entity.GameSugarStepInfo;
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import com.ruoyi.domain.entity.GameSugarWin;
|
||||
import com.ruoyi.framework.manager.AsyncManager;
|
||||
import com.ruoyi.game.sugar.contract.api.response.ApiSugarBuySpinResponse;
|
||||
import com.ruoyi.game.sugar.contract.api.response.ApiSugarHasFreeSpinResponse;
|
||||
import com.ruoyi.game.sugar.contract.api.response.ApiSugarSpinResultResponse;
|
||||
import com.ruoyi.game.sugar.domain.GameSugarRecord;
|
||||
import com.ruoyi.game.sugar.enums.GameSugarRecordStatus;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarRecordMapper;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarSpinMapper;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarUserMapper;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarWinMapper;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameMockCache;
|
||||
import com.ruoyi.game.sugar.repository.GameConfigCacheRepository;
|
||||
import com.ruoyi.game.sugar.repository.SugarCacheRepository;
|
||||
import com.ruoyi.game.sugar.repository.SugarRewardPoolCacheRepository;
|
||||
import com.ruoyi.game.sugar.repository.UserRecordCacheRepository;
|
||||
import com.ruoyi.game.sugar.service.sugar.*;
|
||||
import com.ruoyi.system.domain.SysConfig;
|
||||
import com.ruoyi.system.mapper.SysConfigMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.admin.config.RedisConstants.COMMON_LOCK;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiSugarService {
|
||||
|
||||
private static final String EVENT_TAG = "糖果冲刺1000";
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@Autowired
|
||||
private SugarCacheRepository sugarCacheRepository;
|
||||
|
||||
@Autowired
|
||||
private UserRecordCacheRepository userRecordCacheRepository;
|
||||
|
||||
@Autowired
|
||||
private GameConfigCacheRepository gameConfigCacheRepository;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private ApiGameSugarSpinMapper gameSugarSpinMapper;
|
||||
|
||||
@Autowired
|
||||
private SelectNormalGameLogic selectNormalGameLogic;
|
||||
|
||||
@Autowired
|
||||
private SelectFreeGameLogic selectFreeGameLogic;
|
||||
|
||||
@Autowired
|
||||
private SelectSuperFreeGameLogic selectSuperFreeGameLogic;
|
||||
|
||||
@Autowired
|
||||
private PostGameLogicManager postGameLogicManager;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ApiGameSugarUserMapper apiGameSugarUserMapper;
|
||||
|
||||
@Autowired
|
||||
private ApiGameSugarWinMapper apiGameSugarWinMapper;
|
||||
|
||||
@Autowired
|
||||
private SysConfigMapper sysConfigMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private ApiGameSugarRecordMapper apiGameSugarRecordMapper;
|
||||
|
||||
@Autowired
|
||||
private SugarRewardPoolCacheRepository rewardPoolCacheRepository;
|
||||
|
||||
|
||||
// 符号映射
|
||||
private static final Map<String, Integer> SYMBOL_MAPPING = new HashMap<String, Integer>() {{
|
||||
put("S", 1);
|
||||
put("A", 3);
|
||||
put("B", 4);
|
||||
put("C", 5);
|
||||
put("D", 6);
|
||||
put("E", 7);
|
||||
put("F", 8);
|
||||
put("G", 9);
|
||||
}};
|
||||
|
||||
// 符号映射
|
||||
private static final Map<Integer, Integer> FREE_SPIN_MAPPING = new HashMap<Integer, Integer>() {{
|
||||
put(3, 10);
|
||||
put(4, 12);
|
||||
put(5, 15);
|
||||
put(6, 20);
|
||||
put(7, 30);
|
||||
}};
|
||||
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public R<ApiSugarSpinResultResponse> doSpin(Long userId, BigDecimal bet) {
|
||||
if (bet == null || bet.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return R.fail("下注金额不正确");
|
||||
}
|
||||
|
||||
Boolean lock = redisLock.tryLock(COMMON_LOCK + userId, 3L, 7L, TimeUnit.SECONDS);
|
||||
if (!Boolean.TRUE.equals(lock)) {
|
||||
return R.fail("请求频繁,加锁失败");
|
||||
}
|
||||
try {
|
||||
|
||||
// 获取用户缓存
|
||||
SugarGameCache cache = sugarCacheRepository.getCache(userId);
|
||||
|
||||
UpdateUserAccountBo updateUserAccountBo;
|
||||
if (cache == null) {
|
||||
// 扣减余额
|
||||
R<UpdateUserAccountBo> updateResult = userService.updateUserAccount(userId.intValue(), bet.negate(), TtAccountRecordSource.SUGAR_SPIN_NORMAL);
|
||||
if (R.isError(updateResult)) {
|
||||
return R.fail(updateResult.getMsg());
|
||||
}
|
||||
updateUserAccountBo = updateResult.getData();
|
||||
|
||||
// 更新统计数据
|
||||
GameSugarUser sugarUser = apiGameSugarUserMapper.select(userId.intValue());
|
||||
if (sugarUser == null) {
|
||||
sugarUser = initUser(userId);
|
||||
}
|
||||
sugarUser.setTotalBet(sugarUser.getTotalBet().add(bet));
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
if (apiGameSugarWinMapper.select(DateUtils.getTodayAtZero()) == null) {
|
||||
initWin();
|
||||
}
|
||||
apiGameSugarWinMapper.updateAmount(DateUtils.getTodayAtZero(), bet, BigDecimal.ZERO);
|
||||
rewardPoolCacheRepository.incrementalBet(bet);
|
||||
|
||||
// 初始化缓存
|
||||
cache = new SugarGameCache();
|
||||
cache.setKind("normal");
|
||||
cache.setAccountAmount(updateUserAccountBo.getAccountAmount());
|
||||
cache.setAccountCredits(updateUserAccountBo.getAccountCredits());
|
||||
cache.setBet(bet);
|
||||
cache.setTotalFreeSpins(0);
|
||||
cache.setFreeSpinIndex(0);
|
||||
cache.setTotalWin(new BigDecimal("0"));
|
||||
|
||||
// 选择游戏
|
||||
SugarGameCache.GameSpin gameData = selectGame("", userId, bet, bet);
|
||||
cache.setGameData(gameData);
|
||||
cache.setRecordId(initRecord(userId, "", bet));
|
||||
|
||||
log.info("开始新spin: uid={}, gid={}, score={}", userId, gameData.getGid(), gameData.getScore());
|
||||
}
|
||||
bet = cache.getBet();
|
||||
|
||||
// 处理免费旋转
|
||||
Integer freeSpinIndex = cache.getFreeSpinIndex() + 1;
|
||||
cache.setFreeSpinIndex(freeSpinIndex);
|
||||
|
||||
// 获取当前免费旋转的步骤
|
||||
List<GameSugarStepInfo> currentSteps = cache.getGameData().getSteps().stream()
|
||||
.filter(step -> step.getFreeSpinId().equals(freeSpinIndex))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int extraFreeCount = 0;
|
||||
if (CollectionUtils.isNotEmpty(currentSteps)) {
|
||||
int count = (int) currentSteps.get(currentSteps.size() - 1).getGrid().chars()
|
||||
.filter(c -> c == 'S').count();
|
||||
Integer freeSpins = FREE_SPIN_MAPPING.get(count);
|
||||
if (freeSpins != null) {
|
||||
extraFreeCount = freeSpins;
|
||||
cache.setTotalFreeSpins(cache.getTotalFreeSpins() + freeSpins);
|
||||
}
|
||||
}
|
||||
|
||||
// 转换并计算总赢
|
||||
Pair<BigDecimal, List<Map<String, Object>>> pair = convert(currentSteps, cache.getTotalWin(), bet);
|
||||
BigDecimal totalWin = pair.getFirst();
|
||||
List<Map<String, Object>> steps = pair.getSecond();
|
||||
cache.setTotalWin(totalWin);
|
||||
|
||||
// 保存缓存更新
|
||||
sugarCacheRepository.setCache(userId, cache);
|
||||
|
||||
// 检查是否结束, 结束则进行结算
|
||||
if (CollectionUtils.isEmpty(steps)) {
|
||||
BigDecimal score = cache.getGameData().getScore();
|
||||
BigDecimal win = score.multiply(bet);
|
||||
|
||||
// 增加余额
|
||||
if (win.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
win = new BigDecimal("0.01");
|
||||
}
|
||||
R<UpdateUserAccountBo> updateResult = userService.updateUserAccount(userId.intValue(), win, TtAccountRecordSource.SUGAR_SPIN_REWARD);
|
||||
if (R.isError(updateResult)) {
|
||||
return R.fail(updateResult.getMsg());
|
||||
}
|
||||
updateUserAccountBo = updateResult.getData();
|
||||
cache.setAccountAmount(updateUserAccountBo.getAccountAmount());
|
||||
cache.setAccountCredits(updateUserAccountBo.getAccountCredits());
|
||||
|
||||
if ("normal".equals(cache.getKind()) && score.compareTo(new BigDecimal("30")) >= 0) {
|
||||
String triggeredKind = score.compareTo(new BigDecimal("200")) >= 0 ? "super" : "standard";
|
||||
String triggeredType = "super".equals(triggeredKind) ? "_super" : "_free";
|
||||
log.info("普通旋转触发免费旋转: uid={}, score={}, triggeredKind={}", userId, score, triggeredKind);
|
||||
|
||||
sugarCacheRepository.setCache(userId, null);
|
||||
|
||||
BigDecimal finalWin = win;
|
||||
Long recordId = cache.getRecordId();
|
||||
AsyncManager.me().run(() -> {
|
||||
GameSugarUser sugarUser = apiGameSugarUserMapper.select(userId.intValue());
|
||||
sugarUser.setTotalWin(sugarUser.getTotalWin().add(finalWin));
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
new LambdaUpdateChainWrapper<>(apiGameSugarRecordMapper)
|
||||
.eq(GameSugarRecord::getId, recordId)
|
||||
.set(GameSugarRecord::getStatus, GameSugarRecordStatus.COMPLETE.getCode())
|
||||
.set(GameSugarRecord::getWin, finalWin)
|
||||
.setSql("multiplier = win / bet")
|
||||
.update();
|
||||
apiGameSugarWinMapper.updateAmount(DateUtils.getTodayAtZero(), BigDecimal.ZERO, finalWin);
|
||||
rewardPoolCacheRepository.incrementalWin(finalWin);
|
||||
});
|
||||
|
||||
// 初始化免费旋转缓存
|
||||
SugarGameCache freeCache = new SugarGameCache();
|
||||
freeCache.setKind(triggeredKind);
|
||||
freeCache.setBet(bet);
|
||||
freeCache.setAccountAmount(updateUserAccountBo.getAccountAmount());
|
||||
freeCache.setAccountCredits(updateUserAccountBo.getAccountCredits());
|
||||
freeCache.setTotalFreeSpins(0);
|
||||
freeCache.setFreeSpinIndex(0);
|
||||
freeCache.setTotalWin(BigDecimal.ZERO);
|
||||
SugarGameCache.GameSpin freeGameData = selectGame(triggeredType, userId, bet, BigDecimal.ZERO);
|
||||
freeCache.setGameData(freeGameData);
|
||||
freeCache.setRecordId(initRecord(userId, triggeredKind, BigDecimal.ZERO));
|
||||
sugarCacheRepository.setCache(userId, freeCache);
|
||||
|
||||
ApiSugarSpinResultResponse triggeredResult = new ApiSugarSpinResultResponse();
|
||||
triggeredResult.setKind(triggeredKind);
|
||||
triggeredResult.setBalance(MoneyUtil.toStr(updateUserAccountBo.getAccountAmount()));
|
||||
triggeredResult.setCredits(MoneyUtil.toStr(updateUserAccountBo.getAccountCredits()));
|
||||
triggeredResult.setIndex(freeSpinIndex - 1);
|
||||
triggeredResult.setRemainingFreeSpins(0);
|
||||
triggeredResult.setExtraFreeSpinCount(0);
|
||||
triggeredResult.setData(steps);
|
||||
return R.ok(triggeredResult);
|
||||
}
|
||||
// 清除缓存
|
||||
sugarCacheRepository.setCache(userId, null);
|
||||
|
||||
// 更新统计数据
|
||||
BigDecimal finalWin = win;
|
||||
Long recordId = cache.getRecordId();
|
||||
AsyncManager.me().run(() -> {
|
||||
GameSugarUser sugarUser = apiGameSugarUserMapper.select(userId.intValue());
|
||||
sugarUser.setTotalWin(sugarUser.getTotalWin().add(finalWin));
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
|
||||
new LambdaUpdateChainWrapper<>(apiGameSugarRecordMapper)
|
||||
.eq(GameSugarRecord::getId, recordId)
|
||||
.set(GameSugarRecord::getStatus, GameSugarRecordStatus.COMPLETE.getCode())
|
||||
.set(GameSugarRecord::getWin, finalWin)
|
||||
.setSql("multiplier = win / bet")
|
||||
.update();
|
||||
|
||||
apiGameSugarWinMapper.updateAmount(DateUtils.getTodayAtZero(), BigDecimal.ZERO, finalWin);
|
||||
|
||||
rewardPoolCacheRepository.incrementalWin(finalWin);
|
||||
});
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
ApiSugarSpinResultResponse result = new ApiSugarSpinResultResponse();
|
||||
result.setKind(cache.getKind());
|
||||
result.setBalance(MoneyUtil.toStr(cache.getAccountAmount()));
|
||||
result.setCredits(MoneyUtil.toStr(cache.getAccountCredits()));
|
||||
result.setIndex(freeSpinIndex - 1);
|
||||
result.setRemainingFreeSpins(Math.max(0, cache.getTotalFreeSpins() + 1 - freeSpinIndex));
|
||||
result.setExtraFreeSpinCount(extraFreeCount);
|
||||
result.setData(steps);
|
||||
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
log.error("错误", e);
|
||||
return R.ok();
|
||||
} finally {
|
||||
redisLock.unlock(COMMON_LOCK + userId);
|
||||
}
|
||||
}
|
||||
|
||||
private GameSugarUser initUser(Long userId) {
|
||||
GameSugarUser sugarUser = new GameSugarUser();
|
||||
sugarUser.setUserId(userId.intValue());
|
||||
sugarUser.setTotalWin(BigDecimal.ZERO);
|
||||
sugarUser.setTotalBet(BigDecimal.ZERO);
|
||||
sugarUser.setCount(0);
|
||||
sugarUser.setFreeCount(0);
|
||||
sugarUser.setSuperFreeCount(0);
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
return sugarUser;
|
||||
}
|
||||
|
||||
public GameSugarConfig getGameConfig() {
|
||||
SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("game.sugar.setting");
|
||||
if (sysConfig == null) {
|
||||
GameSugarConfig config = new GameSugarConfig();
|
||||
config.setRtp(0.8);
|
||||
config.setJpPercent(new int[]{10, 100});
|
||||
config.setDefaultAwardPool(0);
|
||||
config.setMinProfit(0.3);
|
||||
return config;
|
||||
}
|
||||
return SnakeCaseJsonUtils.fromSnakeCaseJson(sysConfig.getConfigValue(), GameSugarConfig.class);
|
||||
}
|
||||
|
||||
private GameSugarWin initWin() {
|
||||
GameSugarConfig config = getGameConfig();
|
||||
|
||||
GameSugarWin sugarWin = new GameSugarWin();
|
||||
sugarWin.setDate(DateUtils.getTodayAtZero());
|
||||
sugarWin.setTotalWin(BigDecimal.ZERO);
|
||||
sugarWin.setInitBet(BigDecimal.valueOf(config.getDefaultAwardPool()));
|
||||
sugarWin.setTotalBet(BigDecimal.valueOf(config.getDefaultAwardPool()));
|
||||
sugarWin.setCount(0);
|
||||
sugarWin.setFreeCount(0);
|
||||
sugarWin.setSuperFreeCount(0);
|
||||
apiGameSugarWinMapper.insert(sugarWin);
|
||||
return sugarWin;
|
||||
}
|
||||
|
||||
private SugarGameCache.GameSpin selectGame(String type, Long userId, BigDecimal bet, BigDecimal cost) {
|
||||
StrategyContext context = new StrategyContext();
|
||||
|
||||
GameSugarUser sugarUser = apiGameSugarUserMapper.select(userId.intValue());
|
||||
if (sugarUser == null) {
|
||||
sugarUser = initUser(userId);
|
||||
}
|
||||
|
||||
SugarGameConfigCache cache = gameConfigCacheRepository.getCache();
|
||||
if (cache == null) {
|
||||
cache = SugarGameConfigCache.init();
|
||||
}
|
||||
context.setGameConfigCache(cache);
|
||||
|
||||
GameSugarConfig gameConfig = getGameConfig();
|
||||
|
||||
var rewardPool = rewardPoolCacheRepository.getCache();
|
||||
|
||||
context.setType(type);
|
||||
context.setBet(bet);
|
||||
context.setCost(cost);
|
||||
context.setSugarUser(sugarUser);
|
||||
context.setSysConfig(gameConfig);
|
||||
context.setRewardPool(rewardPool);
|
||||
|
||||
String whereSql = "";
|
||||
if (Objects.equals(type, "")) {
|
||||
sugarUser.setCount(sugarUser.getCount() + 1);
|
||||
apiGameSugarWinMapper.updateCount(DateUtils.getTodayAtZero(), 1, 0, 0);
|
||||
|
||||
whereSql = selectNormalGameLogic.where(context);
|
||||
} else if (Objects.equals(type, "_free")) {
|
||||
sugarUser.setCount(sugarUser.getCount() + 1);
|
||||
sugarUser.setFreeCount(sugarUser.getFreeCount() + 1);
|
||||
apiGameSugarWinMapper.updateCount(DateUtils.getTodayAtZero(), 1, 1, 0);
|
||||
|
||||
whereSql = selectFreeGameLogic.where(context);
|
||||
} else if (Objects.equals(type, "_super")) {
|
||||
sugarUser.setCount(sugarUser.getCount() + 1);
|
||||
sugarUser.setSuperFreeCount(sugarUser.getSuperFreeCount() + 1);
|
||||
apiGameSugarWinMapper.updateCount(DateUtils.getTodayAtZero(), 1, 0, 1);
|
||||
|
||||
whereSql = selectSuperFreeGameLogic.where(context);
|
||||
}
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
|
||||
|
||||
if (StringUtils.isBlank(whereSql)) {
|
||||
whereSql = " 1=1 ";
|
||||
}
|
||||
|
||||
log.info(EVENT_TAG + " uid={} whereSql={}", userId, whereSql);
|
||||
|
||||
GameSugarSpin spin = null;
|
||||
var mockResultCache = sugarCacheRepository.getMockResultCache(Math.toIntExact(userId));
|
||||
if (mockResultCache != null) {
|
||||
List<SugarGameMockCache.MockItem> r = null;
|
||||
if (type.equals("_free")) {
|
||||
r = mockResultCache.getFreeScores();
|
||||
}
|
||||
if (type.isEmpty()) {
|
||||
r = mockResultCache.getNormalScores();
|
||||
}
|
||||
if (type.equals("_super")) {
|
||||
r = mockResultCache.getSuperScores();
|
||||
}
|
||||
if (r != null && !r.isEmpty()) {
|
||||
whereSql = " score >= " + r.removeFirst().getScore();
|
||||
spin = gameSugarSpinMapper.selectMinOne(type, whereSql);
|
||||
log.info(EVENT_TAG + " uid={} use mock result.sql={}", userId, whereSql);
|
||||
sugarCacheRepository.setMockResultCache(Math.toIntExact(userId), mockResultCache);
|
||||
}
|
||||
}
|
||||
if (spin == null) {
|
||||
spin = gameSugarSpinMapper.selectRandomOne(type, whereSql);
|
||||
if (spin == null) {
|
||||
// 随机选择一个游戏
|
||||
spin = gameSugarSpinMapper.selectRandomOne(type, "score <= 10");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取游戏步骤
|
||||
List<GameSugarStepInfo> steps = gameSugarSpinMapper.selectStepsByGid(spin.getGid(), type);
|
||||
steps = steps.stream().peek(v -> {
|
||||
v.setMultipler2(SnakeCaseJsonUtils.parseSnakeCaseList(v.getMultipler(), Integer.class));
|
||||
v.setSymbolLinks2(SnakeCaseJsonUtils.parseSnakeCaseList(v.getSymbolLinks(),
|
||||
GameSugarStepInfo.SymbolLink.class));
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
SugarGameCache.GameSpin result = new SugarGameCache.GameSpin();
|
||||
result.setGid(spin.getGid());
|
||||
result.setScore(spin.getScore());
|
||||
result.setSteps(steps);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public R<ApiSugarBuySpinResponse> buyFreeSpins(Long userId, String kind, BigDecimal bet) {
|
||||
if (bet == null || bet.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return R.fail("下注金额不正确");
|
||||
}
|
||||
if (kind == null || !Arrays.asList("standard", "super").contains(kind)) {
|
||||
return R.fail("类型不正确");
|
||||
}
|
||||
Boolean lock = redisLock.tryLock(COMMON_LOCK + userId, 3L, 7L, TimeUnit.SECONDS);
|
||||
if (!Boolean.TRUE.equals(lock)) {
|
||||
return R.fail("请求频繁,加锁失败");
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查用户是否正在进行免费旋转
|
||||
SugarGameCache cache = sugarCacheRepository.getCache(userId);
|
||||
|
||||
if (cache != null) {
|
||||
return R.fail("免费旋转进行中,请先完成当前免费旋转");
|
||||
}
|
||||
|
||||
// 根据类型处理
|
||||
BigDecimal requiredAmount = null;
|
||||
String type = "";
|
||||
TtAccountRecordSource t = null;
|
||||
if ("standard".equals(kind)) {
|
||||
requiredAmount = bet.multiply(new BigDecimal(50));
|
||||
type = "_free";
|
||||
t = TtAccountRecordSource.SUGAR_BUY_FREE_SPIN;
|
||||
} else {
|
||||
requiredAmount = bet.multiply(new BigDecimal(200));
|
||||
type = "_super";
|
||||
t = TtAccountRecordSource.SUGAR_BUY_SUPER_SPIN;
|
||||
}
|
||||
|
||||
// 检查余额和扣减余额
|
||||
R<UpdateUserAccountBo> updateResult = userService.updateUserAccount(userId.intValue(), requiredAmount.negate(), t);
|
||||
if (R.isError(updateResult)) {
|
||||
return R.fail(updateResult.getMsg());
|
||||
}
|
||||
|
||||
GameSugarUser sugarUser = apiGameSugarUserMapper.select(userId.intValue());
|
||||
if (sugarUser == null) {
|
||||
sugarUser = initUser(userId);
|
||||
}
|
||||
sugarUser.setTotalBet(sugarUser.getTotalBet().add(requiredAmount));
|
||||
apiGameSugarUserMapper.insertOrUpdate(sugarUser);
|
||||
if (apiGameSugarWinMapper.select(DateUtils.getTodayAtZero()) == null) {
|
||||
initWin();
|
||||
}
|
||||
apiGameSugarWinMapper.updateAmount(DateUtils.getTodayAtZero(), requiredAmount, BigDecimal.ZERO);
|
||||
rewardPoolCacheRepository.incrementalBet(requiredAmount);
|
||||
|
||||
|
||||
// 初始化缓存
|
||||
cache = new SugarGameCache();
|
||||
cache.setAccountAmount(updateResult.getData().getAccountAmount());
|
||||
cache.setAccountCredits(updateResult.getData().getAccountCredits());
|
||||
cache.setKind(kind);
|
||||
cache.setBet(bet);
|
||||
cache.setTotalFreeSpins(0);
|
||||
cache.setFreeSpinIndex(0);
|
||||
cache.setTotalWin(BigDecimal.ZERO);
|
||||
|
||||
// 选择标准免费游戏
|
||||
SugarGameCache.GameSpin gameData = selectGame(type, userId, bet, requiredAmount);
|
||||
cache.setGameData(gameData);
|
||||
var recordId = initRecord(userId, kind, requiredAmount);
|
||||
cache.setRecordId(recordId);
|
||||
|
||||
// 保存到缓存
|
||||
sugarCacheRepository.setCache(userId, cache);
|
||||
log.info("用户{}购买了{}免费旋转,下注:{},扣除金额:{},gid={},score={}", userId, kind, bet,
|
||||
requiredAmount, gameData.getGid(), gameData.getScore());
|
||||
|
||||
ApiSugarBuySpinResponse response = new ApiSugarBuySpinResponse();
|
||||
response.setCost(MoneyUtil.toStr(requiredAmount));
|
||||
response.setBalance(MoneyUtil.toStr(updateResult.getData().getAccountAmount()));
|
||||
response.setCredits(MoneyUtil.toStr(updateResult.getData().getAccountCredits()));
|
||||
response.setIsSuperFree("super".equals(kind));
|
||||
|
||||
return R.ok(response);
|
||||
} catch (Exception e) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return R.fail("内部错误");
|
||||
} finally {
|
||||
redisLock.unlock(COMMON_LOCK + userId);
|
||||
}
|
||||
}
|
||||
|
||||
private Long initRecord(Long userId, String kind, BigDecimal bet) {
|
||||
GameSugarRecord record = new GameSugarRecord();
|
||||
record.setUserId(userId);
|
||||
record.setKind(kind);
|
||||
record.setBet(bet);
|
||||
record.setWin(BigDecimal.ZERO);
|
||||
record.setExtraFree(0);
|
||||
record.setStatus(GameSugarRecordStatus.RUNING.getCode());
|
||||
|
||||
apiGameSugarRecordMapper.insert(record);
|
||||
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换步骤数据
|
||||
*
|
||||
* @param steps 步骤列表
|
||||
* @param totalWin 当前总赢取金额
|
||||
* @param bet 下注金额
|
||||
* @return 转换结果
|
||||
*/
|
||||
public Pair<BigDecimal, List<Map<String, Object>>> convert(List<GameSugarStepInfo> steps, BigDecimal totalWin, BigDecimal bet) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
if (CollectionUtils.isEmpty(steps)) {
|
||||
return new Pair<>(totalWin, result);
|
||||
}
|
||||
|
||||
for (GameSugarStepInfo step : steps) {
|
||||
// 转换grid为符号映射值
|
||||
List<Integer> s = step.getGrid().chars().mapToObj(c -> (char) c)
|
||||
.map(symbol -> SYMBOL_MAPPING.getOrDefault(symbol + "", 0))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 转换multipler为slm格式
|
||||
List<List<Integer>> slm = new ArrayList<>();
|
||||
List<Integer> multipler = step.getMultipler2();
|
||||
for (int i = 0; i < multipler.size(); i += 2) {
|
||||
if (i + 1 < multipler.size()) {
|
||||
slm.add(Arrays.asList(multipler.get(i), multipler.get(i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理symbol_links
|
||||
List<List<List<Integer>>> symbolMultipler = new ArrayList<>();
|
||||
List<List<Object>> symbolWin = new ArrayList<>();
|
||||
List<List<Object>> symbolMark = new ArrayList<>();
|
||||
|
||||
for (GameSugarStepInfo.SymbolLink link : step.getSymbolLinks2()) {
|
||||
// 处理s_mark
|
||||
List<Object> markItem = new ArrayList<>();
|
||||
markItem.add(SYMBOL_MAPPING.getOrDefault(link.getSymbol(), 0));
|
||||
|
||||
// 添加坐标列表
|
||||
List<Integer> coordinates = new ArrayList<>(link.getLoc());
|
||||
markItem.add(coordinates);
|
||||
|
||||
// 添加奖励金额
|
||||
markItem.add(bet.multiply(BigDecimal.valueOf(link.getScore()))
|
||||
.setScale(2, RoundingMode.HALF_UP).toPlainString());
|
||||
symbolMark.add(markItem);
|
||||
|
||||
// 处理sm和swin
|
||||
if (link.getTotalMulti() > 1) {
|
||||
// 处理symbol_multipler (sm)
|
||||
List<List<Integer>> multiItem = new ArrayList<>();
|
||||
List<Integer> linkMultipler = link.getMultipler();
|
||||
for (int i = 0; i < linkMultipler.size(); i += 2) {
|
||||
if (i + 1 < linkMultipler.size()) {
|
||||
multiItem.add(Arrays.asList(
|
||||
linkMultipler.get(i),
|
||||
linkMultipler.get(i + 1)
|
||||
));
|
||||
}
|
||||
}
|
||||
symbolMultipler.add(multiItem);
|
||||
|
||||
// 处理symbol_win (swin)
|
||||
symbolWin.add(Arrays.asList(
|
||||
bet.multiply(BigDecimal.valueOf(link.getBaseScore())),
|
||||
link.getTotalMulti()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总赢取金额
|
||||
totalWin = totalWin.add(bet.multiply(step.getScore()));
|
||||
|
||||
// 构建步骤结果
|
||||
Map<String, Object> stepResult = new LinkedHashMap<>();
|
||||
stepResult.put("tw", totalWin.setScale(2, RoundingMode.HALF_UP).toPlainString());
|
||||
stepResult.put("w", step.getScore().multiply(bet)
|
||||
.setScale(2, RoundingMode.HALF_UP).toPlainString());
|
||||
stepResult.put("slm", slm);
|
||||
stepResult.put("sm", symbolMultipler);
|
||||
stepResult.put("swin", symbolWin);
|
||||
stepResult.put("s_mark", symbolMark);
|
||||
stepResult.put("s", s);
|
||||
|
||||
result.add(stepResult);
|
||||
}
|
||||
|
||||
return new Pair<>(totalWin, result);
|
||||
}
|
||||
|
||||
public R<ApiSugarHasFreeSpinResponse> hasFreeSpin(Long userId) {
|
||||
// 检查用户是否正在进行免费旋转
|
||||
SugarGameCache cache = sugarCacheRepository.getCache(userId);
|
||||
|
||||
if (cache != null) {
|
||||
ApiSugarHasFreeSpinResponse resp = new ApiSugarHasFreeSpinResponse();
|
||||
resp.setHasFreeSpin(true);
|
||||
return R.ok(resp);
|
||||
}
|
||||
ApiSugarHasFreeSpinResponse resp = new ApiSugarHasFreeSpinResponse();
|
||||
resp.setHasFreeSpin(false);
|
||||
return R.ok(resp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarSpin;
|
||||
import com.ruoyi.game.sugar.mapper.ApiGameSugarSpinMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Component
|
||||
public class PostGameLogicManager {
|
||||
@Autowired
|
||||
private ApiGameSugarSpinMapper gameSugarSpinMapper;
|
||||
|
||||
public GameSugarSpin postGameLogic(StrategyContext context, GameSugarSpin spin) {
|
||||
if (spin.getScore().doubleValue() >= 20) {
|
||||
return spin;
|
||||
}
|
||||
if (context.getCost().doubleValue() < 100) {
|
||||
return spin;
|
||||
} else if (context.getCost().doubleValue() <= 150) {
|
||||
String whereSql = " score between 20 and 70 ";
|
||||
spin = gameSugarSpinMapper.selectRandomOne(context.getType(), whereSql);
|
||||
} else {
|
||||
double maxScore = context.getCost().divide(context.getBet(), RoundingMode.HALF_UP).doubleValue() / 2;
|
||||
double minScore = maxScore / 1.5;
|
||||
String whereSql = String.format(" score between %.2f and %.2f ", minScore, maxScore);
|
||||
spin = gameSugarSpinMapper.selectRandomOne(context.getType(), whereSql);
|
||||
}
|
||||
return spin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import com.ruoyi.game.sugar.service.sugar.strategy.MaxScoreStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SelectFreeGameLogic {
|
||||
@Autowired
|
||||
private SelectGameCommonLogic selectGameCommonLogic;
|
||||
|
||||
private static final String EVENT_TAG = "糖果冲刺1000免费旋转选择策略";
|
||||
|
||||
|
||||
public String where(StrategyContext context) {
|
||||
// todo
|
||||
var userTag = selectGameCommonLogic.getUserTag(context);
|
||||
var rewardPool = context.getRewardPool();
|
||||
BigDecimal totalBet = rewardPool.getTotalBet();
|
||||
BigDecimal totalWin = rewardPool.getTotalWin();
|
||||
double globalRtp = 0.9;
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) > 0) {
|
||||
globalRtp = totalWin.doubleValue() / totalBet.doubleValue();
|
||||
}
|
||||
context.getGameConfigCache().setGlobalRtp(globalRtp);
|
||||
|
||||
var table = selectGameCommonLogic.calcCurrentProbabilityTable(context);
|
||||
table = table.stream().sorted(Comparator.comparing(SugarGameConfigCache.AwardItem::getLowRewardRound)).toList();
|
||||
var indexes = new ArrayList<Integer>();
|
||||
for (int i = 0; i < table.size(); i++) {
|
||||
indexes.add(i);
|
||||
}
|
||||
var weights = table.stream().map(SugarGameConfigCache.AwardItem::getWeight).toList();
|
||||
log.info("使用的奖励表: {}", table);
|
||||
log.info("weights: {}", weights);
|
||||
var rewardIndex = RandomUtil.choices(indexes, weights, 1).getFirst();
|
||||
log.info("rewardIndex: {}", rewardIndex);
|
||||
var rewardItem = table.get(rewardIndex);
|
||||
var minScore = rewardItem.getLowRewardRound();
|
||||
var maxScore = rewardItem.getHighRewardRound();
|
||||
if (minScore == maxScore && rewardIndex < table.size() - 1) {
|
||||
maxScore = table.get(rewardIndex + 1).getLowRewardRound();
|
||||
}
|
||||
|
||||
context.getControlParam().setMinScore(minScore);
|
||||
context.getControlParam().setMaxScore(maxScore);
|
||||
new MaxScoreStrategy().calcScore(context);
|
||||
|
||||
return SelectGameCommonLogic.applyParam(log, EVENT_TAG, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SelectGameCommonLogic {
|
||||
|
||||
|
||||
public UserTag getUserTag(StrategyContext context) {
|
||||
UserTag result = UserTag.NEWBIE;
|
||||
|
||||
// 获取用户信息
|
||||
BigDecimal totalBet = context.getSugarUser().getTotalBet();
|
||||
int betCount = context.getSugarUser().getCount();
|
||||
|
||||
// 判定新人奖池条件:投注额小于5000或投注次数小于100次
|
||||
if (totalBet.compareTo(new BigDecimal("5000")) < 0 || betCount < 100) {
|
||||
return UserTag.NEWBIE;
|
||||
}
|
||||
|
||||
// 判定老人奖池条件:投注额大于等于1w或投注次数大于300次
|
||||
if (totalBet.compareTo(new BigDecimal("10000")) >= 0 || betCount > 300) {
|
||||
return UserTag.OLDIE;
|
||||
}
|
||||
|
||||
// 判定中等奖池条件:投注额大于4999小于1w或投注次数小于300次
|
||||
if ((totalBet.compareTo(new BigDecimal("4999")) > 0 && totalBet.compareTo(new BigDecimal("10000")) < 0) || betCount < 300) {
|
||||
return UserTag.MIDDLE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<SugarGameConfigCache.AwardItem> calcCurrentProbabilityTable(StrategyContext context) {
|
||||
SugarGameConfigCache cache = context.getGameConfigCache();
|
||||
double globalRtp = cache.getGlobalRtp();
|
||||
boolean useTable2 = globalRtp < cache.getRtpSwitchThreshold();
|
||||
|
||||
log.info("globalRtp={}, threshold={}, useTable2={}", globalRtp, cache.getRtpSwitchThreshold(), useTable2);
|
||||
|
||||
List<SugarGameConfigCache.AwardItem> awardItems = getAwardItems(context, cache, useTable2);
|
||||
|
||||
return new ArrayList<>(awardItems);
|
||||
}
|
||||
|
||||
private static List<SugarGameConfigCache.AwardItem> getAwardItems(
|
||||
StrategyContext context, SugarGameConfigCache cache, boolean useTable2) {
|
||||
|
||||
return switch (context.getType()) {
|
||||
case "" -> useTable2 ? cache.getNormalAwardItems2() : cache.getNormalAwardItems();
|
||||
case "_free" -> useTable2 ? cache.getFreeAwardItems2() : cache.getFreeAwardItems();
|
||||
case "_super" -> useTable2 ? cache.getSuperAwardItems2() : cache.getSuperAwardItems();
|
||||
default -> cache.getNormalAwardItems();
|
||||
};
|
||||
}
|
||||
|
||||
public static String applyParam(Logger log, String EVENT_TAG, StrategyContext context) {
|
||||
// 校验
|
||||
StrategyContext.ControlParam param = context.getControlParam();
|
||||
if (param.getMinScore() > param.getMaxScore()) {
|
||||
log.error("{} minScore is greater than maxScore.minScore={},maxScore={}",
|
||||
EVENT_TAG, param.getMinScore(), param.getMaxScore());
|
||||
param.setMinScore(-1);
|
||||
}
|
||||
|
||||
String whereSql = "";
|
||||
if (param.getMinScore() >= 0) {
|
||||
whereSql += " and score >= " + param.getMinScore();
|
||||
}
|
||||
if (param.getMaxScore() >= 0) {
|
||||
whereSql += " and score < " + param.getMaxScore();
|
||||
}
|
||||
if (param.isJP()) {
|
||||
whereSql += " and extra_free > 0";
|
||||
} else if (param.isNotJP()) {
|
||||
whereSql += " and extra_free = 0";
|
||||
}
|
||||
if (whereSql.startsWith(" and")) {
|
||||
whereSql = whereSql.substring(4);
|
||||
}
|
||||
return whereSql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.common.utils.json.SnakeCaseJsonUtils;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import com.ruoyi.game.sugar.service.sugar.strategy.MaxScoreStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SelectNormalGameLogic {
|
||||
@Autowired
|
||||
private SelectGameCommonLogic selectGameCommonLogic;
|
||||
|
||||
private static final String EVENT_TAG = "糖果冲刺1000普通选择策略";
|
||||
|
||||
|
||||
public String where(StrategyContext context) {
|
||||
// todo
|
||||
var userTag = selectGameCommonLogic.getUserTag(context);
|
||||
var rewardPool = context.getRewardPool();
|
||||
BigDecimal totalBet = rewardPool.getTotalBet();
|
||||
BigDecimal totalWin = rewardPool.getTotalWin();
|
||||
double globalRtp = 0.9;
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) > 0) {
|
||||
globalRtp = totalWin.doubleValue() / totalBet.doubleValue();
|
||||
}
|
||||
context.getGameConfigCache().setGlobalRtp(globalRtp);
|
||||
|
||||
var table = selectGameCommonLogic.calcCurrentProbabilityTable(context);
|
||||
table = table.stream().sorted(Comparator.comparing(SugarGameConfigCache.AwardItem::getLowRewardRound)).toList();
|
||||
var indexes = new ArrayList<Integer>();
|
||||
for (int i = 0; i < table.size(); i++) {
|
||||
indexes.add(i);
|
||||
}
|
||||
var weights = table.stream().map(SugarGameConfigCache.AwardItem::getWeight).toList();
|
||||
log.info("使用的奖励表: {}", SnakeCaseJsonUtils.toSnakeCaseJson(table));
|
||||
log.info("weights: {}", SnakeCaseJsonUtils.toSnakeCaseJson(weights));
|
||||
var rewardIndex = RandomUtil.choices(indexes, weights, 1).getFirst();
|
||||
log.info("rewardIndex: {}", rewardIndex);
|
||||
var rewardItem = table.get(rewardIndex);
|
||||
var minScore = rewardItem.getLowRewardRound();
|
||||
var maxScore = rewardItem.getHighRewardRound();
|
||||
if (minScore == maxScore && rewardIndex < table.size() - 1) {
|
||||
maxScore = table.get(rewardIndex + 1).getLowRewardRound();
|
||||
}
|
||||
|
||||
context.getControlParam().setMinScore(minScore);
|
||||
context.getControlParam().setMaxScore(maxScore);
|
||||
new MaxScoreStrategy().calcScore(context);
|
||||
|
||||
if (minScore >= 30) {
|
||||
context.getControlParam().setJP(true);
|
||||
} else {
|
||||
context.getControlParam().setNotJP(true);
|
||||
}
|
||||
|
||||
return SelectGameCommonLogic.applyParam(log, EVENT_TAG, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import com.ruoyi.game.sugar.service.sugar.strategy.MaxScoreStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SelectSuperFreeGameLogic {
|
||||
@Autowired
|
||||
private SelectGameCommonLogic selectGameCommonLogic;
|
||||
|
||||
private static final String EVENT_TAG = "糖果冲刺1000超级免费旋转选择策略";
|
||||
public static List<Strategy> strategies = new ArrayList<Strategy>() {{
|
||||
add(new MaxScoreStrategy());
|
||||
}};
|
||||
|
||||
public String where(StrategyContext context) {
|
||||
var userTag = selectGameCommonLogic.getUserTag(context);
|
||||
var rewardPool = context.getRewardPool();
|
||||
BigDecimal totalBet = rewardPool.getTotalBet();
|
||||
BigDecimal totalWin = rewardPool.getTotalWin();
|
||||
double globalRtp = 0.9;
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) > 0) {
|
||||
globalRtp = totalWin.doubleValue() / totalBet.doubleValue();
|
||||
}
|
||||
context.getGameConfigCache().setGlobalRtp(globalRtp);
|
||||
|
||||
var table = selectGameCommonLogic.calcCurrentProbabilityTable(context);
|
||||
table = table.stream().sorted(Comparator.comparing(SugarGameConfigCache.AwardItem::getLowRewardRound)).toList();
|
||||
var indexes = new ArrayList<Integer>();
|
||||
for (int i = 0; i < table.size(); i++) {
|
||||
indexes.add(i);
|
||||
}
|
||||
var weights = table.stream().map(SugarGameConfigCache.AwardItem::getWeight).toList();
|
||||
log.info("使用的奖励表: {}", table);
|
||||
log.info("weights: {}", weights);
|
||||
var rewardIndex = RandomUtil.choices(indexes, weights, 1).getFirst();
|
||||
log.info("rewardIndex: {}", rewardIndex);
|
||||
var rewardItem = table.get(rewardIndex);
|
||||
var minScore = rewardItem.getLowRewardRound();
|
||||
var maxScore = rewardItem.getHighRewardRound();
|
||||
if (minScore == maxScore && rewardIndex < table.size() - 1) {
|
||||
maxScore = table.get(rewardIndex + 1).getLowRewardRound();
|
||||
}
|
||||
|
||||
context.getControlParam().setMinScore(minScore);
|
||||
context.getControlParam().setMaxScore(maxScore);
|
||||
new MaxScoreStrategy().calcScore(context);
|
||||
|
||||
return SelectGameCommonLogic.applyParam(log, EVENT_TAG, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
public interface Strategy {
|
||||
public void calcScore(StrategyContext context);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarGameConfigCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarRewardPoolCache;
|
||||
import com.ruoyi.game.sugar.model.cache.SugarUserGameRecordCache;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class StrategyContext {
|
||||
private String type;
|
||||
private BigDecimal bet;
|
||||
private BigDecimal cost;
|
||||
private GameSugarUser sugarUser;
|
||||
private SugarRewardPoolCache rewardPool;
|
||||
private GameSugarConfig sysConfig;
|
||||
private SugarUserGameRecordCache recordCache;
|
||||
private SugarGameConfigCache gameConfigCache;
|
||||
|
||||
private ControlParam controlParam;
|
||||
|
||||
public StrategyContext() {
|
||||
ControlParam param = new ControlParam();
|
||||
param.setMinScore(-1);
|
||||
param.setMaxScore(-1);
|
||||
param.setJP(false);
|
||||
param.setNotJP(false);
|
||||
param.setRandom(false);
|
||||
this.controlParam = param;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ControlParam {
|
||||
private double minScore;
|
||||
private double maxScore;
|
||||
private boolean isRandom;
|
||||
private boolean isJP;
|
||||
private boolean notJP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.game.sugar.service.sugar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum UserTag {
|
||||
NEWBIE("new"), // 新人奖池
|
||||
MIDDLE("middle"), // 中等奖池
|
||||
OLDIE("old"), // 老人奖池
|
||||
|
||||
;
|
||||
|
||||
private String tag;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.ruoyi.game.sugar.service.sugar.strategy;
|
||||
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.service.sugar.Strategy;
|
||||
import com.ruoyi.game.sugar.service.sugar.StrategyContext;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Slf4j
|
||||
public class DynamicRtpStrategy implements Strategy {
|
||||
@Data
|
||||
private static class RewardConfig {
|
||||
private String name;
|
||||
private double weight;
|
||||
private double value;
|
||||
|
||||
public RewardConfig(String name, double weight, double value) {
|
||||
this.name = name;
|
||||
this.weight = weight;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 为了简单,每种奖的价值就是它的概率
|
||||
private static final RewardConfig[] REWARD_CONFIGS = {
|
||||
new RewardConfig("空奖", 56.76, 25), // score=[0,0]
|
||||
new RewardConfig("小奖", 15.46, 25), // score=(0,0.5]
|
||||
new RewardConfig("中奖", 27.11, 25), // score=(0.5,1.5.0]
|
||||
new RewardConfig("大奖", 0.68, 25), // score=(1.5,+]
|
||||
};
|
||||
|
||||
@Override
|
||||
public void calcScore(StrategyContext context) {
|
||||
GameSugarUser user = context.getSugarUser();
|
||||
BigDecimal totalBet = user.getTotalBet();
|
||||
BigDecimal totalWin = user.getTotalWin();
|
||||
|
||||
GameSugarConfig sugarConfig = context.getSysConfig();
|
||||
|
||||
// 还未下过注
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
double rtp = totalWin.divide(totalBet, 4, RoundingMode.HALF_UP).doubleValue();
|
||||
double targetRtp = sugarConfig.getRtp();
|
||||
if (rtp > 0.5) {
|
||||
return;
|
||||
}
|
||||
// 计算当前rtp与目标rtp的差值
|
||||
// double error = rtp - targetRtp;
|
||||
// log.info("DynamicRtpStrategy rtp={},targetRtp={},error={}", rtp, targetRtp, error);
|
||||
//
|
||||
// // 误差小于30%,不做处理
|
||||
// if (Math.abs(error) < 0.3) {
|
||||
// return;
|
||||
// }
|
||||
// if (error > 1) {
|
||||
// error = 1;
|
||||
// }
|
||||
//
|
||||
// double safe_error = Math.max(Math.min(error, 1.0), -1.0);
|
||||
// // 调整的灵敏度系数
|
||||
// double learning_factor = 0.01;
|
||||
// // 如果error是正数,则说明当前rtp高于目标rtp,要减少一些高价值符号的权重
|
||||
// // 如果error是负数,则说明当前rtp小于目标rtp,要增加一些高价值符号的权重
|
||||
// double minWeight = 0.1;
|
||||
|
||||
RewardConfig[] rewardConfigs = Arrays.copyOf(REWARD_CONFIGS, REWARD_CONFIGS.length);
|
||||
// for (RewardConfig config : rewardConfigs) {
|
||||
// double adjustment_factor = -1 * safe_error * config.getWeight() * config.value * learning_factor;
|
||||
// double newWeight = config.getWeight() + adjustment_factor;
|
||||
// if (newWeight < minWeight) {
|
||||
// newWeight = minWeight;
|
||||
// }
|
||||
// config.setWeight(newWeight);
|
||||
// }
|
||||
|
||||
// RewardConfig config = RandomUtil.choices(rewardConfigs, Arrays.stream(rewardConfigs).mapToDouble(RewardConfig::getWeight).toArray(), 1).get(0);
|
||||
// switch (config.getName()) {
|
||||
// case "空奖":
|
||||
// context.setMaxScore(0);
|
||||
// break;
|
||||
// case "小奖":
|
||||
// context.setMaxScore(0.5);
|
||||
// break;
|
||||
// case "中奖":
|
||||
// context.setMinScore(0.5);
|
||||
// context.setMaxScore(2);
|
||||
// break;
|
||||
// case "大奖":
|
||||
// context.setMinScore(1.5);
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.game.sugar.service.sugar.strategy;
|
||||
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.domain.entity.GameSugarUser;
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.service.sugar.Strategy;
|
||||
import com.ruoyi.game.sugar.service.sugar.StrategyContext;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public class JpStrategy implements Strategy {
|
||||
@Override
|
||||
public void calcScore(StrategyContext context) {
|
||||
GameSugarUser user = context.getSugarUser();
|
||||
BigDecimal totalBet = user.getTotalBet();
|
||||
BigDecimal totalWin = user.getTotalWin();
|
||||
|
||||
GameSugarConfig sugarConfig = context.getSysConfig();
|
||||
|
||||
// 还未下过注
|
||||
if (totalBet.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int r = RandomUtil.randInt(1, sugarConfig.getJpPercent()[1]);
|
||||
if (r > sugarConfig.getJpPercent()[0]) {
|
||||
return;
|
||||
}
|
||||
// 命中额外JP,则限制MaxScore
|
||||
// context.setJP(true);
|
||||
// double rtp = totalWin.divide(totalBet, 4, RoundingMode.HALF_UP).doubleValue();
|
||||
// if (rtp > context.getSysConfig().getRtp()) {
|
||||
// if (context.getMaxScore() > 5 || context.getMaxScore() < 0) {
|
||||
// context.setMaxScore(5);
|
||||
// }
|
||||
// } else {
|
||||
// if (context.getMaxScore() > 10 || context.getMaxScore() < 0) {
|
||||
// context.setMaxScore(10);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.game.sugar.service.sugar.strategy;
|
||||
|
||||
import com.ruoyi.game.sugar.model.GameSugarConfig;
|
||||
import com.ruoyi.game.sugar.service.sugar.Strategy;
|
||||
import com.ruoyi.game.sugar.service.sugar.StrategyContext;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public class MaxScoreStrategy implements Strategy {
|
||||
@Override
|
||||
public void calcScore(StrategyContext context) {
|
||||
var win = context.getRewardPool();
|
||||
BigDecimal totalBet = win.getTotalBet();
|
||||
BigDecimal totalWin = win.getTotalWin();
|
||||
|
||||
GameSugarConfig sugarConfig = context.getSysConfig();
|
||||
|
||||
double minProfit = sugarConfig.getMinProfit();
|
||||
|
||||
// 假设系统要保留30%的奖励,minProfit=0.3, 那么可供抽奖的奖池就是total*0.7
|
||||
// 返回的倍数*下注数一定小于可用奖池,防止奖池抽空
|
||||
BigDecimal profitMargin = (totalBet.subtract(totalWin)).multiply(BigDecimal.valueOf(1 - minProfit));
|
||||
BigDecimal maxScore = profitMargin.divide(context.getBet(), 2, RoundingMode.HALF_UP);
|
||||
|
||||
if (maxScore.compareTo(BigDecimal.ZERO) < 0) maxScore = BigDecimal.ZERO;
|
||||
|
||||
if (context.getControlParam().getMaxScore() > maxScore.doubleValue()
|
||||
|| context.getControlParam().getMaxScore() < 0) {
|
||||
context.getControlParam().setMaxScore(maxScore.doubleValue());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
63
skins-service/pom.xml
Normal file
63
skins-service/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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>ruoyi</artifactId>
|
||||
<version>4.8.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>skins-service</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>service-user</module>
|
||||
<module>service-thirdparty</module>
|
||||
<module>service-admin</module>
|
||||
<module>service-playingmethod</module>
|
||||
<module>service-task</module>
|
||||
<module>game-sugar-service</module>
|
||||
<module>game-wheel-service</module>
|
||||
</modules>
|
||||
|
||||
<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-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>skins-model</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger3依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>io.swagger</groupId>-->
|
||||
<!-- <artifactId>swagger-models</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
27
skins-service/service-admin/pom.xml
Normal file
27
skins-service/service-admin/pom.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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>
|
||||
|
||||
<artifactId>service-admin</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-quartz</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.admin.cahe;
|
||||
|
||||
import com.ruoyi.admin.model.BoxMockCache;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class BoxMockCacheRepository {
|
||||
|
||||
private static final String BOX_MOCK_CACHE_KEY = "ruoyi:box:mock:";
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
//将用户的临时爆率存储在redis中,存储七天
|
||||
public void setMockResultCache(Integer uid, BoxMockCache value) {
|
||||
redisCache.setCacheObject(BOX_MOCK_CACHE_KEY + uid, value, 7, TimeUnit.DAYS);
|
||||
}
|
||||
//根据用户的id,从redis中取出用户的临时爆率
|
||||
public BoxMockCache getMockResultCache(Integer uid) {
|
||||
return redisCache.getCacheObject(BOX_MOCK_CACHE_KEY + uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.admin.config;
|
||||
|
||||
public class RedisConstants {
|
||||
// 整个应用的通用uid锁
|
||||
public static final String COMMON_LOCK = "ruoyi:common:lock_";
|
||||
public static final String SUGAR_GAME_CACHE = "ruoyi:sugar:";
|
||||
public static final String SUGAR_GAME_MOCK_QUEUE = "ruoyi:sugar:mock:";
|
||||
public static final String SUGAR_GAME_USER_RECORD_CACHE = "ruoyi:sugar:user:record:";
|
||||
public static final String SUGAR_GAME_CONFIG_CACHE = "ruoyi:sugar:config";
|
||||
public static final String SUGAR_GAME_REWARD_POOL_CACHE = "ruoyi:sugar:reward:pool";
|
||||
public static final String WHEEL_GAME_REWARD_POOL_CACHE = "ruoyi:wheel:reward:pool";
|
||||
|
||||
|
||||
/**
|
||||
* 开箱奖池
|
||||
* 奖池key: baseKey:boxId:poolType
|
||||
*
|
||||
* @see com.ruoyi.admin.enums.BoxPoolType
|
||||
*/
|
||||
public static final String BASE_POOL_KEY = "prize_pool:";
|
||||
|
||||
// 对战模式
|
||||
public static final String JOIN_FIGHT_LOCK = "join_fight:lock_";
|
||||
public static final String JOIN_FIGHT_BEGIN_LOCK = "join_fight:lock_begin";
|
||||
public static final String JOIN_FIGHT_SEAT_READY_LOCK = "join_fight:lock_seat_ready_";
|
||||
public static final String JOIN_FIGHT_END_LOCK = "join_fight:lock_end";
|
||||
|
||||
|
||||
// roll房间
|
||||
public static final String JOIN_ROLL_LOCK = "join_roll:lock_";
|
||||
|
||||
|
||||
public static final String RECEIVE_RED_PACKET_LOCK = "receive_red_packet:lock_";
|
||||
public static final String CARD_PAY_LOCK = "CARD_PAY:lock_";
|
||||
|
||||
|
||||
// 幸运升级
|
||||
public static final String UPGRADE_RANGE = "upgrade_range:"; // 概率区间 '业务key:饰品id:用户类型'
|
||||
public static final String UPGRADE_RANGE_FIXED = "upgrade_range_fixed:"; // 固定概率 '业务key:饰品id:用户类型'
|
||||
|
||||
|
||||
/**
|
||||
* 抽奖box的奖品空间
|
||||
* open_box_goods_apace:
|
||||
* box_id:
|
||||
* odds_key:
|
||||
* valua
|
||||
*/
|
||||
public static final String OPEN_BOX_GOODS_SPACE = "open_box_goods_apace:";
|
||||
public static final String OPEN_BOX_LOTTERY = "open_box_lottery:";
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtOrnamentService;
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.admin.service.ShoppingService;
|
||||
import com.ruoyi.domain.other.ShoppingBody;
|
||||
import com.ruoyi.domain.vo.ShoppingDataVO;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Api(tags = "管理端 商城管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/shopping")
|
||||
public class ShoppingController extends BaseController {
|
||||
|
||||
private final ShoppingService shoppingService;
|
||||
private final TtOrnamentService ornamentsService;
|
||||
|
||||
public ShoppingController(ShoppingService shoppingService,
|
||||
TtOrnamentService ornamentsService) {
|
||||
this.shoppingService = shoppingService;
|
||||
this.ornamentsService = ornamentsService;
|
||||
}
|
||||
|
||||
@ApiOperation("商品列表")
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<ShoppingDataVO> list(ShoppingBody shoppingBody) {
|
||||
startPage();
|
||||
return shoppingService.list(shoppingBody);
|
||||
}
|
||||
|
||||
@PostMapping("/batchPutAwayOrSoldOut/{status}")
|
||||
public R<Boolean> batchPutAwayOrSoldOut(@RequestBody List<TtOrnament> ornamentsList,
|
||||
@PathVariable("status") String status) {
|
||||
if (StringUtils.isNull(ornamentsList) || ornamentsList.isEmpty()) return R.fail();
|
||||
ornamentsList = ornamentsList.stream().peek(ttOrnaments -> ttOrnaments.setIsPutaway(status)).collect(Collectors.toList());
|
||||
if (ornamentsService.updateBatchById(ornamentsList, 1)) return R.ok(true);
|
||||
return R.fail(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtAdvertisementService;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.other.TtAdvertisement;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/advertisement")
|
||||
public class TtAdvertisementController extends BaseController {
|
||||
|
||||
private final TtAdvertisementService ttAdvertisementService;
|
||||
|
||||
public TtAdvertisementController(TtAdvertisementService ttAdvertisementService) {
|
||||
this.ttAdvertisementService = ttAdvertisementService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtAdvertisement> list() {
|
||||
startPage();
|
||||
List<TtAdvertisement> list = ttAdvertisementService.list();
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtAdvertisement> getInfo(@PathVariable("id") Integer id) {
|
||||
TtAdvertisement ttAdvertisement = ttAdvertisementService.getById(id);
|
||||
// ttBanner.setPicture("");
|
||||
return R.ok(ttAdvertisement);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtAdvertisement ttAdvertisement) {
|
||||
if (StringUtils.isEmpty(ttAdvertisement.getPicture())) ttAdvertisement.setPicture("");
|
||||
else ttAdvertisement.setPicture(RuoYiConfig.getDomainName() + ttAdvertisement.getPicture());
|
||||
return toAjax(ttAdvertisementService.save(ttAdvertisement));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtAdvertisement ttAdvertisement) {
|
||||
String msg = ttAdvertisementService.updatePicById(ttAdvertisement);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(ttAdvertisementService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtAnnouncementService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.domain.other.TtAnnouncement;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/announcement")
|
||||
public class TtAnnouncementController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private TtAnnouncementService ttAnnouncementService;
|
||||
|
||||
/**
|
||||
* 获取公告列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtAnnouncement> list() {
|
||||
startPage();
|
||||
List<TtAnnouncement> announcementList = ttAnnouncementService.getAnnouncementList();
|
||||
return getPageData(announcementList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公告详情
|
||||
*/
|
||||
@GetMapping("/{announcementId}")
|
||||
public AjaxResult getAnnouncementByAnnouncementId(@PathVariable Integer announcementId) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
TtAnnouncement ttAnnouncement = ttAnnouncementService.getAnnouncementByAnnouncementId(userId, announcementId);
|
||||
return AjaxResult.success(ttAnnouncement);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增公告
|
||||
*/
|
||||
@PostMapping
|
||||
public AjaxResult addAnnouncement(@RequestBody TtAnnouncement ttAnnouncement) {
|
||||
return ttAnnouncementService.addAnnouncement(ttAnnouncement) > 0 ? AjaxResult.success("新增成功") : AjaxResult.error("新增失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改公告
|
||||
*/
|
||||
@PutMapping
|
||||
public AjaxResult editAnnouncement(@RequestBody TtAnnouncement ttAnnouncement) {
|
||||
return ttAnnouncementService.editAnnouncement(ttAnnouncement) > 0 ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除公告
|
||||
*/
|
||||
@DeleteMapping("/{announcementId}")
|
||||
public AjaxResult removeAnnouncementByAnnouncementId(@PathVariable Integer announcementId) {
|
||||
return ttAnnouncementService.removeAnnouncementByAnnouncementId(announcementId) > 0 ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.domain.other.TtBanner;
|
||||
import com.ruoyi.admin.service.TtBannerService;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/banner")
|
||||
public class TtBannerController extends BaseController {
|
||||
|
||||
private final TtBannerService bannerService;
|
||||
|
||||
public TtBannerController(TtBannerService bannerService) {
|
||||
this.bannerService = bannerService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtBanner> list() {
|
||||
startPage();
|
||||
List<TtBanner> list = bannerService.list();
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtBanner> getInfo(@PathVariable("id") Integer id) {
|
||||
TtBanner ttBanner = bannerService.getById(id);
|
||||
// ttBanner.setPicture("");
|
||||
return R.ok(ttBanner);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBanner ttBanner) {
|
||||
if (StringUtils.isEmpty(ttBanner.getPicture())) ttBanner.setPicture("");
|
||||
else ttBanner.setPicture(RuoYiConfig.getDomainName() + ttBanner.getPicture());
|
||||
return toAjax(bannerService.save(ttBanner));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBanner ttBanner) {
|
||||
String msg = bannerService.updateBannerById(ttBanner);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(bannerService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.domain.other.TtBonus;
|
||||
import com.ruoyi.admin.service.TtBonusService;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/bonus")
|
||||
public class TtBonusController extends BaseController {
|
||||
|
||||
private final TtBonusService bonusService;
|
||||
|
||||
public TtBonusController(TtBonusService bonusService) {
|
||||
this.bonusService = bonusService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtBonus> list(String type){
|
||||
startPage();
|
||||
LambdaQueryWrapper<TtBonus> wrapper = Wrappers.lambdaQuery();
|
||||
if (StringUtils.isNotEmpty(type)) wrapper.eq(TtBonus::getType, type);
|
||||
List<TtBonus> list = bonusService.list(wrapper);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtBonus> getInfo(@PathVariable("id") Integer id) {
|
||||
TtBonus bonus = bonusService.getById(id);
|
||||
bonus.setCoverPicture("");
|
||||
return R.ok(bonus);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBonus ttBonus) {
|
||||
if (StringUtils.isEmpty(ttBonus.getCoverPicture())) ttBonus.setCoverPicture("");
|
||||
else ttBonus.setCoverPicture(RuoYiConfig.getDomainName() + ttBonus.getCoverPicture());
|
||||
ttBonus.setCreateBy(getUsername());
|
||||
ttBonus.setCreateTime(DateUtils.getNowDate());
|
||||
return toAjax(bonusService.save(ttBonus));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBonus ttBonus) {
|
||||
ttBonus.setUpdateBy(getUsername());
|
||||
ttBonus.setUpdateTime(DateUtils.getNowDate());
|
||||
String msg = bonusService.updateBonusById(ttBonus);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(bonusService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.domain.body.BoxMockRequest;
|
||||
import com.ruoyi.admin.model.BoxMockCache;
|
||||
import com.ruoyi.admin.service.BoxMockService;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.util.core.fight.LotteryMachine;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.other.TtBoxBody;
|
||||
import com.ruoyi.domain.vo.BoxCacheDataVO;
|
||||
import com.ruoyi.domain.vo.TtBoxDataVO;
|
||||
import com.ruoyi.domain.vo.box.TtBoxUserVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "管理端 宝箱管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/box")
|
||||
public class TtBoxController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private TtBoxService boxService;
|
||||
|
||||
@Autowired
|
||||
private LotteryMachine lotteryMachine;
|
||||
|
||||
@Autowired
|
||||
private BoxMockService boxMockService;
|
||||
|
||||
@GetMapping("/test")
|
||||
public AjaxResult test() {
|
||||
return success();
|
||||
}
|
||||
|
||||
@ApiOperation("获取宝箱列表")
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtBoxDataVO> list(TtBoxBody ttBoxBody) {
|
||||
startPage();
|
||||
return boxService.selectTtBoxList(ttBoxBody);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{boxId}")
|
||||
public R<TtBox> getInfo(@PathVariable("boxId") Long boxId) {
|
||||
TtBox ttBox = boxService.getById(boxId);
|
||||
return R.ok(ttBox);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBox ttBox) {
|
||||
ttBox.setCreateBy(getUsername());
|
||||
ttBox.setCreateTime(DateUtils.getNowDate());
|
||||
return toAjax(boxService.save(ttBox));
|
||||
}
|
||||
|
||||
@ApiOperation("修改宝箱")
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBoxDataVO ttBoxDataVO) {
|
||||
String msg = boxService.updateTtBoxById(ttBoxDataVO);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@ApiOperation("清空当前奖池")
|
||||
@GetMapping("clearPrizePool/{boxId}")
|
||||
public R clearPrizePool(@PathVariable("boxId") Integer boxId) {
|
||||
|
||||
lotteryMachine.clearBoxPrizePool(boxId);
|
||||
|
||||
return R.ok("清空当前奖池成功。");
|
||||
}
|
||||
|
||||
@DeleteMapping("/{boxIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] boxIds) {
|
||||
return toAjax(boxService.removeByIds(Arrays.asList(boxIds)));
|
||||
}
|
||||
|
||||
@GetMapping("/resetBox/{boxId}")
|
||||
public AjaxResult resetBox(@PathVariable Integer boxId) {
|
||||
boxService.isReplenishment(boxId);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@GetMapping("/statisticsBoxData/{boxId}")
|
||||
public R<BoxCacheDataVO> statisticsBoxData(@PathVariable Integer boxId, @RequestParam(required = false) Date date) {
|
||||
BoxCacheDataVO boxCacheData = boxService.statisticsBoxData(boxId, date);
|
||||
return R.ok(boxCacheData);
|
||||
}
|
||||
|
||||
@ApiOperation("添加宝箱三级爆率玩家范围")
|
||||
@PostMapping("/createTtBoxThirdExplosiveUsers")
|
||||
public AjaxResult createTtBoxThirdExplosiveUsers(@RequestBody TtBoxUserVO ttBoxUserVO) {
|
||||
String msg = boxService.createTtBoxThirdExplosiveUsers(ttBoxUserVO);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@ApiOperation("查询宝箱三级爆率玩家范围")
|
||||
@GetMapping("/queryTtBoxThirdExplosiveUsers/{boxId}")
|
||||
public AjaxResult queryBoxUsers(@PathVariable Integer boxId) {
|
||||
return boxService.queryTtBoxThirdExplosiveUsers(boxId);
|
||||
}
|
||||
|
||||
@ApiOperation("删除宝箱三级爆率玩家范围")
|
||||
@PostMapping("/deleteTtBoxThirdExplosiveUsers")
|
||||
public AjaxResult deleteTtBoxThirdExplosiveUsers(@RequestBody TtBoxUserVO ttBoxUserVO) {
|
||||
return boxService.deleteTtBoxThirdExplosiveUsers(ttBoxUserVO);
|
||||
}
|
||||
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, TtBoxBody ttBoxBody) {
|
||||
var list = boxService.selectTtBoxList(ttBoxBody);
|
||||
ExcelUtil<TtBoxDataVO> util = new ExcelUtil<>(TtBoxDataVO.class);
|
||||
util.exportExcel(response, list.getRows(), "宝箱列表");
|
||||
}
|
||||
|
||||
@ApiOperation("查询开箱临时爆率队列")
|
||||
@GetMapping("/mockresult/list")
|
||||
public R<List<BoxMockCache.MockItem>> getBoxMockResult(@RequestParam("uid") Integer uid) {
|
||||
return R.ok(boxMockService.getMockResultList(uid));
|
||||
}
|
||||
|
||||
@ApiOperation("设置开箱临时爆率队列")
|
||||
@PostMapping("/mockresult/list")
|
||||
public R<List<BoxMockCache.MockItem>> updateBoxMockResult(@RequestBody BoxMockRequest request) {
|
||||
return R.ok(boxMockService.updateMockResultList(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.admin.domain.TtBoxOpenChance;
|
||||
import com.ruoyi.admin.service.TtBoxOpenChanceService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* 开箱机会Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-07-09
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin/chance")
|
||||
public class TtBoxOpenChanceController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private TtBoxOpenChanceService ttBoxOpenChanceService;
|
||||
|
||||
/**
|
||||
* 查询开箱机会列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('admin:chance:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(TtBoxOpenChance ttBoxOpenChance)
|
||||
{
|
||||
startPage();
|
||||
List<TtBoxOpenChance> list = ttBoxOpenChanceService.selectTtBoxOpenChanceList(ttBoxOpenChance);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开箱机会详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('admin:chance:query')")
|
||||
@GetMapping(value = "/{ornamentId}")
|
||||
public AjaxResult getInfo(@PathVariable("ornamentId") Integer ornamentId)
|
||||
{
|
||||
return success(ttBoxOpenChanceService.selectTtBoxOpenChanceByOrnamentId(ornamentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增开箱机会
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('admin:chance:add')")
|
||||
@Log(title = "开箱机会", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBoxOpenChance ttBoxOpenChance)
|
||||
{
|
||||
return toAjax(ttBoxOpenChanceService.insertTtBoxOpenChance(ttBoxOpenChance));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改开箱机会
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('admin:chance:edit')")
|
||||
@Log(title = "开箱机会", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBoxOpenChance ttBoxOpenChance)
|
||||
{
|
||||
return toAjax(ttBoxOpenChanceService.updateTtBoxOpenChance(ttBoxOpenChance));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除开箱机会
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('admin:chance:remove')")
|
||||
@Log(title = "开箱机会", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ornamentIds}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ornamentIds)
|
||||
{
|
||||
return toAjax(ttBoxOpenChanceService.deleteTtBoxOpenChanceByOrnamentIds(ornamentIds));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.domain.other.TtBoxOrnaments;
|
||||
import com.ruoyi.admin.service.TtBoxOrnamentsService;
|
||||
import com.ruoyi.domain.vo.TtBoxOrnamentsDataVO;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Api(tags = "管理端 宝箱物品")
|
||||
@RestController
|
||||
@RequestMapping("/admin/boxOrnaments")
|
||||
public class TtBoxOrnamentsController extends BaseController {
|
||||
|
||||
private final TtBoxOrnamentsService boxOrnamentsService;
|
||||
|
||||
public TtBoxOrnamentsController(TtBoxOrnamentsService boxOrnamentsService) {
|
||||
this.boxOrnamentsService = boxOrnamentsService;
|
||||
}
|
||||
|
||||
@ApiOperation("宝箱物品详情")
|
||||
@GetMapping("/list/{boxId}")
|
||||
public PageDataInfo<TtBoxOrnamentsDataVO> list(@PathVariable("boxId") Integer boxId) {
|
||||
startPage();
|
||||
List<TtBoxOrnamentsDataVO> list = boxOrnamentsService.selectTtBoxOrnamentsList(boxId);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@ApiOperation("宝箱统计数据")
|
||||
@GetMapping("/globalData/{boxId}")
|
||||
public R globalData(@PathVariable("boxId") Integer boxId) {
|
||||
return boxOrnamentsService.globalData(boxId);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtBoxOrnaments> getInfo(@PathVariable("id") Integer id) {
|
||||
TtBoxOrnaments ttBoxOrnaments = boxOrnamentsService.getById(id);
|
||||
return R.ok(ttBoxOrnaments);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBoxOrnaments ttBoxOrnaments) {
|
||||
ttBoxOrnaments.setCreateBy(getUsername());
|
||||
ttBoxOrnaments.setCreateTime(DateUtils.getNowDate());
|
||||
String msg = boxOrnamentsService.saveBoxOrnaments(ttBoxOrnaments);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改宝箱物品
|
||||
*
|
||||
* @param ttBoxOrnamentsDataVO
|
||||
* @return
|
||||
*/
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBoxOrnamentsDataVO ttBoxOrnamentsDataVO) {
|
||||
String msg = boxOrnamentsService.updateBoxOrnamentsById(ttBoxOrnamentsDataVO);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{boxId}/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer boxId, @PathVariable Long[] ids) {
|
||||
String msg = boxOrnamentsService.removeBoxOrnamentsByIds(boxId, Arrays.asList(ids));
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@GetMapping("/getProfitMargin/{boxId}")
|
||||
public AjaxResult getProfitMargin(@PathVariable("boxId") Integer boxId) {
|
||||
return boxOrnamentsService.getProfitMargin(boxId);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public static class batchAddParam{
|
||||
private Integer boxId;
|
||||
private Integer partyType;
|
||||
// private List<Integer> ornamentsIds;
|
||||
|
||||
@NotEmpty(message = "物品id不能为空")
|
||||
private List<Long> ornamentIds;
|
||||
}
|
||||
// 宝箱填货
|
||||
@ApiOperation("宝箱填货")
|
||||
@PostMapping("/batchAdd")
|
||||
public AjaxResult batchAdd(@RequestBody batchAddParam param) {
|
||||
return boxOrnamentsService.batchAdd(param);
|
||||
// return StringUtils.isEmpty(msg) ? AjaxResult.success("批量填货成功,请手动修改饰品数量!") : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.domain.other.TtBoxRecordsBody;
|
||||
import com.ruoyi.domain.vo.TtBoxRecordsDataVO;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/boxRecords")
|
||||
public class TtBoxRecordsController extends BaseController {
|
||||
|
||||
private final TtBoxRecordsService boxRecordsService;
|
||||
|
||||
public TtBoxRecordsController(TtBoxRecordsService boxRecordsService) {
|
||||
this.boxRecordsService = boxRecordsService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtBoxRecordsDataVO> list(TtBoxRecordsBody ttBoxRecordsBody) {
|
||||
startPage();
|
||||
List<TtBoxRecordsDataVO> list = boxRecordsService.selectBoxRecordsList(ttBoxRecordsBody);
|
||||
return getPageData(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.domain.other.TtBoxType;
|
||||
import com.ruoyi.admin.service.TtBoxTypeService;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/boxType")
|
||||
@Slf4j
|
||||
public class TtBoxTypeController extends BaseController {
|
||||
|
||||
private final TtBoxTypeService boxTypeService;
|
||||
|
||||
public TtBoxTypeController(TtBoxTypeService boxTypeService) {
|
||||
this.boxTypeService = boxTypeService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtBoxType> list(@RequestParam(required = false) String isFightType) {
|
||||
startPage();
|
||||
LambdaQueryWrapper<TtBoxType> wrapper = Wrappers.lambdaQuery();
|
||||
if (StringUtils.isNotEmpty(isFightType)) wrapper.eq(TtBoxType::getIsFightType, isFightType);
|
||||
List<TtBoxType> list = boxTypeService.list(wrapper);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtBoxType> getInfo(@PathVariable("id") Integer id) {
|
||||
TtBoxType boxType = boxTypeService.getById(id);
|
||||
boxType.setIcon("");
|
||||
return R.ok(boxType);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtBoxType ttBoxType) {
|
||||
if (StringUtils.isEmpty(ttBoxType.getIcon())) ttBoxType.setIcon("");
|
||||
else ttBoxType.setIcon(RuoYiConfig.getDomainName() + ttBoxType.getIcon());
|
||||
ttBoxType.setCreateTime(DateUtils.getNowDate());
|
||||
return toAjax(boxTypeService.save(ttBoxType));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtBoxType ttBoxType) {
|
||||
ttBoxType.setUpdateTime(DateUtils.getNowDate());
|
||||
String msg = boxTypeService.updateBoxTypeById(ttBoxType);
|
||||
return StringUtils.isEmpty(msg) ? AjaxResult.success() : AjaxResult.error(msg);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(boxTypeService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.domain.other.TtContent;
|
||||
import com.ruoyi.admin.service.TtContentService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/content")
|
||||
public class TtContentController extends BaseController {
|
||||
|
||||
private final TtContentService contentService;
|
||||
|
||||
public TtContentController(TtContentService contentService) {
|
||||
this.contentService = contentService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtContent> list(TtContent ttContent) {
|
||||
startPage();
|
||||
List<TtContent> list = contentService.queryList(ttContent);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtContent> getInfo(@PathVariable("id") Long id) {
|
||||
return R.ok(contentService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtContent ttContent) {
|
||||
ttContent.setCreateTime(DateUtils.getNowDate());
|
||||
return toAjax(contentService.save(ttContent));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtContent ttContent) {
|
||||
ttContent.setUpdateTime(DateUtils.getNowDate());
|
||||
return toAjax(contentService.updateById(ttContent));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(contentService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.domain.other.TtContentType;
|
||||
import com.ruoyi.admin.service.TtContentTypeService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/contentType")
|
||||
public class TtContentTypeController extends BaseController {
|
||||
|
||||
private final TtContentTypeService contentTypeService;
|
||||
|
||||
public TtContentTypeController(TtContentTypeService contentTypeService) {
|
||||
this.contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtContentType> list(TtContentType ttContentType) {
|
||||
startPage();
|
||||
List<TtContentType> list = contentTypeService.queryList(ttContentType);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<TtContentType> getInfo(@PathVariable("id") Long id) {
|
||||
return R.ok(contentTypeService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TtContentType ttContentType) {
|
||||
ttContentType.setCreateTime(DateUtils.getNowDate());
|
||||
return toAjax(contentTypeService.save(ttContentType));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TtContentType ttContentType) {
|
||||
ttContentType.setUpdateTime(DateUtils.getNowDate());
|
||||
return toAjax(contentTypeService.updateById(ttContentType));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(contentTypeService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtDeliveryRecordService;
|
||||
import com.ruoyi.domain.other.TtDeliveryApplyBody;
|
||||
import com.ruoyi.domain.vo.DeliveryApplyVO;
|
||||
import com.ruoyi.domain.other.TtDeliveryRecordBody;
|
||||
import com.ruoyi.domain.vo.TtDeliveryRecordDataVO;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "管理端 发货管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/deliverGoods")
|
||||
public class TtDeliveryRecordController extends BaseController {
|
||||
|
||||
private final TtDeliveryRecordService deliveryRecordService;
|
||||
|
||||
public TtDeliveryRecordController(TtDeliveryRecordService deliveryRecordService) {
|
||||
this.deliveryRecordService = deliveryRecordService;
|
||||
}
|
||||
|
||||
@ApiOperation("发货申请列表")
|
||||
@GetMapping("/getDeliveryApplyList")
|
||||
public PageDataInfo<DeliveryApplyVO> getDeliveryApplyList(TtDeliveryApplyBody deliveryApplyBody) {
|
||||
startPage();
|
||||
List<DeliveryApplyVO> list = deliveryRecordService.getDeliveryApplyList(deliveryApplyBody);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@ApiOperation("退回发货申请")
|
||||
@PostMapping("/deliveryFail")
|
||||
public R<Boolean> deliveryFail(@RequestParam("deliveryRecordId") Integer deliveryRecordId, @RequestParam("message") String message) {
|
||||
String msg = deliveryRecordService.deliveryFail(deliveryRecordId, message);
|
||||
return StringUtils.isEmpty(msg) ? R.ok(true, "操作成功!") : R.fail(false, msg);
|
||||
}
|
||||
|
||||
@ApiOperation("发货记录申请")
|
||||
@GetMapping("/getDeliveryRecordList")
|
||||
public PageDataInfo<TtDeliveryRecordDataVO> getDeliveryRecordList(TtDeliveryRecordBody deliveryRecordBody) {
|
||||
startPage();
|
||||
List<TtDeliveryRecordDataVO> list = deliveryRecordService.getDeliveryRecordList(deliveryRecordBody);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ruoyi.admin.controller;
|
||||
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.admin.service.TtFightService;
|
||||
import com.ruoyi.domain.vo.FightBoxDataVO;
|
||||
import com.ruoyi.domain.other.TtFightBody;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/fight")
|
||||
public class TtFightController extends BaseController {
|
||||
|
||||
private final TtFightService fightService;
|
||||
|
||||
public TtFightController(TtFightService fightService) {
|
||||
this.fightService = fightService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public PageDataInfo<TtFight> list(TtFightBody ttFightBody) {
|
||||
startPage();
|
||||
List<TtFight> list = fightService.selectFightList(ttFightBody);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@GetMapping("/getFightBoxList/{fightId}")
|
||||
public PageDataInfo<FightBoxDataVO> getFightBoxList(@PathVariable("fightId") Integer fightId) {
|
||||
startPage();
|
||||
List<FightBoxDataVO> list = fightService.selectFightBoxList(fightId);
|
||||
return getPageData(list);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user