Files
webman-buildadmin/app/admin/controller/game/User.php

592 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\admin\controller\game;
use Throwable;
use app\common\controller\Backend;
use app\common\service\GameChannelUserCount;
use support\think\Db;
use support\Response;
use Webman\Http\Request as WebmanRequest;
/**
* 用户管理
*/
class User extends Backend
{
/**
* GameUser模型对象
* @var object|null
* @phpstan-var \app\common\model\GameUser|null
*/
protected ?object $model = null;
/**
* 数据范围:非超管仅本人 + 下级角色组内管理员(与 auth.Admin 一致,见 Backend::getDataLimitAdminIds parent
*/
protected bool|string|int $dataLimit = 'parent';
/**
* admin_id 由表单选择归属管理员,勿在保存时强制改为当前登录账号
*/
protected bool $dataLimitFieldAutoFill = false;
protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time'];
protected array $withJoinTable = ['gameChannel', 'admin'];
protected string|array $quickSearchField = ['id', 'username', 'phone'];
/**
* 与渠道 Config 类似:从游戏配置拉取默认权重;辅助接口,需具备用户管理列表权限
*/
protected array $noNeedPermission = ['defaultWeightPresets', 'defaultWeightByChannel'];
/** game_weight 分组下的配置名 */
private const GC_GROUP_WEIGHT = 'game_weight';
private const GC_NAME_TIER = 'default_tier_weight';
private const GC_NAME_BIGWIN_PRIMARY = 'default_bigwin_weight';
/** 档位权重固定键T1~T5 */
private const TIER_WEIGHT_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5'];
/** 中大奖权重固定键5~30 */
private const BIGWIN_WEIGHT_KEYS = ['5', '10', '15', '20', '25', '30'];
protected function initController(WebmanRequest $request): ?Response
{
$this->model = new \app\common\model\GameUser();
return null;
}
/**
* 添加重写password 使用 Admin 同款加密uuid 由 username+channel_id 生成)
* @throws Throwable
*/
protected function _add(): Response
{
if ($this->request && $this->request->method() === 'POST') {
$data = $this->request->post();
if (!$data) {
return $this->error(__('Parameter %s can not be empty', ['']));
}
$data = $this->applyInputFilter($data);
$data = $this->excludeFields($data);
$password = $data['password'] ?? null;
if (!is_string($password) || trim($password) === '') {
return $this->error(__('Parameter %s can not be empty', ['password']));
}
$data['password'] = hash_password($password);
$username = $data['username'] ?? '';
$channelId = $data['channel_id'] ?? ($data['game_channel_id'] ?? null);
if (!is_string($username) || trim($username) === '' || $channelId === null || $channelId === '') {
return $this->error(__('Parameter %s can not be empty', ['username/channel_id']));
}
$data['uuid'] = md5(trim($username) . '|' . $channelId);
if ($this->gameUserUsernameExistsInChannel($username, $channelId)) {
return $this->error(__('Game user username exists in channel'));
}
if (!$this->auth->isSuperAdmin()) {
$allowed = $this->getDataLimitAdminIds();
$adminIdNew = $data['admin_id'] ?? null;
if ($adminIdNew === null || $adminIdNew === '') {
return $this->error(__('Parameter %s can not be empty', ['admin_id']));
}
if ($allowed !== [] && !in_array($adminIdNew, $allowed)) {
return $this->error(__('You have no permission'));
}
}
$result = false;
$this->model->startTrans();
try {
if ($this->modelValidate) {
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
if (class_exists($validate)) {
$validate = new $validate();
if ($this->modelSceneValidate) {
$validate->scene('add');
}
$validate->check($data);
}
}
$result = $this->model->save($data);
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($result !== false) {
$cid = $data['game_channel_id'] ?? $data['channel_id'] ?? null;
if (($cid === null || $cid === '') && $this->model) {
$rowData = $this->model->getData();
$cid = $rowData['game_channel_id'] ?? $rowData['channel_id'] ?? null;
}
if ($cid !== null && $cid !== '') {
GameChannelUserCount::syncFromGameUser($cid);
}
return $this->success(__('Added successfully'));
}
return $this->error(__('No rows were added'));
}
return $this->error(__('Parameter error'));
}
/**
* 编辑重写password 使用 Admin 同款加密uuid 由 username+channel_id 生成)
* @throws Throwable
*/
protected function _edit(): Response
{
$pk = $this->model->getPk();
$id = $this->request ? ($this->request->post($pk) ?? $this->request->get($pk)) : null;
$row = $this->model->find($id);
if (!$row) {
return $this->error(__('Record not found'));
}
$dataLimitAdminIds = $this->getDataLimitAdminIds();
if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
return $this->error(__('You have no permission'));
}
$oldChannelId = $row['game_channel_id'] ?? $row['channel_id'] ?? null;
if ($this->request && $this->request->method() === 'POST') {
$data = $this->request->post();
if (!$data) {
return $this->error(__('Parameter %s can not be empty', ['']));
}
$data = $this->applyInputFilter($data);
$data = $this->excludeFields($data);
if (array_key_exists('password', $data)) {
$password = $data['password'];
if (!is_string($password) || trim($password) === '') {
unset($data['password']);
} else {
$data['password'] = hash_password($password);
}
}
$nextUsername = array_key_exists('username', $data) ? $data['username'] : $row['username'];
$nextChannelId = null;
if (array_key_exists('channel_id', $data)) {
$nextChannelId = $data['channel_id'];
} elseif (array_key_exists('game_channel_id', $data)) {
$nextChannelId = $data['game_channel_id'];
} else {
$nextChannelId = $row['channel_id'] ?? $row['game_channel_id'] ?? null;
}
if (is_string($nextUsername) && trim($nextUsername) !== '' && $nextChannelId !== null && $nextChannelId !== '') {
$data['uuid'] = md5(trim($nextUsername) . '|' . $nextChannelId);
if ($this->gameUserUsernameExistsInChannel($nextUsername, $nextChannelId, $row[$pk])) {
return $this->error(__('Game user username exists in channel'));
}
}
if (!$this->auth->isSuperAdmin()) {
$allowed = $this->getDataLimitAdminIds();
$adminIdAfter = array_key_exists('admin_id', $data) ? $data['admin_id'] : ($row['admin_id'] ?? null);
if ($allowed !== [] && $adminIdAfter !== null && $adminIdAfter !== '' && !in_array($adminIdAfter, $allowed)) {
return $this->error(__('You have no permission'));
}
}
$result = false;
$this->model->startTrans();
try {
if ($this->modelValidate) {
$validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
if (class_exists($validate)) {
$validate = new $validate();
if ($this->modelSceneValidate) {
$validate->scene('edit');
}
$data[$pk] = $row[$pk];
$validate->check($data);
}
}
$result = $row->save($data);
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($result !== false) {
$merged = array_merge($row->toArray(), $data);
$newChannelId = $merged['game_channel_id'] ?? $merged['channel_id'] ?? null;
if ($newChannelId !== null && $newChannelId !== '') {
GameChannelUserCount::syncFromGameUser($newChannelId);
}
if ($oldChannelId !== null && $oldChannelId !== '' && (string) $oldChannelId !== (string) $newChannelId) {
GameChannelUserCount::syncFromGameUser($oldChannelId);
}
return $this->success(__('Update successful'));
}
return $this->error(__('No rows updated'));
}
// GET: 返回编辑数据时,剔除敏感字段
unset($row['password'], $row['salt'], $row['token'], $row['refresh_token']);
return $this->success('', [
'row' => $row
]);
}
/**
* 删除后按 game_user 重算相关渠道的 user_count
*
* @throws Throwable
*/
protected function _del(): Response
{
$where = [];
$dataLimitAdminIds = $this->getDataLimitAdminIds();
if ($dataLimitAdminIds) {
$where[] = [$this->dataLimitField, 'in', $dataLimitAdminIds];
}
$ids = $this->request ? ($this->request->post('ids') ?? $this->request->get('ids') ?? []) : [];
$ids = is_array($ids) ? $ids : [];
$where[] = [$this->model->getPk(), 'in', $ids];
$data = $this->model->where($where)->select();
$channelIdsToSync = [];
foreach ($data as $v) {
$cid = $v['game_channel_id'] ?? $v['channel_id'] ?? null;
if ($cid !== null && $cid !== '') {
$channelIdsToSync[] = $cid;
}
}
$count = 0;
$this->model->startTrans();
try {
foreach ($data as $v) {
$count += $v->delete();
}
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($count) {
GameChannelUserCount::syncChannels($channelIdsToSync);
return $this->success(__('Deleted successfully'));
}
return $this->error(__('No rows were deleted'));
}
/**
* 查看
* @throws Throwable
*/
protected function _index(): Response
{
// 如果是 select 则转发到 select 方法,若未重写该方法,其实还是继续执行 index
if ($this->request && $this->request->get('select')) {
return $this->select($this->request);
}
/**
* 1. withJoin 不可使用 alias 方法设置表别名,别名将自动使用关联模型名称(小写下划线命名规则)
* 2. 以下的别名设置了主表别名,同时便于拼接查询参数等
* 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理
*/
list($where, $alias, $limit, $order) = $this->queryBuilder();
$res = $this->model
->withJoin($this->withJoinTable, $this->withJoinType)
->with($this->withJoinTable)
->visible(['gameChannel' => ['name'], 'admin' => ['username']])
->alias($alias)
->where($where)
->order($order)
->paginate($limit);
return $this->success('', [
'list' => $res->items(),
'total' => $res->total(),
'remark' => get_route_remark(),
]);
}
/**
* 新建用户时:超管可选各渠道 default_tier_weight / default_bigwin_weight大奖仅 default_bigwin_weightdefault_kill_score_weight 为 T1T5 击杀分档位,不作大奖回退)
*
* @throws Throwable
*/
public function defaultWeightPresets(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
if (!$this->auth->check('game/user/index')) {
return $this->error(__('You have no permission'));
}
$allowed = $this->getAllowedChannelIdsForGameConfig();
$channelQuery = Db::name('game_channel')->field(['id', 'name'])->order('id', 'asc');
if ($allowed !== null) {
$channelQuery->where('id', 'in', $allowed);
}
$channels = $channelQuery->select()->toArray();
$tierOut = [];
$bigwinOut = [];
/** 超管:首条为 game_config.channel_id=0 的全局默认权重 */
if ($this->auth->isSuperAdmin()) {
$gp = $this->fetchDefaultWeightsForChannelId(0);
$gname = __('Global default');
$tierOut[] = [
'channel_id' => 0,
'channel_name' => $gname,
'value' => $gp['tier_weight'],
];
$bigwinOut[] = [
'channel_id' => 0,
'channel_name' => $gname,
'value' => $gp['bigwin_weight'],
];
}
if ($channels === []) {
return $this->success('', [
'tier' => $tierOut,
'bigwin' => $bigwinOut,
]);
}
$channelIds = array_column($channels, 'id');
$nameList = [self::GC_NAME_TIER, self::GC_NAME_BIGWIN_PRIMARY];
$rows = Db::name('game_config')
->where('group', self::GC_GROUP_WEIGHT)
->where('name', 'in', $nameList)
->where('channel_id', 'in', $channelIds)
->field(['channel_id', 'name', 'value'])
->select()
->toArray();
$map = [];
foreach ($rows as $row) {
$cid = $row['channel_id'];
if (!isset($map[$cid])) {
$map[$cid] = [];
}
$map[$cid][$row['name']] = $row['value'];
}
foreach ($channels as $ch) {
$cid = $ch['id'];
$cname = $ch['name'] ?? '';
$names = $map[$cid] ?? [];
$tierVal = $names[self::GC_NAME_TIER] ?? null;
$tierOut[] = [
'channel_id' => $cid,
'channel_name' => $cname,
'value' => ($tierVal !== null && $tierVal !== '') ? trim((string) $tierVal) : '[]',
];
$bigPrimary = $names[self::GC_NAME_BIGWIN_PRIMARY] ?? null;
$bigVal = ($bigPrimary !== null && $bigPrimary !== '') ? trim((string) $bigPrimary) : null;
$bigwinOut[] = [
'channel_id' => $cid,
'channel_name' => $cname,
'value' => $bigVal !== null ? $bigVal : '[]',
];
}
return $this->success('', [
'tier' => $tierOut,
'bigwin' => $bigwinOut,
]);
}
/**
* 按渠道取默认档位/大奖权重(非超管仅可访问权限内渠道)
*
* @throws Throwable
*/
public function defaultWeightByChannel(WebmanRequest $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
if (!$this->auth->check('game/user/index')) {
return $this->error(__('You have no permission'));
}
$channelId = $request->get('channel_id', $request->post('channel_id'));
if ($channelId === null || $channelId === '') {
return $this->error(__('Parameter error'));
}
$cid = (int) $channelId;
if ($cid === 0) {
if (!$this->auth->isSuperAdmin()) {
return $this->error(__('You have no permission'));
}
$pair = $this->fetchDefaultWeightsForChannelId(0);
return $this->success('', [
'tier_weight' => $pair['tier_weight'],
'bigwin_weight' => $pair['bigwin_weight'],
]);
}
if ($cid < 1 || !$this->canAccessChannelGameConfig($cid)) {
return $this->error(__('You have no permission'));
}
$pair = $this->fetchDefaultWeightsForChannelId($cid);
return $this->success('', [
'tier_weight' => $pair['tier_weight'],
'bigwin_weight' => $pair['bigwin_weight'],
]);
}
/**
* @return list<int|string>|null null 表示超管不限制渠道
*/
private function getAllowedChannelIdsForGameConfig(): ?array
{
if ($this->auth->isSuperAdmin()) {
return null;
}
$adminIds = parent::getDataLimitAdminIds();
if ($adminIds === []) {
return [-1];
}
$channelIds = Db::name('game_channel')->where('admin_id', 'in', $adminIds)->column('id');
if ($channelIds === []) {
return [-1];
}
return array_values(array_unique($channelIds));
}
private function canAccessChannelGameConfig(int $channelId): bool
{
$exists = Db::name('game_channel')->where('id', $channelId)->count();
if ($exists < 1) {
return false;
}
$allowed = $this->getAllowedChannelIdsForGameConfig();
if ($allowed === null) {
return true;
}
return in_array($channelId, $allowed, false);
}
/**
* @return array{tier_weight: string, bigwin_weight: string}
*/
private function fetchDefaultWeightsForChannelId(int $channelId): array
{
$rows = Db::name('game_config')
->where('channel_id', $channelId)
->where('group', self::GC_GROUP_WEIGHT)
->where('name', 'in', [self::GC_NAME_TIER, self::GC_NAME_BIGWIN_PRIMARY])
->column('value', 'name');
$tier = $rows[self::GC_NAME_TIER] ?? null;
$tierStr = ($tier !== null && $tier !== '') ? trim((string) $tier) : '[]';
$bigPrimary = $rows[self::GC_NAME_BIGWIN_PRIMARY] ?? null;
$bigStr = '[]';
if ($bigPrimary !== null && $bigPrimary !== '') {
$bigStr = trim((string) $bigPrimary);
}
// 适配导入:强制返回固定键的 JSON缺失键补空字符串
$tierStr = $this->normalizeWeightJsonToFixedKeys($tierStr, self::TIER_WEIGHT_KEYS);
$bigStr = $this->normalizeWeightJsonToFixedKeys($bigStr, self::BIGWIN_WEIGHT_KEYS);
return [
'tier_weight' => $tierStr,
'bigwin_weight' => $bigStr,
];
}
/**
* 将 JSON 权重(数组形式:[{key:value}, ...])归一化到固定键顺序。
* 缺失键补空字符串;解析失败则返回固定键且值为空。
*/
private function normalizeWeightJsonToFixedKeys(string $raw, array $fixedKeys): string
{
$raw = trim($raw);
if ($raw === '' || $raw === '[]') {
$empty = [];
foreach ($fixedKeys as $k) {
$empty[] = [$k => ''];
}
return json_encode($empty, JSON_UNESCAPED_UNICODE);
}
$decoded = json_decode($raw, true);
if (!is_array($decoded)) {
$empty = [];
foreach ($fixedKeys as $k) {
$empty[] = [$k => ''];
}
return json_encode($empty, JSON_UNESCAPED_UNICODE);
}
$map = [];
foreach ($decoded as $item) {
if (!is_array($item)) {
continue;
}
foreach ($item as $k => $v) {
if (!is_string($k) && !is_int($k)) {
continue;
}
$map[strval($k)] = $v === null ? '' : strval($v);
}
}
$pairs = [];
foreach ($fixedKeys as $k) {
$pairs[] = [$k => $map[$k] ?? ''];
}
return json_encode($pairs, JSON_UNESCAPED_UNICODE);
}
/**
* 当前渠道game_channel_id下是否已存在该用户名编辑时排除当前记录主键
*/
private function gameUserUsernameExistsInChannel(string $username, int|string $channelId, string|int|null $excludePk = null): bool
{
$name = trim($username);
$cid = (int) $channelId;
$query = Db::name('game_user')
->where('game_channel_id', $cid)
->where('username', $name);
if ($excludePk !== null && $excludePk !== '') {
$query->where('id', '<>', $excludePk);
}
return $query->count() > 0;
}
/**
* 若需重写查看、编辑、删除等方法,请复制 @see \app\admin\library\traits\Backend 中对应的方法至此进行重写
*/
}