提交roll日创建

This commit is contained in:
2026-07-14 18:32:14 +08:00
parent 09883eacdb
commit 6971daf9a7
16 changed files with 1119 additions and 52 deletions
@@ -0,0 +1,88 @@
package com.ruoyi.admin.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.service.TtOfficialRollConfigService;
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.domain.entity.roll.TtOfficialRollConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "管理端 官方周期Roll配置")
@RestController
@RequestMapping("/admin/officialRollConfig")
public class TtOfficialRollConfigController extends BaseController {
private final TtOfficialRollConfigService configService;
public TtOfficialRollConfigController(TtOfficialRollConfigService configService) {
this.configService = configService;
}
@ApiOperation("配置列表")
@GetMapping("/list")
public PageDataInfo<TtOfficialRollConfig> list(
@RequestParam(value = "configName", required = false) String configName,
@RequestParam(value = "periodType", required = false) String periodType,
@RequestParam(value = "status", required = false) String status) {
startPage();
LambdaQueryWrapper<TtOfficialRollConfig> wrapper = Wrappers.lambdaQuery();
if (StringUtils.isNotEmpty(configName)) wrapper.like(TtOfficialRollConfig::getConfigName, configName);
if (StringUtils.isNotEmpty(periodType)) wrapper.eq(TtOfficialRollConfig::getPeriodType, periodType);
if (StringUtils.isNotEmpty(status)) wrapper.eq(TtOfficialRollConfig::getStatus, status);
wrapper.orderByAsc(TtOfficialRollConfig::getSortBy).orderByDesc(TtOfficialRollConfig::getCreateTime);
List<TtOfficialRollConfig> configList = configService.list(wrapper);
return getPageData(configList);
}
@ApiOperation("配置详情")
@GetMapping("/{id}")
public R<TtOfficialRollConfig> getInfo(@PathVariable("id") Integer id) {
return R.ok(configService.getById(id));
}
@ApiOperation("新增配置")
@PostMapping
public AjaxResult add(@RequestBody TtOfficialRollConfig config) {
config.setCreateBy(getUsername());
config.setCreateTime(DateUtils.getNowDate());
return configService.addConfig(config);
}
@ApiOperation("编辑配置")
@PutMapping
public AjaxResult edit(@RequestBody TtOfficialRollConfig config) {
config.setUpdateBy(getUsername());
config.setUpdateTime(DateUtils.getNowDate());
return configService.editConfig(config);
}
@ApiOperation("删除配置")
@DeleteMapping("/{id}")
public AjaxResult remove(@PathVariable("id") Integer id) {
return configService.removeById(id) ? AjaxResult.success() : AjaxResult.error("删除失败");
}
@ApiOperation("启用/停用配置")
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody TtOfficialRollConfig config) {
if (config.getId() == null) return AjaxResult.error("配置ID不能为空");
if (!"0".equals(config.getStatus()) && !"1".equals(config.getStatus())) {
return AjaxResult.error("状态不合法");
}
TtOfficialRollConfig update = new TtOfficialRollConfig();
update.setId(config.getId());
update.setStatus(config.getStatus());
update.setUpdateBy(getUsername());
update.setUpdateTime(DateUtils.getNowDate());
return configService.updateById(update) ? AjaxResult.success() : AjaxResult.error("操作失败");
}
}
@@ -201,12 +201,13 @@ public class TtRollController extends BaseController {
return success("生成成功");
}
/** 查看Roll房所有CDK列表 */
/** 查看Roll房所有CDK列表(分页) */
@ApiOperation("查看Roll房CDK列表")
@GetMapping("/cdk/list/{rollId}")
public AjaxResult getCdkList(@PathVariable("rollId") Integer rollId) {
public PageDataInfo<TtRollCdk> getCdkList(@PathVariable("rollId") Integer rollId) {
startPage();
List<TtRollCdk> list = rollCdkService.listByRollId(rollId);
return success(list);
return getPageData(list);
}
/** 删除指定CDK */
@@ -0,0 +1,9 @@
package com.ruoyi.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.domain.entity.roll.TtOfficialRollConfig;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TtOfficialRollConfigMapper extends BaseMapper<TtOfficialRollConfig> {
}
@@ -0,0 +1,14 @@
package com.ruoyi.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.domain.entity.roll.TtOfficialRollConfig;
public interface TtOfficialRollConfigService extends IService<TtOfficialRollConfig> {
/** 新增配置(含校验) */
AjaxResult addConfig(TtOfficialRollConfig config);
/** 编辑配置(含校验) */
AjaxResult editConfig(TtOfficialRollConfig config);
}
@@ -0,0 +1,78 @@
package com.ruoyi.admin.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.admin.mapper.TtOfficialRollConfigMapper;
import com.ruoyi.admin.service.TtOfficialRollConfigService;
import com.ruoyi.admin.service.TtRollJackpotService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.domain.entity.roll.TtOfficialRollConfig;
import com.ruoyi.domain.entity.roll.TtRollJackpot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class TtOfficialRollConfigServiceImpl
extends ServiceImpl<TtOfficialRollConfigMapper, TtOfficialRollConfig>
implements TtOfficialRollConfigService {
/** 周期类型:每日 */
public static final String PERIOD_DAILY = "DAILY";
@Autowired
private TtRollJackpotService rollJackpotService;
@Override
public AjaxResult addConfig(TtOfficialRollConfig config) {
AjaxResult check = validate(config);
if (check != null) return check;
normalize(config);
return save(config) ? AjaxResult.success() : AjaxResult.error("新增失败");
}
@Override
public AjaxResult editConfig(TtOfficialRollConfig config) {
if (config.getId() == null) return AjaxResult.error("配置ID不能为空");
AjaxResult check = validate(config);
if (check != null) return check;
return updateById(config) ? AjaxResult.success() : AjaxResult.error("修改失败");
}
/**
* 基本校验:配置名、房间名、奖池存在、周期合法、人数>0、门槛/机器人数非负;返回 null 表示通过
*/
private AjaxResult validate(TtOfficialRollConfig config) {
if (config == null) return AjaxResult.error("参数不能为空");
if (StringUtils.isEmpty(config.getConfigName())) return AjaxResult.error("配置标识不能为空");
if (StringUtils.isEmpty(config.getRoomName())) return AjaxResult.error("房间名称不能为空");
if (config.getJackpotId() == null) return AjaxResult.error("请选择奖池");
TtRollJackpot jackpot = rollJackpotService.getById(config.getJackpotId());
if (jackpot == null) return AjaxResult.error("指定的奖池不存在");
String period = config.getPeriodType();
if (!PERIOD_DAILY.equals(period)) {
return AjaxResult.error("周期类型不合法,仅支持 DAILY");
}
if (config.getPeopleNum() == null || config.getPeopleNum() <= 0) {
return AjaxResult.error("人数上限必须大于0");
}
if (config.getMinRecharge() != null && config.getMinRecharge().compareTo(BigDecimal.ZERO) < 0) {
return AjaxResult.error("充值门槛不能为负数");
}
if (config.getRobotNum() != null && config.getRobotNum() < 0) {
return AjaxResult.error("机器人数量不能为负数");
}
return null;
}
/**
* 归一化默认值
*/
private void normalize(TtOfficialRollConfig config) {
if (config.getMinRecharge() == null) config.setMinRecharge(BigDecimal.ZERO);
if (config.getRobotNum() == null) config.setRobotNum(0);
if (config.getSortBy() == null) config.setSortBy(0);
if (StringUtils.isEmpty(config.getStatus())) config.setStatus("0");
}
}
@@ -132,7 +132,12 @@ public class TtUserServiceImpl extends ServiceImpl<TtUserMapper, TtUser> impleme
wrapper.like(TtUser::getUserName, ttUserBody.getUserName());
if (StringUtils.isNotNull(ttUserBody.getNickName()))
wrapper.like(TtUser::getNickName, ttUserBody.getNickName());
if (StringUtils.isNotNull(ttUserBody.getUserType())) wrapper.eq(TtUser::getUserType, ttUserBody.getUserType());
if (StringUtils.isNotEmpty(ttUserBody.getUserType())) {
wrapper.eq(TtUser::getUserType, ttUserBody.getUserType());
} else {
// 用户列表默认排除机器人(userType=03),机器人在独立的机器人管理页显式传 userType=03 查询
wrapper.and(w -> w.isNull(TtUser::getUserType).or().ne(TtUser::getUserType, "03"));
}
if (StringUtils.isNotEmpty(ttUserBody.getPhoneNumber()))
wrapper.likeRight(TtUser::getPhoneNumber, ttUserBody.getPhoneNumber());
if (StringUtils.isNotEmpty(ttUserBody.getStatus())) wrapper.eq(TtUser::getStatus, ttUserBody.getStatus());
@@ -0,0 +1,201 @@
package com.ruoyi.admin.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.domain.body.AddRobotBody;
import com.ruoyi.admin.service.TtOfficialRollConfigService;
import com.ruoyi.admin.service.TtRollCdkService;
import com.ruoyi.admin.service.TtRollService;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.admin.service.impl.TtOfficialRollConfigServiceImpl;
import com.ruoyi.domain.entity.roll.TtOfficialRollConfig;
import com.ruoyi.domain.entity.roll.TtRoll;
import com.ruoyi.domain.entity.sys.TtUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* 官方周期Roll房定时创建任务。
* <p>
* 注册到 RuoYi 系统监控-定时任务(由 sys_job 统一调度,不加 @Scheduled):
* - 任务名称:官方日Roll生成 调用目标:officialRollTask.generateDailyRolls() 默认cron0 0 10 * * ?(每天10:00
* <p>
* 复用官方房(roll_type=0)语义,直接创建 tt_roll,并按配置的机器人数量自动加入机器人。
*/
@Slf4j
@Component("officialRollTask")
public class OfficialRollTask {
/** 官方房类型 */
private static final String ROLL_TYPE_OFFICIAL = "0";
/** 未开奖状态 */
private static final String ROLL_STATUS_OPEN = "0";
/** 官方房创建者ID(0=官方) */
private static final Integer OFFICIAL_USER_ID = 0;
/** 机器人用户类型 */
private static final String ROBOT_USER_TYPE = "03";
@Autowired
private TtOfficialRollConfigService configService;
@Autowired
private TtRollService rollService;
@Autowired
private TtUserService userService;
@Autowired
private TtRollCdkService rollCdkService;
/**
* 每天为所有启用的 DAILY 配置创建当日官方roll房并自动加入机器人。
* 充值统计起点=当天0点(今日充值),开奖=当日23:59:59。
*/
public void generateDailyRolls() {
Date start = todayStart();
Date end = todayEnd();
generate(TtOfficialRollConfigServiceImpl.PERIOD_DAILY, start, end);
}
private void generate(String periodType, Date rechargeStartTime, Date endTime) {
log.info("[官方周期Roll] 开始生成 period={}, start={}, end={}", periodType, rechargeStartTime, endTime);
LambdaQueryWrapper<TtOfficialRollConfig> query = Wrappers.lambdaQuery();
query.eq(TtOfficialRollConfig::getStatus, "0")
.eq(TtOfficialRollConfig::getPeriodType, periodType);
List<TtOfficialRollConfig> configs = configService.list(query);
if (configs == null || configs.isEmpty()) {
log.info("[官方周期Roll] 无启用的 {} 配置,跳过", periodType);
return;
}
int created = 0;
int skipped = 0;
for (TtOfficialRollConfig config : configs) {
try {
// 幂等:同一配置 + 同一充值统计起点 至多生成一个房间
long exists = rollService.count(Wrappers.<TtRoll>lambdaQuery()
.eq(TtRoll::getOfficialConfigId, config.getId())
.eq(TtRoll::getRechargeStartTime, rechargeStartTime));
if (exists > 0) {
skipped++;
log.info("[官方周期Roll] 配置{}({})本周期房间已存在,跳过", config.getId(), config.getConfigName());
continue;
}
TtRoll roll = TtRoll.builder()
.jackpotId(config.getJackpotId())
.userId(OFFICIAL_USER_ID)
.rollType(ROLL_TYPE_OFFICIAL)
.rollName(config.getRoomName())
.description(config.getDescription())
.endTime(endTime)
.peopleNum(config.getPeopleNum())
.rollPassword(config.getRollPassword())
.minRecharge(config.getMinRecharge() != null ? config.getMinRecharge() : BigDecimal.ZERO)
.rechargeStartTime(rechargeStartTime)
.sortBy(config.getSortBy())
.rollStatus(ROLL_STATUS_OPEN)
.officialConfigId(config.getId())
.createBy("system")
.createTime(new Date())
.build();
rollService.save(roll);
// 自动加入机器人:数量取配置值,且不超过可用机器人总数;addRobot 会自动跳过该房已有机器人
addRobots(roll.getId(), config);
// 按配置数量为房间生成CDK(0则不生成,使用普通密码)
generateCdks(roll.getId(), config);
// 回写上次生成时间(幂等辅助/可观测)
TtOfficialRollConfig update = new TtOfficialRollConfig();
update.setId(config.getId());
update.setLastGenerateTime(new Date());
configService.updateById(update);
created++;
log.info("[官方周期Roll] 配置{}({}) 已生成房间 rollId={},起点={},开奖={}",
config.getId(), config.getConfigName(), roll.getId(), rechargeStartTime, endTime);
} catch (Exception e) {
log.error("[官方周期Roll] 配置{}生成房间失败:{}", config.getId(), e.getMessage(), e);
}
}
log.info("[官方周期Roll] 生成完成 period={}, 配置数={}, 新建={}, 跳过={}",
periodType, configs.size(), created, skipped);
}
/**
* 为指定房间按配置数量加入机器人。数量取 min(配置机器人数, 可用机器人总数),避免"可用机器人数量不足"导致一个都不加。
*/
private void addRobots(Integer rollId, TtOfficialRollConfig config) {
Integer robotNum = config.getRobotNum();
if (rollId == null || robotNum == null || robotNum <= 0) {
return;
}
try {
long robotTotal = userService.count(new QueryWrapper<TtUser>().eq("user_type", ROBOT_USER_TYPE));
int toAdd = (int) Math.min(robotNum.longValue(), robotTotal);
if (toAdd <= 0) {
log.warn("[官方周期Roll] 配置{}需要{}个机器人,但用户表无可用机器人,跳过加入", config.getId(), robotNum);
return;
}
AddRobotBody body = new AddRobotBody();
body.setRollId(rollId);
body.setRobotNum(toAdd);
rollService.addRobot(body);
log.info("[官方周期Roll] 配置{} 房间{} 已尝试加入{}个机器人", config.getId(), rollId, toAdd);
} catch (Exception e) {
log.error("[官方周期Roll] 配置{} 房间{} 加入机器人失败:{}", config.getId(), rollId, e.getMessage(), e);
}
}
/**
* 按配置数量为房间生成CDK;0或空则不生成(使用普通密码)。
*/
private void generateCdks(Integer rollId, TtOfficialRollConfig config) {
Integer cdkCount = config.getCdkCount();
if (rollId == null || cdkCount == null || cdkCount <= 0) {
return;
}
try {
rollCdkService.generateCdks(rollId, cdkCount);
log.info("[官方周期Roll] 配置{} 房间{} 已生成{}个CDK", config.getId(), rollId, cdkCount);
} catch (Exception e) {
log.error("[官方周期Roll] 配置{} 房间{} 生成CDK失败:{}", config.getId(), rollId, e.getMessage(), e);
}
}
/** 今天0点 */
private Date todayStart() {
Calendar c = Calendar.getInstance();
clearTime(c);
return c.getTime();
}
/** 今天23:59:59 */
private Date todayEnd() {
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 23);
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 59);
c.set(Calendar.MILLISECOND, 0);
return c.getTime();
}
private void clearTime(Calendar c) {
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
}
}