diff --git a/doc/官方日Roll与机器人管理.sql b/doc/官方日Roll与机器人管理.sql new file mode 100644 index 0000000..2b8f956 --- /dev/null +++ b/doc/官方日Roll与机器人管理.sql @@ -0,0 +1,67 @@ +-- ============================================================ +-- 官方日Roll定时任务 + 机器人管理页面 一次性数据库脚本 +-- 目标库:ruoyi 业务库(与 tt_roll / tt_user / sys_menu / sys_job 同库) +-- 说明:执行一次即可;执行后请【重启后端】,随后在"系统监控-定时任务"可见"官方日Roll生成"。 +-- ============================================================ + +-- ------------------------------------------------------------ +-- 1) 官方周期Roll房配置表 +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS `tt_official_roll_config` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '配置ID', + `config_name` varchar(64) NOT NULL DEFAULT '' COMMENT '配置标识', + `jackpot_id` int DEFAULT NULL COMMENT '奖池ID(tt_roll_jackpot.jackpot_id)', + `period_type` varchar(16) NOT NULL DEFAULT 'DAILY' COMMENT '周期类型 DAILY每日/WEEKLY每周', + `room_name` varchar(64) NOT NULL DEFAULT '' COMMENT '生成的房间名称', + `description` varchar(255) DEFAULT '' COMMENT '房间描述', + `roll_password` varchar(32) DEFAULT '' COMMENT '房间密码,为空则无密码', + `min_recharge` decimal(10,2) DEFAULT '0.00' COMMENT '充值门槛,0为无门槛', + `people_num` int DEFAULT '50' COMMENT '人数上限', + `robot_num` int DEFAULT '0' COMMENT '每次自动加入的机器人数量,0则不加', + `cdk_count` int DEFAULT '0' COMMENT '每次自动创建房间生成的CDK数量,0则不启用CDK', + `sort_by` int DEFAULT '0' COMMENT '排序', + `status` char(1) DEFAULT '0' COMMENT '状态 0启用 1停用', + `last_generate_time` datetime DEFAULT NULL COMMENT '上次生成时间', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `del_flag` char(1) DEFAULT '0' COMMENT '删除标志 0存在 2删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='官方周期Roll房配置' ROW_FORMAT=DYNAMIC; + +-- 若 tt_official_roll_config 表此前已创建(无 cdk_count 列),执行下句补列;全新执行本脚本已含该列,可跳过: +-- ALTER TABLE `tt_official_roll_config` ADD COLUMN `cdk_count` int DEFAULT '0' COMMENT '每次自动创建房间生成的CDK数量,0则不启用CDK'; + +-- ------------------------------------------------------------ +-- 2) tt_roll 增加 official_config_id 列(标记房间由哪条配置生成,用于每日幂等) +-- 注意:MySQL 不支持 ADD COLUMN IF NOT EXISTS,若该列已存在请跳过本句。 +-- ------------------------------------------------------------ +ALTER TABLE `tt_roll` + ADD COLUMN `official_config_id` int DEFAULT NULL COMMENT '官方周期Roll配置ID(tt_official_roll_config.id),手动创建为NULL'; + +-- ------------------------------------------------------------ +-- 3) 注册定时任务到"系统监控-定时任务":每天10点创建官方日Roll房并自动加入机器人 +-- ------------------------------------------------------------ +SET @jobId = (SELECT IFNULL(MAX(job_id), 100) + 1 FROM sys_job); +INSERT INTO `sys_job`(`job_id`,`job_name`,`job_group`,`invoke_target`,`cron_expression`,`misfire_policy`,`concurrent`,`status`,`create_by`,`create_time`,`remark`) +VALUES (@jobId, '官方日Roll生成', 'DEFAULT', 'officialRollTask.generateDailyRolls()', '0 0 10 * * ?', '3', '1', '0', 'admin', NOW(), '每天10点为启用的日Roll配置自动创建官方Roll房并加入机器人'); + +-- ------------------------------------------------------------ +-- 4) 菜单:机器人管理(挂"用户管理"下)、日Roll配置(挂"Roll房设置"下) +-- 父菜单按名称查找;若你的父菜单名称不同,请调整下面 menu_name 的匹配值。 +-- 若父菜单未找到(@xxxId 为 NULL),菜单将挂到顶级(parent_id=0),可在"菜单管理"里再拖动调整。 +-- ------------------------------------------------------------ +SET @userMgrId = (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = '用户管理' AND menu_type = 'M' ORDER BY menu_id LIMIT 1) t); +SET @rollMgrId = (SELECT menu_id FROM (SELECT menu_id FROM sys_menu WHERE menu_name = 'Roll房设置' AND menu_type = 'M' ORDER BY menu_id LIMIT 1) t); +SET @menuBase = (SELECT IFNULL(MAX(menu_id), 2000) FROM sys_menu); + +INSERT INTO `sys_menu`(`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,`query`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`) +VALUES (@menuBase + 1, '机器人管理', IFNULL(@userMgrId, 0), 5, 'robotUser', 'skins/robotUser/index', '', 1, 0, 'C', '0', '0', 'admin:robot:list', 'user', 'admin', NOW()); + +INSERT INTO `sys_menu`(`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,`query`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`) +VALUES (@menuBase + 2, '日Roll配置', IFNULL(@rollMgrId, 0), 9, 'officialRollConfig', 'skins/ttRoll/officialRollConfig', '', 1, 0, 'C', '0', '0', 'admin:officialRollConfig:list', 'time', 'admin', NOW()); + +-- ============================================================ +-- 完成。若为非 admin 角色,请在"角色管理"里为角色勾选以上两个菜单权限。 +-- ============================================================ diff --git a/ruoyi-ui/src/api/skins/ttRoll/api.js b/ruoyi-ui/src/api/skins/ttRoll/api.js index ec6fd38..1d2926a 100644 --- a/ruoyi-ui/src/api/skins/ttRoll/api.js +++ b/ruoyi-ui/src/api/skins/ttRoll/api.js @@ -225,11 +225,12 @@ export function generateRollCdk(data) { }); } -/** 查询Roll房CDK列表 */ -export function getRollCdkList(rollId) { +/** 查询Roll房CDK列表(分页) */ +export function getRollCdkList(rollId, query) { return request({ url: `/admin/roll/cdk/list/${rollId}`, - method: 'get' + method: 'get', + params: query }); } @@ -240,3 +241,49 @@ export function deleteRollCdk(cdkId) { method: 'delete' }); } + +// ---- 官方周期Roll配置(日Roll/周Roll 自动创建) ---- + +// 官方周期Roll配置-列表 +export function officialRollConfigList(query) { + return request({ + url: '/admin/officialRollConfig/list', + method: 'get', + params: query + }); +} + +// 官方周期Roll配置-新增 +export function officialRollConfigAdd(data) { + return request({ + url: '/admin/officialRollConfig', + method: 'post', + data: data + }); +} + +// 官方周期Roll配置-编辑 +export function officialRollConfigChange(data) { + return request({ + url: '/admin/officialRollConfig', + method: 'put', + data: data + }); +} + +// 官方周期Roll配置-删除 +export function officialRollConfigDel(id) { + return request({ + url: `/admin/officialRollConfig/${id}`, + method: 'delete' + }); +} + +// 官方周期Roll配置-启用/停用 +export function officialRollConfigChangeStatus(data) { + return request({ + url: '/admin/officialRollConfig/changeStatus', + method: 'put', + data: data + }); +} diff --git a/ruoyi-ui/src/views/skins/robotUser/index.vue b/ruoyi-ui/src/views/skins/robotUser/index.vue new file mode 100644 index 0000000..a7f1081 --- /dev/null +++ b/ruoyi-ui/src/views/skins/robotUser/index.vue @@ -0,0 +1,209 @@ + + + diff --git a/ruoyi-ui/src/views/skins/ttRoll/index.vue b/ruoyi-ui/src/views/skins/ttRoll/index.vue index 8b7f7fa..e788fd7 100644 --- a/ruoyi-ui/src/views/skins/ttRoll/index.vue +++ b/ruoyi-ui/src/views/skins/ttRoll/index.vue @@ -318,6 +318,14 @@ + @@ -401,7 +409,12 @@ export default { cdkList: [], cdkLoading: false, cdkGenCount: 10, - currentCdkRollId: null + currentCdkRollId: null, + cdkTotal: 0, + cdkQueryParams: { + pageNum: 1, + pageSize: 10 + } }; }, mounted() { @@ -577,14 +590,16 @@ export default { // 打开CDK管理弹窗 handleCdk(row) { this.currentCdkRollId = row.id; + this.cdkQueryParams.pageNum = 1; this.cdkDialogVisible = true; this.loadCdkList(); }, // 加载CDK列表 loadCdkList() { this.cdkLoading = true; - getRollCdkList(this.currentCdkRollId).then(res => { - this.cdkList = res.data || []; + getRollCdkList(this.currentCdkRollId, this.cdkQueryParams).then(res => { + this.cdkList = res.rows || []; + this.cdkTotal = res.total || 0; this.cdkLoading = false; }); }, diff --git a/ruoyi-ui/src/views/skins/ttRoll/officialRollConfig.vue b/ruoyi-ui/src/views/skins/ttRoll/officialRollConfig.vue new file mode 100644 index 0000000..aceb120 --- /dev/null +++ b/ruoyi-ui/src/views/skins/ttRoll/officialRollConfig.vue @@ -0,0 +1,279 @@ + + + diff --git a/ruoyi-ui/src/views/skins/ttuser/index.vue b/ruoyi-ui/src/views/skins/ttuser/index.vue index c6936a7..789ae98 100644 --- a/ruoyi-ui/src/views/skins/ttuser/index.vue +++ b/ruoyi-ui/src/views/skins/ttuser/index.vue @@ -71,14 +71,6 @@ v-hasPermi="['admin:user:export']" >导出 - - 生成机器人 - @@ -114,21 +106,6 @@ 确 定 - - - - - - -
- 要生成的账号数量 -
-
- -
{ - this.getList(); - (this.newNumber = null), (this.addRobot = false); - const blob = new Blob([res], { - type: "text/plain;charset=utf-8" - }); - const objectURL = URL.createObjectURL(blob); - const aTag = document.createElement("a"); - aTag.href = objectURL; - aTag.download = `账号数据_${new Date().getTime()}.txt`; - aTag.click(); - URL.revokeObjectURL(objectURL); - }); - }, /** 查询注册用户列表 */ getList() { this.loading = true; diff --git a/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtOfficialRollConfig.java b/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtOfficialRollConfig.java new file mode 100644 index 0000000..4c2f8e1 --- /dev/null +++ b/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtOfficialRollConfig.java @@ -0,0 +1,92 @@ +package com.ruoyi.domain.entity.roll; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +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.util.Date; + +/** + * 官方周期Roll房配置模板。 + * 每条配置指定奖池、周期(日/周)、充值门槛、人数、机器人数量等, + * 由 OfficialRollTask 定时按周期自动创建 tt_roll 房间并自动加入机器人。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@Builder +@TableName(value = "tt_official_roll_config") +public class TtOfficialRollConfig implements Serializable { + + @TableField(exist = false) + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Excel(name = "配置标识") + private String configName; + + @Excel(name = "奖池ID") + private Integer jackpotId; + + /** 周期类型: DAILY=每日, WEEKLY=每周 */ + @Excel(name = "周期类型") + private String periodType; + + @Excel(name = "房间名称") + private String roomName; + + @Excel(name = "房间描述") + private String description; + + private String rollPassword; + + @Excel(name = "充值门槛") + private BigDecimal minRecharge; + + @Excel(name = "人数上限") + private Integer peopleNum; + + /** 每天(每周期)自动加入的机器人数量 */ + @Excel(name = "机器人数量") + private Integer robotNum; + + /** 每次自动创建房间生成的CDK数量,0则不启用CDK(使用普通密码) */ + @Excel(name = "CDK数量") + private Integer cdkCount; + + @Excel(name = "排序依据") + private Integer sortBy; + + /** 状态: 0=启用, 1=停用 */ + @Excel(name = "状态") + private String status; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date lastGenerateTime; + + private String createBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date createTime; + + private String updateBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date updateTime; + + @TableField(select = false) + private String delFlag; +} diff --git a/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtRoll.java b/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtRoll.java index 1f80056..4c97ff3 100644 --- a/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtRoll.java +++ b/skins-model/src/main/java/com/ruoyi/domain/entity/roll/TtRoll.java @@ -82,4 +82,7 @@ public class TtRoll implements Serializable { @TableField(select = false) private String delFlag; + // 官方周期Roll配置ID(tt_official_roll_config.id),标记该房由哪条配置生成,手动创建的房间为NULL + private Integer officialConfigId; + } diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtOfficialRollConfigController.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtOfficialRollConfigController.java new file mode 100644 index 0000000..796c49c --- /dev/null +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtOfficialRollConfigController.java @@ -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 list( + @RequestParam(value = "configName", required = false) String configName, + @RequestParam(value = "periodType", required = false) String periodType, + @RequestParam(value = "status", required = false) String status) { + startPage(); + LambdaQueryWrapper 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 configList = configService.list(wrapper); + return getPageData(configList); + } + + @ApiOperation("配置详情") + @GetMapping("/{id}") + public R 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("操作失败"); + } +} diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtRollController.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtRollController.java index 66332f7..955ce9f 100644 --- a/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtRollController.java +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TtRollController.java @@ -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 getCdkList(@PathVariable("rollId") Integer rollId) { + startPage(); List list = rollCdkService.listByRollId(rollId); - return success(list); + return getPageData(list); } /** 删除指定CDK */ diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/mapper/TtOfficialRollConfigMapper.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/mapper/TtOfficialRollConfigMapper.java new file mode 100644 index 0000000..7fe95af --- /dev/null +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/mapper/TtOfficialRollConfigMapper.java @@ -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 { +} diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/TtOfficialRollConfigService.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/TtOfficialRollConfigService.java new file mode 100644 index 0000000..8f11ffc --- /dev/null +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/TtOfficialRollConfigService.java @@ -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 { + + /** 新增配置(含校验) */ + AjaxResult addConfig(TtOfficialRollConfig config); + + /** 编辑配置(含校验) */ + AjaxResult editConfig(TtOfficialRollConfig config); +} diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtOfficialRollConfigServiceImpl.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtOfficialRollConfigServiceImpl.java new file mode 100644 index 0000000..42ce524 --- /dev/null +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtOfficialRollConfigServiceImpl.java @@ -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 + 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"); + } +} diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtUserServiceImpl.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtUserServiceImpl.java index 4210bfa..d22a31f 100644 --- a/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtUserServiceImpl.java +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TtUserServiceImpl.java @@ -132,7 +132,12 @@ public class TtUserServiceImpl extends ServiceImpl 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()); diff --git a/skins-service/service-admin/src/main/java/com/ruoyi/admin/task/OfficialRollTask.java b/skins-service/service-admin/src/main/java/com/ruoyi/admin/task/OfficialRollTask.java new file mode 100644 index 0000000..7b3a27d --- /dev/null +++ b/skins-service/service-admin/src/main/java/com/ruoyi/admin/task/OfficialRollTask.java @@ -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房定时创建任务。 + *

+ * 注册到 RuoYi 系统监控-定时任务(由 sys_job 统一调度,不加 @Scheduled): + * - 任务名称:官方日Roll生成 调用目标:officialRollTask.generateDailyRolls() 默认cron:0 0 10 * * ?(每天10:00) + *

+ * 复用官方房(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 query = Wrappers.lambdaQuery(); + query.eq(TtOfficialRollConfig::getStatus, "0") + .eq(TtOfficialRollConfig::getPeriodType, periodType); + List 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.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().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); + } +} diff --git a/skins-service/service-playingmethod/src/main/java/com/ruoyi/playingmethod/scheduled/fightTask.java b/skins-service/service-playingmethod/src/main/java/com/ruoyi/playingmethod/scheduled/fightTask.java index 174eb4e..cd63990 100644 --- a/skins-service/service-playingmethod/src/main/java/com/ruoyi/playingmethod/scheduled/fightTask.java +++ b/skins-service/service-playingmethod/src/main/java/com/ruoyi/playingmethod/scheduled/fightTask.java @@ -50,7 +50,7 @@ public class fightTask { private Executor customThreadPoolExecutor; // 定时更新超时未结束的对局 - @Scheduled(cron = "0/6 * * * * ?") + //@Scheduled(cron = "0/6 * * * * ?") private void refreshDayTask() { log.info("定时更新超时未结束的对局"); // Timestamp now = new Timestamp(System.currentTimeMillis());