提交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
+67
View File
@@ -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 角色,请在"角色管理"里为角色勾选以上两个菜单权限。
-- ============================================================
+50 -3
View File
@@ -225,11 +225,12 @@ export function generateRollCdk(data) {
}); });
} }
/** 查询Roll房CDK列表 */ /** 查询Roll房CDK列表(分页) */
export function getRollCdkList(rollId) { export function getRollCdkList(rollId, query) {
return request({ return request({
url: `/admin/roll/cdk/list/${rollId}`, url: `/admin/roll/cdk/list/${rollId}`,
method: 'get' method: 'get',
params: query
}); });
} }
@@ -240,3 +241,49 @@ export function deleteRollCdk(cdkId) {
method: 'delete' 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
});
}
@@ -0,0 +1,209 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="用户ID" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input
v-model="queryParams.nickName"
placeholder="请输入机器人昵称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phoneNumber">
<el-input
v-model="queryParams.phoneNumber"
placeholder="请输入手机号码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
icon="el-icon-circle-plus-outline"
type="primary"
plain
size="mini"
@click="addRobot = true"
>生成机器人</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="ID" align="center" prop="userId" />
<el-table-column label="机器人昵称" align="center" prop="nickName" />
<el-table-column label="头像" align="center" prop="avatar">
<template slot-scope="scope">
<image-preview :src="scope.row.avatar" :width="50" :height="50" />
</template>
</el-table-column>
<el-table-column label="手机号码" align="center" prop="phoneNumber" />
<el-table-column label="账户金额" align="center" prop="accountAmount" />
<el-table-column label="VIP等级" align="center" prop="vipLevel">
<template slot-scope="scope">VIP{{ scope.row.vipLevel }}</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 生成机器人 -->
<el-dialog title="生成机器人" :visible.sync="addRobot" width="600px">
<el-form>
<el-form-item label="请选择数量:" style="margin-bottom: 2px;">
<el-input-number size="mini" v-model="newNumber" :min="1" :max="10" label="描述文字"></el-input-number>
</el-form-item>
<div style="font-size:14px;color:#797979;">
<i class="el-icon-info"></i> 要生成的机器人数量每次最多10个
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addRobot = false"> </el-button>
<el-button type="primary" @click="generateRobot"> </el-button>
</div>
</el-dialog>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listUser, delUser, generateRobot } from "@/api/skins/ttuser/api";
export default {
name: "RobotUser",
data() {
return {
loading: true,
showSearch: true,
// 选中数组
ids: [],
// 非多个禁用
multiple: true,
total: 0,
userList: [],
addRobot: false,
newNumber: 1,
queryParams: {
userId: null,
nickName: null,
phoneNumber: null,
// 固定只查机器人(userType=03
userType: "03",
pageNum: 1,
pageSize: 10
}
};
},
created() {
this.getList();
},
methods: {
/** 查询机器人列表 */
getList() {
this.loading = true;
this.queryParams.userType = "03";
listUser(this.queryParams).then(response => {
this.userList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.queryParams = {
userId: null,
nickName: null,
phoneNumber: null,
userType: "03",
pageNum: 1,
pageSize: 10
};
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.userId);
this.multiple = !selection.length;
},
/** 生成机器人 */
generateRobot() {
generateRobot(this.newNumber).then(res => {
this.getList();
(this.newNumber = 1), (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);
});
},
/** 删除机器人 */
handleDelete(row) {
const userIds = row.userId || this.ids;
this.$modal
.confirm('是否确认删除机器人编号为"' + userIds + '"的数据项?')
.then(() => {
return delUser(userIds);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}
}
};
</script>
+18 -3
View File
@@ -318,6 +318,14 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination
v-show="cdkTotal > 0"
:total="cdkTotal"
:page.sync="cdkQueryParams.pageNum"
:limit.sync="cdkQueryParams.pageSize"
@pagination="loadCdkList"
small
/>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="cdkDialogVisible = false"> </el-button> <el-button @click="cdkDialogVisible = false"> </el-button>
</div> </div>
@@ -401,7 +409,12 @@ export default {
cdkList: [], cdkList: [],
cdkLoading: false, cdkLoading: false,
cdkGenCount: 10, cdkGenCount: 10,
currentCdkRollId: null currentCdkRollId: null,
cdkTotal: 0,
cdkQueryParams: {
pageNum: 1,
pageSize: 10
}
}; };
}, },
mounted() { mounted() {
@@ -577,14 +590,16 @@ export default {
// 打开CDK管理弹窗 // 打开CDK管理弹窗
handleCdk(row) { handleCdk(row) {
this.currentCdkRollId = row.id; this.currentCdkRollId = row.id;
this.cdkQueryParams.pageNum = 1;
this.cdkDialogVisible = true; this.cdkDialogVisible = true;
this.loadCdkList(); this.loadCdkList();
}, },
// 加载CDK列表 // 加载CDK列表
loadCdkList() { loadCdkList() {
this.cdkLoading = true; this.cdkLoading = true;
getRollCdkList(this.currentCdkRollId).then(res => { getRollCdkList(this.currentCdkRollId, this.cdkQueryParams).then(res => {
this.cdkList = res.data || []; this.cdkList = res.rows || [];
this.cdkTotal = res.total || 0;
this.cdkLoading = false; this.cdkLoading = false;
}); });
}, },
@@ -0,0 +1,279 @@
<template>
<div class="home">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item label="配置名称" prop="configName">
<el-input
v-model="queryParams.configName"
placeholder="请输入配置名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="启用" value="0"></el-option>
<el-option label="停用" value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-plus"
size="mini"
@click="handleCreat"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table :data="tableData" style="width: 100%" v-loading="loading">
<el-table-column align="center" prop="id" label="ID" width="60"></el-table-column>
<el-table-column align="center" prop="configName" label="配置名称"></el-table-column>
<el-table-column align="center" prop="jackpotId" label="奖池" width="140">
<template slot-scope="scope">
{{ jackpotName(scope.row.jackpotId) }}
</template>
</el-table-column>
<el-table-column align="center" prop="roomName" label="房间名"></el-table-column>
<el-table-column align="center" prop="robotNum" label="机器人数量" width="100"></el-table-column>
<el-table-column align="center" prop="cdkCount" label="CDK数量" width="90"></el-table-column>
<el-table-column align="center" prop="peopleNum" label="人数上限" width="90"></el-table-column>
<el-table-column align="center" prop="rollPassword" label="密码" width="80"></el-table-column>
<el-table-column align="center" prop="status" label="状态" width="90">
<template slot-scope="scope">
<el-switch
:value="scope.row.status === '0'"
active-color="#13ce66"
inactive-color="#ff4949"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column align="center" prop="lastGenerateTime" label="上次生成时间" width="160"></el-table-column>
<el-table-column align="center" label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleChange(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="35%">
<el-form :model="form" :rules="rules" label-width="120px" ref="ruleForm">
<el-form-item label="配置名称" prop="configName">
<el-input v-model="form.configName" autocomplete="off" placeholder="请输入配置名称(内部标识)"></el-input>
</el-form-item>
<el-form-item label="奖池" prop="jackpotId">
<el-select v-model="form.jackpotId" filterable placeholder="请选择奖池" clearable>
<el-option
v-for="item in JackpotList"
:key="item.jackpotId"
:label="item.jackpotName"
:value="item.jackpotId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="房间名" prop="roomName">
<el-input v-model="form.roomName" autocomplete="off" placeholder="请输入生成房间的名称"></el-input>
</el-form-item>
<el-form-item label="房间描述" prop="description">
<el-input type="textarea" v-model="form.description" placeholder="请输入房间描述"></el-input>
</el-form-item>
<el-form-item label="机器人数量" prop="robotNum">
<el-input-number v-model="form.robotNum" :min="0" :max="100000" label="机器人数量"></el-input-number>
<span style="font-size: 12px;">
<i class="el-icon-info"></i> 每天自动创建房间后加入的机器人数量为0则不加机器人
</span>
</el-form-item>
<el-form-item label="人数上限" prop="peopleNum">
<el-input-number v-model="form.peopleNum" :min="1" :max="100000" label="人数上限"></el-input-number>
</el-form-item>
<el-form-item label="房间密码" prop="rollPassword">
<el-input v-model="form.rollPassword" autocomplete="off" placeholder="请输入房间密码"></el-input>
<span style="font-size: 12px;">
<i class="el-icon-info"></i> 为空表示无密码
</span>
</el-form-item>
<el-form-item label="CDK数量" prop="cdkCount">
<el-input-number v-model="form.cdkCount" :min="0" :max="500" label="CDK数量"></el-input-number>
<span style="font-size: 12px;">
<i class="el-icon-info"></i> 填0则不启用CDK模式使用普通密码
</span>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="0">启用</el-radio>
<el-radio label="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="submitForm('ruleForm')"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
officialRollConfigList,
officialRollConfigAdd,
officialRollConfigChange,
officialRollConfigDel,
officialRollConfigChangeStatus,
rollJackpotList
} from "@/api/skins/ttRoll/api";
export default {
name: "OfficialRollConfig",
data() {
return {
total: 0,
loading: false,
showSearch: true,
dialogFormVisible: false,
title: "",
isEdit: false,
tableData: [],
JackpotList: [],
queryParams: {
configName: null,
periodType: null,
status: null,
pageNum: 1,
pageSize: 10
},
form: this.emptyForm(),
rules: {
configName: [{ required: true, message: "请输入配置名称", trigger: "blur" }],
jackpotId: [{ required: true, message: "请选择奖池", trigger: "change" }],
roomName: [{ required: true, message: "请输入房间名", trigger: "blur" }],
peopleNum: [{ required: true, message: "请输入人数上限", trigger: "blur" }]
}
};
},
mounted() {
this.getList();
this.loadJackpot();
},
methods: {
emptyForm() {
return {
id: null,
configName: null,
periodType: "DAILY",
jackpotId: null,
roomName: null,
description: null,
minRecharge: 0,
robotNum: 0,
cdkCount: 0,
peopleNum: 100,
rollPassword: null,
sortBy: 0,
status: "0"
};
},
jackpotName(jackpotId) {
const j = this.JackpotList.find(item => item.jackpotId == jackpotId);
return j ? j.jackpotName : jackpotId;
},
loadJackpot() {
rollJackpotList({ pageNum: 1, pageSize: 9999 }).then(res => {
this.JackpotList = res.rows || [];
});
},
getList() {
this.loading = true;
officialRollConfigList(this.queryParams).then(res => {
this.tableData = res.rows || [];
this.total = res.total || 0;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.queryParams = {
configName: null,
periodType: null,
status: null,
pageNum: 1,
pageSize: 10
};
this.handleQuery();
},
handleCreat() {
this.form = this.emptyForm();
this.isEdit = false;
this.title = "新增官方周期Roll配置";
this.dialogFormVisible = true;
},
handleChange(row) {
this.form = Object.assign(this.emptyForm(), row);
this.isEdit = true;
this.title = "编辑官方周期Roll配置";
this.dialogFormVisible = true;
},
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (!valid) return false;
const api = this.isEdit ? officialRollConfigChange : officialRollConfigAdd;
api(this.form).then(() => {
this.$modal.msgSuccess(this.isEdit ? "编辑成功" : "新增成功");
this.dialogFormVisible = false;
this.getList();
});
});
},
handleStatusChange(row) {
const newStatus = row.status === "0" ? "1" : "0";
const text = newStatus === "0" ? "启用" : "停用";
this.$modal
.confirm(`确认要${text}配置"${row.configName}"吗?`)
.then(() => {
return officialRollConfigChangeStatus({ id: row.id, status: newStatus });
})
.then(() => {
row.status = newStatus;
this.$modal.msgSuccess(text + "成功");
})
.catch(() => {});
},
handleDelete(id) {
this.$modal
.confirm('是否确认删除ID为"' + id + '"的配置?')
.then(() => {
return officialRollConfigDel(id);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}
}
};
</script>
<style scoped lang="scss">
.home {
padding: 20px;
}
</style>
-41
View File
@@ -71,14 +71,6 @@
v-hasPermi="['admin:user:export']" v-hasPermi="['admin:user:export']"
>导出</el-button> >导出</el-button>
</el-col> </el-col>
<el-col :span="1.5">
<el-button
icon="el-icon-circle-plus-outline"
type="primary"
size="mini"
@click="addRobot = true"
>生成机器人</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
@@ -114,21 +106,6 @@
<el-button type="primary" @click="generateAccount"> </el-button> <el-button type="primary" @click="generateAccount"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<!-- 生成机器人 -->
<el-dialog title="生成机器人" :visible.sync="addRobot" width="600px">
<el-form>
<el-form-item label="请选择数量:" style="margin-bottom: 2px;">
<el-input-number size="mini" v-model="newNumber" :min="1" :max="10" label="描述文字"></el-input-number>
</el-form-item>
<div style="font-size:14px;color:#797979;">
<i class="el-icon-info"></i> 要生成的账号数量
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addRobot = false"> </el-button>
<el-button type="primary" @click="generateRobot"> </el-button>
</div>
</el-dialog>
<pagination <pagination
v-show="total > 0" v-show="total > 0"
@@ -284,7 +261,6 @@ import {
delUser, delUser,
updateUser, updateUser,
generateAccount, generateAccount,
generateRobot,
getUserProfitStatistics, getUserProfitStatistics,
resetUserBalance, resetUserBalance,
forceDeleteUser forceDeleteUser
@@ -300,7 +276,6 @@ export default {
openView: false, openView: false,
newNumber: 1, newNumber: 1,
addNewUser: false, addNewUser: false,
addRobot: false,
// 遮罩层 // 遮罩层
loading: true, loading: true,
// 选中数组 // 选中数组
@@ -467,22 +442,6 @@ export default {
URL.revokeObjectURL(objectURL); URL.revokeObjectURL(objectURL);
}); });
}, },
/* 生成账号 */
generateRobot() {
generateRobot(this.newNumber).then(res => {
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() { getList() {
this.loading = true; this.loading = true;
@@ -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;
}
@@ -82,4 +82,7 @@ public class TtRoll implements Serializable {
@TableField(select = false) @TableField(select = false)
private String delFlag; private String delFlag;
// 官方周期Roll配置IDtt_official_roll_config.id),标记该房由哪条配置生成,手动创建的房间为NULL
private Integer officialConfigId;
} }
@@ -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("生成成功"); return success("生成成功");
} }
/** 查看Roll房所有CDK列表 */ /** 查看Roll房所有CDK列表(分页) */
@ApiOperation("查看Roll房CDK列表") @ApiOperation("查看Roll房CDK列表")
@GetMapping("/cdk/list/{rollId}") @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); List<TtRollCdk> list = rollCdkService.listByRollId(rollId);
return success(list); return getPageData(list);
} }
/** 删除指定CDK */ /** 删除指定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()); wrapper.like(TtUser::getUserName, ttUserBody.getUserName());
if (StringUtils.isNotNull(ttUserBody.getNickName())) if (StringUtils.isNotNull(ttUserBody.getNickName()))
wrapper.like(TtUser::getNickName, 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())) if (StringUtils.isNotEmpty(ttUserBody.getPhoneNumber()))
wrapper.likeRight(TtUser::getPhoneNumber, ttUserBody.getPhoneNumber()); wrapper.likeRight(TtUser::getPhoneNumber, ttUserBody.getPhoneNumber());
if (StringUtils.isNotEmpty(ttUserBody.getStatus())) wrapper.eq(TtUser::getStatus, ttUserBody.getStatus()); 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);
}
}
@@ -50,7 +50,7 @@ public class fightTask {
private Executor customThreadPoolExecutor; private Executor customThreadPoolExecutor;
// 定时更新超时未结束的对局 // 定时更新超时未结束的对局
@Scheduled(cron = "0/6 * * * * ?") //@Scheduled(cron = "0/6 * * * * ?")
private void refreshDayTask() { private void refreshDayTask() {
log.info("定时更新超时未结束的对局"); log.info("定时更新超时未结束的对局");
// Timestamp now = new Timestamp(System.currentTimeMillis()); // Timestamp now = new Timestamp(System.currentTimeMillis());