1.修改电话号码格式为60前缀,马来西亚格式
2.优化渠道可以查看分红方式,可以查看游玩详情
This commit is contained in:
@@ -4,6 +4,7 @@ namespace app\admin\controller;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\service\AdminChannelScopeService;
|
||||
use app\common\service\ChannelSettlementService;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
@@ -17,7 +18,16 @@ class Channel extends Backend
|
||||
/**
|
||||
* 预览接口与手动结算共用「手动结算」按钮权限(避免额外菜单节点)
|
||||
*/
|
||||
protected array $noNeedPermission = ['manualSettlePreview', 'channelAdminShareList', 'saveChannelAdminShare', 'batchSettlePending', 'settleStats'];
|
||||
protected array $noNeedPermission = [
|
||||
'manualSettlePreview',
|
||||
'channelAdminShareList',
|
||||
'saveChannelAdminShare',
|
||||
'batchSettlePending',
|
||||
'settleStats',
|
||||
'dividendRecordList',
|
||||
'directBetRecordList',
|
||||
'settlementBetRecordList',
|
||||
];
|
||||
|
||||
/**
|
||||
* Channel模型对象
|
||||
@@ -34,15 +44,40 @@ class Channel extends Backend
|
||||
|
||||
protected bool $modelSceneValidate = true;
|
||||
|
||||
private array $currentChannelIds = [];
|
||||
/** @var array<int, int> 当前管理员绑定的渠道(写操作范围) */
|
||||
private array $ownChannelIds = [];
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\Channel();
|
||||
$this->currentChannelIds = $this->getCurrentChannelIds();
|
||||
$this->ownChannelIds = $this->resolveOwnChannelIds();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表/统计按可读渠道收窄;null 表示不限制
|
||||
*
|
||||
* @param array<int, array<int|string, mixed>> $where
|
||||
*/
|
||||
private function appendReadableChannelWhere(array &$where, array $alias, string $tableKey = 'channel'): void
|
||||
{
|
||||
$scope = $this->readableChannelIds();
|
||||
if ($scope !== null) {
|
||||
$where[] = [$alias[$tableKey] . '.id', 'in', $scope];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \think\db\BaseQuery|\think\db\Query $query
|
||||
*/
|
||||
private function applyReadableChannelScope($query, string $column): void
|
||||
{
|
||||
$scope = $this->readableChannelIds();
|
||||
if ($scope !== null) {
|
||||
$query->where($column, 'in', $scope);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道-管理员树(父级=渠道,子级=管理员,仅可选择子级)
|
||||
*/
|
||||
@@ -54,9 +89,7 @@ class Channel extends Backend
|
||||
$query = Db::name('channel')
|
||||
->field(['id', 'name'])
|
||||
->order('id', 'asc');
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$query = $query->where('id', 'in', $this->currentChannelIds ?: [0]);
|
||||
}
|
||||
$this->applyReadableChannelScope($query, 'id');
|
||||
$channels = $query->select()->toArray();
|
||||
|
||||
$tree = [];
|
||||
@@ -161,7 +194,7 @@ class Channel extends Backend
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin() && !in_array($row['id'], $this->currentChannelIds, true)) {
|
||||
if (!$this->assertChannelVisible((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
@@ -171,6 +204,9 @@ class Channel extends Backend
|
||||
}
|
||||
|
||||
if ($this->request && $this->request->method() === 'POST') {
|
||||
if (!$this->assertChannelWritable((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
$data = $this->request->post();
|
||||
if (!$data) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
@@ -220,6 +256,21 @@ class Channel extends Backend
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除:仅允许删除本人绑定渠道(查看所有渠道不扩大写权限)
|
||||
*/
|
||||
protected function _del(): Response
|
||||
{
|
||||
$ids = $this->request ? ($this->request->post('ids') ?? $this->request->get('ids') ?? []) : [];
|
||||
$ids = is_array($ids) ? $ids : [];
|
||||
foreach ($ids as $id) {
|
||||
if (!$this->assertChannelWritable((int) $id)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
}
|
||||
return parent::_del();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @throws Throwable
|
||||
@@ -231,9 +282,7 @@ class Channel extends Backend
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$where[] = [$alias['channel'] . '.id', 'in', $this->currentChannelIds ?: [0]];
|
||||
}
|
||||
$this->appendReadableChannelWhere($where, $alias);
|
||||
$res = $this->model
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
@@ -269,12 +318,18 @@ class Channel extends Backend
|
||||
->where('status', 2)
|
||||
->group('channel_id')
|
||||
->column('SUM(total_amount - win_amount - jackpot_extra_amount) AS p', 'channel_id');
|
||||
$directBetMap = Db::name('game_play_record')
|
||||
->where('channel_id', 'in', $channelIds)
|
||||
->group('channel_id')
|
||||
->column('SUM(total_amount) AS s', 'channel_id');
|
||||
foreach ($items as $k => $item) {
|
||||
$cid = intval($item['id'] ?? 0);
|
||||
if ($cid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$items[$k]['user_count'] = intval($userCountMap[$cid] ?? 0);
|
||||
$directBet = strval($directBetMap[$cid] ?? '0.00');
|
||||
$items[$k]['direct_bet_amount'] = bcadd($directBet, '0', 2);
|
||||
$profit = strval($profitMap[$cid] ?? '0.00');
|
||||
$items[$k]['profit_amount'] = bcadd($profit, '0', 2);
|
||||
if (!isset($items[$k]['total_profit_amount']) || $items[$k]['total_profit_amount'] === null || $items[$k]['total_profit_amount'] === '') {
|
||||
@@ -302,7 +357,7 @@ class Channel extends Backend
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->auth->check('channel/manualSettle')) {
|
||||
if (!$this->canManualSettle()) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
@@ -314,7 +369,7 @@ class Channel extends Backend
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) {
|
||||
if (!$this->assertChannelVisible((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
@@ -346,7 +401,7 @@ class Channel extends Backend
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) {
|
||||
if (!$this->assertChannelVisible((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
@@ -537,7 +592,7 @@ class Channel extends Backend
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) {
|
||||
if (!$this->assertChannelWritable((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
$rowsRaw = $request->post('list', []);
|
||||
@@ -613,8 +668,8 @@ class Channel extends Backend
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
return $this->error(__('Only super admin can settle; after settlement, commissions will be automatically paid to admin wallets'));
|
||||
if (!$this->canManualSettle()) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
$id = (int) ($request->post('id', $request->get('id', 0)));
|
||||
@@ -625,7 +680,7 @@ class Channel extends Backend
|
||||
if (!$row) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) {
|
||||
if (!$this->assertChannelVisible((int) $row['id'])) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
@@ -635,7 +690,7 @@ class Channel extends Backend
|
||||
if (($res['ok'] ?? false) !== true) {
|
||||
return $this->error((string) ($res['msg'] ?? __('Settlement failed')));
|
||||
}
|
||||
return $this->success(__('Super admin settlement completed; paid automatically by share ratios'));
|
||||
return $this->success(__('Settlement completed; commissions paid automatically by share ratios'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -665,9 +720,7 @@ class Channel extends Backend
|
||||
return $response;
|
||||
}
|
||||
$query = Db::name('channel');
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
$query->where('id', 'in', $this->currentChannelIds ?: [0]);
|
||||
}
|
||||
$this->applyReadableChannelScope($query, 'id');
|
||||
$rows = $query->field(['id', 'status', 'carryover_balance'])->select()->toArray();
|
||||
$total = count($rows);
|
||||
$enabled = 0;
|
||||
@@ -675,7 +728,12 @@ class Channel extends Backend
|
||||
$carryoverPositiveCount = 0;
|
||||
$carryoverTotal = '0.00';
|
||||
$carryoverPositiveTotal = '0.00';
|
||||
$channelIdList = [];
|
||||
foreach ($rows as $row) {
|
||||
$cid = intval($row['id'] ?? 0);
|
||||
if ($cid > 0) {
|
||||
$channelIdList[] = $cid;
|
||||
}
|
||||
$status = intval($row['status'] ?? 0);
|
||||
if ($status === 1) {
|
||||
$enabled++;
|
||||
@@ -689,6 +747,15 @@ class Channel extends Backend
|
||||
$carryoverPositiveTotal = bcadd($carryoverPositiveTotal, $carry, 2);
|
||||
}
|
||||
}
|
||||
$paidDividendTotal = '0.00';
|
||||
if ($channelIdList !== []) {
|
||||
$paidRow = Db::name('agent_commission_record')
|
||||
->where('channel_id', 'in', $channelIdList)
|
||||
->where('status', 1)
|
||||
->field('SUM(commission_amount) AS s')
|
||||
->find();
|
||||
$paidDividendTotal = bcadd(strval(is_array($paidRow) ? ($paidRow['s'] ?? '0') : '0'), '0', 2);
|
||||
}
|
||||
return $this->success('', [
|
||||
'channel_total' => $total,
|
||||
'enabled_count' => $enabled,
|
||||
@@ -696,9 +763,418 @@ class Channel extends Backend
|
||||
'carryover_positive_count' => $carryoverPositiveCount,
|
||||
'carryover_total' => $carryoverTotal,
|
||||
'carryover_positive_total' => $carryoverPositiveTotal,
|
||||
'paid_dividend_total' => $paidDividendTotal,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 已分红记录列表(顶部统计卡片点击)
|
||||
*/
|
||||
public function dividendRecordList(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->auth->check('channel/viewDividendRecords')) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
[$page, $limit] = $this->parseListPageParams($request);
|
||||
$channelId = (int) ($request->get('channel_id', 0));
|
||||
|
||||
$query = Db::name('agent_commission_record')->alias('acr')
|
||||
->leftJoin('agent_settlement_period asp', 'acr.settlement_period_id = asp.id')
|
||||
->leftJoin('channel c', 'acr.channel_id = c.id')
|
||||
->leftJoin('admin a', 'acr.admin_id = a.id')
|
||||
->where('acr.status', 1);
|
||||
$this->applyReadableChannelScope($query, 'acr.channel_id');
|
||||
if ($channelId > 0) {
|
||||
if (!$this->assertChannelAccessible($channelId)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
$query->where('acr.channel_id', $channelId);
|
||||
}
|
||||
|
||||
$total = (int) $query->count('acr.id');
|
||||
$list = $query
|
||||
->field([
|
||||
'acr.id',
|
||||
'acr.channel_id',
|
||||
'acr.admin_id',
|
||||
'acr.commission_amount',
|
||||
'acr.settled_at',
|
||||
'acr.remark',
|
||||
'asp.settlement_no',
|
||||
'asp.period_start_at',
|
||||
'asp.period_end_at',
|
||||
'c.name as channel_name',
|
||||
'a.username as admin_username',
|
||||
])
|
||||
->order('acr.settled_at', 'desc')
|
||||
->order('acr.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($list as $idx => $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$startTs = intval($row['period_start_at'] ?? 0);
|
||||
$endTs = intval($row['period_end_at'] ?? 0);
|
||||
$list[$idx]['period_start_at'] = $startTs > 0 ? date('Y-m-d H:i:s', $startTs) : '';
|
||||
$list[$idx]['period_end_at'] = $endTs > 0 ? date('Y-m-d H:i:s', $endTs) : '';
|
||||
$settledTs = intval($row['settled_at'] ?? 0);
|
||||
$list[$idx]['settled_at'] = $settledTs > 0 ? date('Y-m-d H:i:s', $settledTs) : '';
|
||||
$list[$idx]['commission_amount'] = bcadd(strval($row['commission_amount'] ?? '0'), '0', 2);
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道直属玩家下注记录(直属投注额列点击)
|
||||
*/
|
||||
public function directBetRecordList(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->auth->check('channel/viewDirectBetRecords')) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
$channelId = (int) ($request->get('channel_id', 0));
|
||||
if ($channelId <= 0 || !$this->assertChannelAccessible($channelId)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
return $this->success('', $this->fetchChannelPlayRecordListPayload($request, $channelId, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 参与分红口径的下注记录(操作列「查看总投注金额」)
|
||||
*/
|
||||
public function settlementBetRecordList(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->auth->check('channel/viewSettlementBetRecords')) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
$channelId = (int) ($request->get('channel_id', 0));
|
||||
if ($channelId <= 0 || !$this->assertChannelAccessible($channelId)) {
|
||||
return $this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
return $this->success('', $this->fetchChannelPlayRecordListPayload($request, $channelId, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{list: array<int, array<string, mixed>>, total: int, summary: array{record_count:int,total_bet_amount:string,total_win_amount:string}}
|
||||
*/
|
||||
private function fetchChannelPlayRecordListPayload(WebmanRequest $request, int $channelId, bool $settledOnly): array
|
||||
{
|
||||
[$page, $limit] = $this->parseListPageParams($request);
|
||||
$filters = $this->parsePlayRecordListFilters($request);
|
||||
|
||||
$listQuery = $this->buildChannelPlayRecordQuery($channelId, $settledOnly);
|
||||
$this->applyPlayRecordListFilters($listQuery, $filters);
|
||||
$total = (int) $listQuery->count('pr.id');
|
||||
$list = $listQuery
|
||||
->field($this->channelPlayRecordListFields())
|
||||
->order('pr.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$summaryQuery = $this->buildChannelPlayRecordQuery($channelId, $settledOnly);
|
||||
$this->applyPlayRecordListFilters($summaryQuery, $filters);
|
||||
$summary = $this->summarizePlayRecordQuery($summaryQuery);
|
||||
|
||||
return [
|
||||
'list' => $this->normalizePlayRecordListRows($list),
|
||||
'total' => $total,
|
||||
'summary' => $summary,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{period_no: string, user_keyword: string, result_number: string, pick_number: string, win_hit: string}
|
||||
*/
|
||||
private function parsePlayRecordListFilters(WebmanRequest $request): array
|
||||
{
|
||||
$winHit = trim((string) $request->get('win_hit', ''));
|
||||
if (!in_array($winHit, ['won', 'lost', 'pending'], true)) {
|
||||
$winHit = '';
|
||||
}
|
||||
|
||||
return [
|
||||
'period_no' => trim((string) $request->get('period_no', '')),
|
||||
'user_keyword' => trim((string) $request->get('user_keyword', '')),
|
||||
'result_number' => trim((string) $request->get('result_number', '')),
|
||||
'pick_number' => trim((string) $request->get('pick_number', '')),
|
||||
'win_hit' => $winHit,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \think\db\Query $query
|
||||
* @param array{period_no: string, user_keyword: string, result_number: string, pick_number: string, win_hit: string} $filters
|
||||
*/
|
||||
private function applyPlayRecordListFilters($query, array $filters): void
|
||||
{
|
||||
if ($filters['period_no'] !== '') {
|
||||
$like = '%' . $this->escapeLikeKeyword($filters['period_no']) . '%';
|
||||
$query->where(function ($sub) use ($like) {
|
||||
$sub->where('pr.period_no', 'like', $like)->whereOr('gr.period_no', 'like', $like);
|
||||
});
|
||||
}
|
||||
if ($filters['user_keyword'] !== '') {
|
||||
$like = '%' . $this->escapeLikeKeyword($filters['user_keyword']) . '%';
|
||||
$query->where(function ($sub) use ($like) {
|
||||
$sub->where('u.username', 'like', $like)->whereOr('u.phone', 'like', $like);
|
||||
});
|
||||
}
|
||||
if ($filters['result_number'] !== '') {
|
||||
$query->where('gr.result_number', $filters['result_number']);
|
||||
}
|
||||
if ($filters['pick_number'] !== '') {
|
||||
$like = '%' . $this->escapeLikeKeyword($filters['pick_number']) . '%';
|
||||
$query->where('pr.pick_numbers', 'like', $like);
|
||||
}
|
||||
if ($filters['win_hit'] === 'won') {
|
||||
$query->where('pr.status', 2)
|
||||
->whereRaw('(pr.win_amount + IFNULL(pr.jackpot_extra_amount, 0)) > 0');
|
||||
} elseif ($filters['win_hit'] === 'lost') {
|
||||
$query->where('pr.status', 2)
|
||||
->whereRaw('(pr.win_amount + IFNULL(pr.jackpot_extra_amount, 0)) <= 0');
|
||||
} elseif ($filters['win_hit'] === 'pending') {
|
||||
$query->where('pr.status', '<>', 2);
|
||||
}
|
||||
}
|
||||
|
||||
private function escapeLikeKeyword(string $keyword): string
|
||||
{
|
||||
return str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], $keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:int,1:int}
|
||||
*/
|
||||
private function parseListPageParams(WebmanRequest $request): array
|
||||
{
|
||||
$pageRaw = $request->get('page', 1);
|
||||
$page = is_numeric((string) $pageRaw) ? (int) $pageRaw : 1;
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
$limitRaw = $request->get('limit', 20);
|
||||
$limit = is_numeric((string) $limitRaw) ? (int) $limitRaw : 20;
|
||||
if ($limit < 1) {
|
||||
$limit = 1;
|
||||
}
|
||||
if ($limit > 200) {
|
||||
$limit = 200;
|
||||
}
|
||||
return [$page, $limit];
|
||||
}
|
||||
|
||||
private function assertChannelVisible(int $channelId): bool
|
||||
{
|
||||
if ($channelId <= 0) {
|
||||
return false;
|
||||
}
|
||||
$scope = $this->readableChannelIds();
|
||||
if ($scope === null) {
|
||||
return Db::name('channel')->where('id', $channelId)->value('id') !== null;
|
||||
}
|
||||
|
||||
return in_array($channelId, $scope, true);
|
||||
}
|
||||
|
||||
private function assertChannelWritable(int $channelId): bool
|
||||
{
|
||||
if ($channelId <= 0) {
|
||||
return false;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return Db::name('channel')->where('id', $channelId)->value('id') !== null;
|
||||
}
|
||||
return in_array($channelId, $this->ownChannelIds, true);
|
||||
}
|
||||
|
||||
private function assertChannelAccessible(int $channelId): bool
|
||||
{
|
||||
return $this->assertChannelVisible($channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \think\db\Query
|
||||
*/
|
||||
private function canManualSettle(): bool
|
||||
{
|
||||
if ($this->auth === null) {
|
||||
return false;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->auth->check('channel/manualSettle');
|
||||
}
|
||||
|
||||
private function buildChannelPlayRecordQuery(int $channelId, bool $settledOnly)
|
||||
{
|
||||
$query = Db::name('game_play_record')->alias('pr')
|
||||
->leftJoin('user u', 'u.id = pr.user_id')
|
||||
->leftJoin('game_record gr', 'gr.id = pr.period_id')
|
||||
->leftJoin('channel c', 'c.id = pr.channel_id')
|
||||
->where('pr.channel_id', $channelId);
|
||||
if ($settledOnly) {
|
||||
$query->where('pr.status', 2);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function channelPlayRecordListFields(): array
|
||||
{
|
||||
return [
|
||||
'pr.id',
|
||||
'pr.period_no',
|
||||
'pr.period_id',
|
||||
'pr.user_id',
|
||||
'pr.pick_numbers',
|
||||
'pr.total_amount',
|
||||
'pr.win_amount',
|
||||
'pr.jackpot_extra_amount',
|
||||
'pr.status',
|
||||
'pr.create_time',
|
||||
'u.username as user_username',
|
||||
'c.name as channel_name',
|
||||
'gr.period_no as game_record_period_no',
|
||||
'gr.result_number',
|
||||
'gr.status as game_record_status',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $list
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function normalizePlayRecordListRows(array $list): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($list as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$periodNo = trim((string) ($row['period_no'] ?? ''));
|
||||
if ($periodNo === '') {
|
||||
$periodNo = trim((string) ($row['game_record_period_no'] ?? ''));
|
||||
}
|
||||
$win = bcadd(strval($row['win_amount'] ?? '0'), strval($row['jackpot_extra_amount'] ?? '0'), 2);
|
||||
$playStatus = intval($row['status'] ?? 0);
|
||||
$out[] = [
|
||||
'id' => intval($row['id'] ?? 0),
|
||||
'period_no' => $periodNo,
|
||||
'user_username' => (string) ($row['user_username'] ?? ''),
|
||||
'channel_name' => (string) ($row['channel_name'] ?? ''),
|
||||
'pick_numbers' => $this->formatPickNumbersForList($row['pick_numbers'] ?? null),
|
||||
'result_number' => $this->formatResultNumberForList($row['result_number'] ?? null),
|
||||
'win_hit' => $this->resolveWinHitCode($win, $playStatus),
|
||||
'play_status' => $playStatus,
|
||||
'total_amount' => bcadd(strval($row['total_amount'] ?? '0'), '0', 2),
|
||||
'win_amount' => bcadd($win, '0', 2),
|
||||
'create_time' => intval($row['create_time'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function formatPickNumbersForList(mixed $pickNumbers): string
|
||||
{
|
||||
if ($pickNumbers === null || $pickNumbers === '') {
|
||||
return '';
|
||||
}
|
||||
if (is_string($pickNumbers)) {
|
||||
$trimmed = trim($pickNumbers);
|
||||
if ($trimmed === '') {
|
||||
return '';
|
||||
}
|
||||
$decoded = json_decode($trimmed, true);
|
||||
if (is_array($decoded)) {
|
||||
$pickNumbers = $decoded;
|
||||
} else {
|
||||
return $trimmed;
|
||||
}
|
||||
}
|
||||
if (!is_array($pickNumbers)) {
|
||||
return '';
|
||||
}
|
||||
$parts = [];
|
||||
foreach ($pickNumbers as $num) {
|
||||
if ($num === null || $num === '') {
|
||||
continue;
|
||||
}
|
||||
$parts[] = (string) $num;
|
||||
}
|
||||
|
||||
return implode(',', $parts);
|
||||
}
|
||||
|
||||
private function formatResultNumberForList(mixed $resultNumber): string
|
||||
{
|
||||
if ($resultNumber === null || $resultNumber === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $resultNumber;
|
||||
}
|
||||
|
||||
private function resolveWinHitCode(string $winAmount, int $playStatus): string
|
||||
{
|
||||
if ($playStatus > 0 && $playStatus !== 2) {
|
||||
return 'pending';
|
||||
}
|
||||
if (bccomp($winAmount, '0', 2) > 0) {
|
||||
return 'won';
|
||||
}
|
||||
|
||||
return 'lost';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \think\db\Query $query
|
||||
* @return array{record_count:int,total_bet_amount:string,total_win_amount:string}
|
||||
*/
|
||||
private function summarizePlayRecordQuery($query): array
|
||||
{
|
||||
$row = $query
|
||||
->field('COUNT(pr.id) AS c, SUM(pr.total_amount) AS tb, SUM(pr.win_amount) AS tw, SUM(pr.jackpot_extra_amount) AS tj')
|
||||
->find();
|
||||
$count = is_array($row) ? intval($row['c'] ?? 0) : 0;
|
||||
$tb = is_array($row) ? strval($row['tb'] ?? '0') : '0';
|
||||
$tw = is_array($row) ? strval($row['tw'] ?? '0') : '0';
|
||||
$tj = is_array($row) ? strval($row['tj'] ?? '0') : '0';
|
||||
return [
|
||||
'record_count' => $count,
|
||||
'total_bet_amount' => bcadd($tb, '0', 2),
|
||||
'total_win_amount' => bcadd(bcadd($tw, $tj, 2), '0', 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string 成功返回预览数据数组,失败返回错误文案
|
||||
*/
|
||||
@@ -936,19 +1412,31 @@ class Channel extends Backend
|
||||
return $chosen;
|
||||
}
|
||||
|
||||
private function getCurrentChannelIds(): array
|
||||
/**
|
||||
* @return array<int, int>
|
||||
*/
|
||||
/**
|
||||
* 写操作可作用的渠道(角色组绑定渠道 + 账号 channel_id,不含全平台只读)
|
||||
*
|
||||
* @return array<int, int>
|
||||
*/
|
||||
private function resolveOwnChannelIds(): array
|
||||
{
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return Db::name('channel')->column('id');
|
||||
if ($this->auth === null) {
|
||||
return [];
|
||||
}
|
||||
$ids = AdminChannelScopeService::resolveBoundGroupChannelIds($this->auth);
|
||||
if ($ids !== []) {
|
||||
return $ids;
|
||||
}
|
||||
$admin = Db::name('admin')
|
||||
->field(['id', 'channel_id'])
|
||||
->where('id', $this->auth->id)
|
||||
->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
$ids[] = (int) $admin['channel_id'];
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ class Dashboard extends Backend
|
||||
*/
|
||||
private function ownerAdminIdOrNull(): ?int
|
||||
{
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
if (!$this->auth || $this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return null;
|
||||
}
|
||||
$idRaw = $this->auth->id;
|
||||
|
||||
@@ -76,7 +76,7 @@ class PlayRecord extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ class PlayRecord extends Backend
|
||||
$remark = is_string($remarkRaw) ? trim($remarkRaw) : '';
|
||||
|
||||
// 权限范围校验:复用列表逻辑(非超管只能操作其下辖用户)
|
||||
if ($this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($this->shouldApplyUserAdminScope()) {
|
||||
$uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id');
|
||||
$uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw;
|
||||
if ($uid <= 0) {
|
||||
@@ -240,7 +240,7 @@ class PlayRecord extends Backend
|
||||
return $this->error(__('Please provide reject reason'));
|
||||
}
|
||||
|
||||
if ($this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($this->shouldApplyUserAdminScope()) {
|
||||
$uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id');
|
||||
$uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw;
|
||||
if ($uid <= 0) {
|
||||
|
||||
@@ -78,7 +78,7 @@ class UserNoticeRead extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,9 @@ class AdminWithdrawOrder extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $this->getCurrentAdminChannelIds()];
|
||||
$channelScope = $this->readableChannelIds();
|
||||
if ($mainShort !== '' && $channelScope !== null) {
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelScope];
|
||||
}
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
@@ -170,8 +171,9 @@ class AdminWithdrawOrder extends Backend
|
||||
return $response;
|
||||
}
|
||||
$query = Db::name('admin_withdraw_order');
|
||||
if ($this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$query->where('channel_id', 'in', $this->getCurrentAdminChannelIds());
|
||||
$channelScope = $this->readableChannelIds();
|
||||
if ($channelScope !== null) {
|
||||
$query->where('channel_id', 'in', $channelScope);
|
||||
}
|
||||
$rows = $query->field(['status', 'amount', 'actual_amount'])->select()->toArray();
|
||||
$total = count($rows);
|
||||
@@ -227,53 +229,19 @@ class AdminWithdrawOrder extends Backend
|
||||
if (!$this->auth) {
|
||||
return false;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
if ($this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return true;
|
||||
}
|
||||
$channelId = intval($order['channel_id'] ?? 0);
|
||||
if ($channelId <= 0) {
|
||||
return false;
|
||||
}
|
||||
$allowed = $this->getCurrentAdminChannelIds();
|
||||
$allowed = $this->readableChannelIds();
|
||||
if ($allowed === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($channelId, $allowed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前管理员可审核的渠道(优先取自身 channel_id,同时兼容角色组继承链上的 channel_id)
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function getCurrentAdminChannelIds(): array
|
||||
{
|
||||
$uid = intval($this->auth->id ?? 0);
|
||||
if ($uid <= 0) {
|
||||
return [0];
|
||||
}
|
||||
$channelIds = [];
|
||||
|
||||
$selfChannelId = intval(Db::name('admin')->where('id', $uid)->value('channel_id') ?? 0);
|
||||
if ($selfChannelId > 0) {
|
||||
$channelIds[] = $selfChannelId;
|
||||
}
|
||||
|
||||
$groupIds = Db::name('admin_group_access')->where('uid', $uid)->column('group_id');
|
||||
if ($groupIds !== []) {
|
||||
$groupIds = array_values(array_unique(array_merge($groupIds, $this->auth->getAdminChildGroups())));
|
||||
$rows = Db::name('admin_group')
|
||||
->field(['id', 'channel_id'])
|
||||
->where('id', 'in', $groupIds)
|
||||
->whereNotNull('channel_id')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rows as $row) {
|
||||
$cid = intval($row['channel_id'] ?? 0);
|
||||
if ($cid > 0) {
|
||||
$channelIds[] = $cid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $channelIds === [] ? [0] : array_values(array_unique($channelIds));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class BetOrder extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class DepositOrder extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
$this->appendDepositOrderIndexWhere($where, $mainShort);
|
||||
@@ -129,7 +129,7 @@ class DepositOrder extends Backend
|
||||
|
||||
private function checkChannelScoped(array $row): bool
|
||||
{
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
if (!$this->auth || $this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return true;
|
||||
}
|
||||
$userRow = $row['user'] ?? null;
|
||||
|
||||
@@ -49,7 +49,7 @@ class WithdrawOrder extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
@@ -658,7 +658,7 @@ class WithdrawOrder extends Backend
|
||||
|
||||
private function checkChannelScoped(array|object $row): bool
|
||||
{
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
if (!$this->auth || $this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return true;
|
||||
}
|
||||
$uidRaw = is_array($row) ? ($row['user_id'] ?? null) : ($row->user_id ?? null);
|
||||
|
||||
@@ -77,7 +77,7 @@ class UserWalletRecord extends Backend
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
if ($mainShort !== '' && $this->shouldApplyUserAdminScope()) {
|
||||
$where[] = ['user.admin_id', 'in', $this->scopedAdminIds()];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'Remark lang' => '开源等于互助;开源需要大家一起来支持,支持的方式有很多种,比如使用、推荐、写教程、保护生态、贡献代码、回答问题、分享经验、打赏赞助等;欢迎您加入我们!',
|
||||
'Remark lang' => 'telegram@zhenhui666',
|
||||
];
|
||||
@@ -5,6 +5,7 @@ namespace app\api\controller;
|
||||
use ba\Date;
|
||||
use ba\Captcha;
|
||||
use ba\Random;
|
||||
use app\common\library\MalaysiaMobilePhone;
|
||||
use app\common\library\finance\WithdrawFlow;
|
||||
use app\common\model\User;
|
||||
use app\common\facade\Token;
|
||||
@@ -235,7 +236,7 @@ class Account extends Frontend
|
||||
return $this->error(__('Email') . ' ' . __('already exists'));
|
||||
}
|
||||
} else {
|
||||
Validator::make($params, ['mobile' => 'required|regex:/^1[3-9]\d{9}$/'])->validate();
|
||||
Validator::make($params, ['mobile' => 'required|regex:' . MalaysiaMobilePhone::PATTERN])->validate();
|
||||
if (User::where('mobile', $params['mobile'])->find()) {
|
||||
return $this->error(__('Mobile') . ' ' . __('already exists'));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace app\api\controller;
|
||||
|
||||
use app\common\facade\Token;
|
||||
use app\common\library\Auth as UserAuth;
|
||||
use app\common\library\MalaysiaMobilePhone;
|
||||
use app\common\model\User;
|
||||
use app\common\service\MobileAuthDeviceService;
|
||||
use ba\Random;
|
||||
@@ -38,7 +39,7 @@ class Auth extends MobileBase
|
||||
if ($inviteCode === '') {
|
||||
return $this->mobileError(1001, 'Invite code required');
|
||||
}
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $username)) {
|
||||
if (!MalaysiaMobilePhone::isValid($username)) {
|
||||
return $this->mobileError(1003, 'Please enter the correct mobile number');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\library\MalaysiaMobilePhone;
|
||||
use ba\Captcha;
|
||||
use ba\ClickCaptcha;
|
||||
use app\common\controller\Frontend;
|
||||
@@ -138,7 +139,7 @@ class User extends Frontend
|
||||
'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
||||
'registerType' => 'required|in:email,mobile',
|
||||
'email' => 'required_if:registerType,email|email|unique:user,email',
|
||||
'mobile' => 'required_if:registerType,mobile|regex:/^1[3-9]\d{9}$/|unique:user,mobile',
|
||||
'mobile' => 'required_if:registerType,mobile|regex:' . MalaysiaMobilePhone::PATTERN . '|unique:user,mobile',
|
||||
'captcha' => 'required|string',
|
||||
'invite_code' => 'nullable|string|max:64',
|
||||
],
|
||||
|
||||
@@ -30,7 +30,7 @@ return [
|
||||
'Invite code not bound to channel' => 'This invite code is not bound to a valid channel',
|
||||
'Channel disabled' => 'Channel is disabled',
|
||||
'Account already registered' => 'This phone number is already registered. Please sign in.',
|
||||
'Please enter the correct mobile number' => 'Please enter the correct mobile number',
|
||||
'Please enter the correct mobile number' => 'Please enter a valid Malaysia mobile number starting with 60',
|
||||
'Registered successfully but login failed' => 'Registered successfully but login failed',
|
||||
'Incorrect account or password' => 'Incorrect account or password',
|
||||
'Login status has expired' => 'Login status has expired',
|
||||
|
||||
@@ -62,7 +62,7 @@ return [
|
||||
'Invite code not bound to channel' => '该邀请码未绑定有效渠道',
|
||||
'Channel disabled' => '渠道已关闭',
|
||||
'Account already registered' => '该手机号已注册,请直接登录',
|
||||
'Please enter the correct mobile number' => '请输入正确的手机号',
|
||||
'Please enter the correct mobile number' => '请输入正确的马来西亚手机号(60开头)',
|
||||
'Registered successfully but login failed' => '注册成功但登录失败',
|
||||
'Incorrect account or password' => '账号或密码错误',
|
||||
'Login status has expired' => '登录状态已过期',
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace app\common\controller;
|
||||
|
||||
use Throwable;
|
||||
use app\admin\library\Auth;
|
||||
use app\common\service\AdminChannelScopeService;
|
||||
use app\common\library\token\TokenExpirationException;
|
||||
use app\admin\library\traits\Backend as BackendTrait;
|
||||
use support\Response;
|
||||
@@ -421,7 +422,7 @@ class Backend extends Api
|
||||
*/
|
||||
protected function getDataLimitAdminIds(): array
|
||||
{
|
||||
if (!$this->dataLimit || !$this->auth || $this->auth->isSuperAdmin()) {
|
||||
if (!$this->dataLimit || !$this->auth || $this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return [];
|
||||
}
|
||||
$adminIds = [];
|
||||
@@ -475,6 +476,51 @@ class Backend extends Api
|
||||
return get_controller_path($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全平台只读范围:角色组均未绑定渠道,或拥有查看所有渠道权限,或超管
|
||||
*/
|
||||
protected function hasGlobalReadScope(): bool
|
||||
{
|
||||
return $this->auth !== null && AdminChannelScopeService::hasGlobalReadScope($this->auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否按「名下管理员/用户」收窄列表(非超管且非全平台只读范围)
|
||||
*/
|
||||
protected function shouldApplyUserAdminScope(): bool
|
||||
{
|
||||
if ($this->auth === null || !$this->auth->isLogin()) {
|
||||
return false;
|
||||
}
|
||||
if ($this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可读渠道 ID;null 表示全部渠道
|
||||
*
|
||||
* @return array<int, int>|null
|
||||
*/
|
||||
protected function readableChannelIds(): ?array
|
||||
{
|
||||
if ($this->auth === null) {
|
||||
return [0];
|
||||
}
|
||||
|
||||
return AdminChannelScopeService::readableChannelIds($this->auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表是否需要按 channel_id 收窄
|
||||
*/
|
||||
protected function shouldApplyChannelIdScope(): bool
|
||||
{
|
||||
return $this->readableChannelIds() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造权限节点候选:兼容 snake_case 与 camelCase 节点名
|
||||
*
|
||||
|
||||
@@ -120,6 +120,11 @@ return [
|
||||
'createNextManual' => 'Create next period manually',
|
||||
'periodSettings' => 'Period settings',
|
||||
'manualSettle' => 'Manual settle',
|
||||
'batchSettlePending' => 'Batch settle pending channels',
|
||||
'viewDividendRecords' => 'View paid dividend records',
|
||||
'viewDirectBetRecords' => 'View direct bet records',
|
||||
'viewSettlementBetRecords' => 'View settlement-scope bets',
|
||||
'viewAllChannels' => 'View all channels',
|
||||
|
||||
// 其它中文按钮文案
|
||||
'期号开关' => 'Period toggle',
|
||||
|
||||
@@ -115,6 +115,7 @@ return [
|
||||
'Only super admin can settle; after settlement, commissions will be automatically paid to admin wallets' => 'Only super admin can settle; after settlement, commissions will be automatically paid to admin wallets',
|
||||
'Settlement failed' => 'Settlement failed',
|
||||
'Super admin settlement completed; paid automatically by share ratios' => 'Super admin settlement completed; paid automatically by share ratios',
|
||||
'Settlement completed; commissions paid automatically by share ratios' => 'Settlement completed; commissions paid automatically by share ratios',
|
||||
'Batch settlement completed' => 'Batch settlement completed',
|
||||
|
||||
'Invalid settlement cycle' => 'Invalid settlement cycle',
|
||||
|
||||
@@ -53,6 +53,10 @@ return [
|
||||
'periodSettings' => '期号设置',
|
||||
'manualSettle' => '手动结算',
|
||||
'batchSettlePending' => '批量结算待结算渠道',
|
||||
'viewDividendRecords' => '查看已分红记录',
|
||||
'viewDirectBetRecords' => '查看直属投注记录',
|
||||
'viewSettlementBetRecords' => '查看总投注金额',
|
||||
'viewAllChannels' => '查看所有渠道',
|
||||
'walletAdjust' => '钱包加减点',
|
||||
'Markdown文档' => 'Markdown文档',
|
||||
'分红说明文档' => '分红说明文档',
|
||||
|
||||
@@ -115,6 +115,7 @@ return [
|
||||
'Only super admin can settle; after settlement, commissions will be automatically paid to admin wallets' => '仅超管可执行结算,结算后系统会自动发放至管理员钱包',
|
||||
'Settlement failed' => '结算失败',
|
||||
'Super admin settlement completed; paid automatically by share ratios' => '超管结算完成,已按分配比例自动发放给管理员',
|
||||
'Settlement completed; commissions paid automatically by share ratios' => '结算完成,已按分配比例自动发放给管理员',
|
||||
'Batch settlement completed' => '批量结算完成',
|
||||
|
||||
'Invalid settlement cycle' => '结算周期不合法',
|
||||
|
||||
@@ -105,7 +105,7 @@ class Auth extends \ba\Auth
|
||||
$this->setError(__('Email'));
|
||||
return false;
|
||||
}
|
||||
$isMobileUsername = preg_match('/^1[3-9]\d{9}$/', $username) === 1;
|
||||
$isMobileUsername = MalaysiaMobilePhone::isValid($username);
|
||||
$isNormalUsername = preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username) === 1;
|
||||
if ($username && !$isMobileUsername && !$isNormalUsername) {
|
||||
$this->setError(__('Username'));
|
||||
@@ -127,7 +127,7 @@ class Auth extends \ba\Auth
|
||||
return false;
|
||||
}
|
||||
|
||||
$nickname = preg_replace_callback('/1[3-9]\d{9}/', fn($m) => substr($m[0], 0, 3) . '****' . substr($m[0], 7), $username);
|
||||
$nickname = MalaysiaMobilePhone::maskInNickname($username);
|
||||
$time = time();
|
||||
$data = [
|
||||
'group_id' => $group,
|
||||
@@ -170,9 +170,13 @@ class Auth extends \ba\Auth
|
||||
public function login(string $username, string $password, bool $keep): bool
|
||||
{
|
||||
$accountType = false;
|
||||
if (preg_match('/^1[3-9]\d{9}$/', $username)) $accountType = 'phone';
|
||||
elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) $accountType = 'email';
|
||||
elseif (preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username)) $accountType = 'username';
|
||||
if (MalaysiaMobilePhone::isValid($username)) {
|
||||
$accountType = 'phone';
|
||||
} elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
||||
$accountType = 'email';
|
||||
} elseif (preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username)) {
|
||||
$accountType = 'username';
|
||||
}
|
||||
if (!$accountType) {
|
||||
$this->setError('Account not exist');
|
||||
return false;
|
||||
|
||||
29
app/common/library/MalaysiaMobilePhone.php
Normal file
29
app/common/library/MalaysiaMobilePhone.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
/**
|
||||
* 马来西亚手机号(60 国际前缀,不含 +)
|
||||
*/
|
||||
class MalaysiaMobilePhone
|
||||
{
|
||||
public const PATTERN = '/^60(1[0-9])\d{7,9}$/';
|
||||
|
||||
public static function isValid(string $phone): bool
|
||||
{
|
||||
return $phone !== '' && preg_match(self::PATTERN, $phone) === 1;
|
||||
}
|
||||
|
||||
public static function maskInNickname(string $text): string
|
||||
{
|
||||
$masked = preg_replace_callback(
|
||||
self::PATTERN,
|
||||
static fn(array $matches): string => substr($matches[0], 0, 3) . '****' . substr($matches[0], -4),
|
||||
$text
|
||||
);
|
||||
|
||||
return is_string($masked) ? $masked : $text;
|
||||
}
|
||||
}
|
||||
123
app/common/service/AdminChannelScopeService.php
Normal file
123
app/common/service/AdminChannelScopeService.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\admin\library\Auth;
|
||||
use support\think\Db;
|
||||
|
||||
/**
|
||||
* 后台管理员渠道数据范围:角色组 channel_id 未绑定时可读全平台;否则按绑定渠道过滤。
|
||||
*/
|
||||
class AdminChannelScopeService
|
||||
{
|
||||
/**
|
||||
* 是否具备全平台只读范围(超管 / 角色组均未绑定渠道 / 拥有查看所有渠道权限)
|
||||
*/
|
||||
public static function hasGlobalReadScope(Auth $auth): bool
|
||||
{
|
||||
if (!$auth->isLogin()) {
|
||||
return false;
|
||||
}
|
||||
if ($auth->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
if (self::resolveBoundGroupChannelIds($auth) === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return self::hasViewAllChannelsPermission($auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可读渠道 ID 列表;null 表示不限制(全部渠道)
|
||||
*
|
||||
* @return array<int, int>|null
|
||||
*/
|
||||
public static function readableChannelIds(Auth $auth): ?array
|
||||
{
|
||||
if (!$auth->isLogin()) {
|
||||
return [0];
|
||||
}
|
||||
if (self::hasGlobalReadScope($auth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ids = self::resolveBoundGroupChannelIds($auth);
|
||||
if ($ids !== []) {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
$selfChannelId = (int) Db::name('admin')->where('id', (int) $auth->id)->value('channel_id');
|
||||
if ($selfChannelId > 0) {
|
||||
return [$selfChannelId];
|
||||
}
|
||||
|
||||
return [0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前管理员所属角色组上绑定的渠道 ID(去重)
|
||||
*
|
||||
* @return array<int, int>
|
||||
*/
|
||||
public static function resolveBoundGroupChannelIds(Auth $auth): array
|
||||
{
|
||||
$uid = (int) $auth->id;
|
||||
if ($uid <= 0) {
|
||||
return [];
|
||||
}
|
||||
$groupIds = Db::name('admin_group_access')->where('uid', $uid)->column('group_id');
|
||||
if ($groupIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('admin_group')
|
||||
->where('id', 'in', $groupIds)
|
||||
->whereNotNull('channel_id')
|
||||
->where('channel_id', '>', 0)
|
||||
->column('channel_id');
|
||||
|
||||
$ids = [];
|
||||
foreach ($rows as $cid) {
|
||||
$ids[] = (int) $cid;
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否拥有「查看所有渠道」按钮权限
|
||||
*/
|
||||
public static function hasViewAllChannelsPermission(Auth $auth): bool
|
||||
{
|
||||
if (!$auth->isLogin()) {
|
||||
return false;
|
||||
}
|
||||
$nodes = ['channel/viewAllChannels', 'channel/viewallchannels'];
|
||||
foreach ($nodes as $node) {
|
||||
if ($auth->check($node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$ruleList = $auth->getRuleList();
|
||||
foreach ($nodes as $node) {
|
||||
if (in_array(strtolower($node), $ruleList, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表按 channel_id 过滤时使用的 ID;null=不过滤
|
||||
*
|
||||
* @return array<int, int>|null
|
||||
*/
|
||||
public static function channelIdFilterForQuery(Auth $auth): ?array
|
||||
{
|
||||
return self::readableChannelIds($auth);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user