authorize('GET')) { return $denied; } $params = $this->request->get(); $query = Db::name('ai_grant')->alias('g')->leftJoin('admin a', 'a.id = g.admin_id') ->field('g.id,g.admin_id,a.name as admin_name,a.account as admin_account,g.token_prefix,g.client,g.client_instance,g.label,g.status,' . 'g.expire_time,g.idle_days,g.last_used_time,g.last_used_ip,g.created_ip,g.revoke_time,g.revoke_reason,g.create_time'); if (!$this->can('ai.grant/lists')) { $query->where('g.admin_id', $this->adminId()); } elseif (!empty($params['admin_id'])) { $query->where('g.admin_id', (int) $params['admin_id']); } if (isset($params['status']) && $params['status'] !== '') { $query->where('g.status', (int) $params['status']); } if (!empty($params['keyword'])) { $keyword = '%' . trim((string) $params['keyword']) . '%'; $query->where(static fn ($q) => $q->whereLike('a.name', $keyword)->whereOr('a.account', 'like', $keyword)->whereOr('g.label', 'like', $keyword)); } [$pageNo, $pageSize] = $this->page($params); $count = (clone $query)->count(); $rows = $query->order('g.id', 'desc')->page($pageNo, $pageSize)->select()->toArray(); $now = time(); foreach ($rows as &$row) { $idleUntil = (int) $row['last_used_time'] + (int) $row['idle_days'] * 86400; $active = (int) $row['status'] === GrantService::STATUS_ACTIVE && (int) $row['expire_time'] > $now && $idleUntil > $now; $row['status_text'] = $active ? '有效' : ((int) $row['status'] === GrantService::STATUS_REVOKED ? '已撤销' : '已过期'); $row['can_revoke'] = $active && ((int) $row['admin_id'] === $this->adminId() || $this->can('ai.grant/revoke')); foreach (['expire_time', 'last_used_time', 'revoke_time', 'create_time'] as $field) { $row[$field . '_text'] = (int) $row[$field] > 0 ? date('Y-m-d H:i', (int) $row[$field]) : ''; } } unset($row); return $this->lists($rows, $count, $pageNo, $pageSize, ['enabled' => McpConfig::enabled()]); } public function revoke(): Response { if ($denied = $this->authorize('POST')) { return $denied; } $id = (int) ($this->request->post('id') ?? 0); $grant = GrantService::find($id); if (!$grant) { return Guard::envelope(0, '授权不存在', [], 200, 1); } if ((int) $grant['admin_id'] !== $this->adminId() && !$this->can('ai.grant/revoke')) { return Guard::envelope(0, '权限不足,无法访问或操作', [], 200, 1); } GrantService::close($id, GrantService::STATUS_REVOKED, 'admin_revoke', $this->adminId()); return Guard::envelope(1, '已撤销', [], 200, 1); } public function logs(): Response { if ($denied = $this->authorize('GET')) { return $denied; } $params = $this->request->get(); $query = Db::name('ai_access_log')->alias('l')->leftJoin('admin a', 'a.id = l.admin_id') ->field('l.*,a.name as admin_name,a.account as admin_account'); if (!$this->can('ai.accessLog/lists')) { $query->where('l.admin_id', $this->adminId()); } elseif (!empty($params['admin_id'])) { $query->where('l.admin_id', (int) $params['admin_id']); } foreach (['status' => 'l.status', 'tool' => 'l.tool', 'client_task_id' => 'l.client_task_id'] as $param => $column) { if (!empty($params[$param])) { $query->where($column, (string) $params[$param]); } } if (!empty($params['resource'])) { $query->whereLike('l.resource', '%' . trim((string) $params['resource']) . '%'); } if (!empty($params['record_id'])) { $query->whereRaw('FIND_IN_SET(:rid, l.record_ids)', ['rid' => (string) $params['record_id']]); } if (!empty($params['start_time']) && strtotime((string) $params['start_time'])) { $query->where('l.create_time', '>=', strtotime((string) $params['start_time'])); } if (!empty($params['end_time']) && strtotime((string) $params['end_time'])) { $query->where('l.create_time', '<=', strtotime((string) $params['end_time'])); } [$pageNo, $pageSize] = $this->page($params); $count = (clone $query)->count(); $rows = $query->order('l.id', 'desc')->page($pageNo, $pageSize)->select()->toArray(); $names = []; foreach (Catalog::all() as $key => $r) { $names[$key] = $r['name']; } foreach ($rows as &$row) { $row['create_time_text'] = date('Y-m-d H:i:s', (int) $row['create_time']); $row['resource_name'] = $names[$row['resource']] ?? ''; } unset($row); return $this->lists($rows, $count, $pageNo, $pageSize); } public function catalog(): Response { if ($denied = $this->authorize('GET', 'ai.catalog/lists')) { return $denied; } $params = $this->request->get(); $rows = []; foreach (Catalog::all() as $key => $r) { if (!empty($params['status']) && $r['status'] !== $params['status']) { continue; } if (!empty($params['domain']) && $r['domain'] !== $params['domain']) { continue; } if (!empty($params['keyword']) && mb_stripos($r['name'] . ' ' . $key, trim((string) $params['keyword'])) === false) { continue; } $rows[] = ['resource' => $key, 'name' => $r['name'], 'domain' => $r['domain'], 'kind' => $r['kind'], 'status' => $r['status'], 'reason' => $r['reason'], 'reviewed' => $r['reviewed'], 'registered' => $r['registered']]; } [$pageNo, $pageSize] = $this->page($params, 100); $domains = array_values(array_unique(array_column(Catalog::all(), 'domain'))); sort($domains); return $this->lists(array_slice($rows, ($pageNo - 1) * $pageSize, $pageSize), count($rows), $pageNo, $pageSize, ['counts' => Catalog::counts(), 'domains' => $domains]); } /** 后台登录令牌 + IP 绑定 + 企微强制绑定,与后台登录/权限中间件一致;可再要求一个权限点 */ private function authorize(string $method, string $perm = ''): ?Response { if ($this->request->method(true) !== $method) { return response('', 405)->header(['Allow' => $method]); } $token = (string) $this->request->header('token', ''); $adminInfo = $token !== '' ? (new AdminTokenCache())->getAdminInfo($token) : false; if (empty($adminInfo)) { return Guard::envelope(-1, '登录超时,请重新登录', [], 200, 0); } if (($adminInfo['login_ip'] ?? '') != $this->request->ip()) { return Guard::envelope(-1, 'ip地址发生变化,请重新登录', [], 200, 0); } if (LoginLogic::adminMustBindWorkWechat($adminInfo)) { return Guard::envelope(LoginLogic::CODE_NEED_BIND_WORK_WECHAT, '请先绑定企业微信后再使用系统', [], 200, 0); } $this->adminInfo = $adminInfo; if ($perm !== '' && !$this->can($perm)) { return Guard::envelope(0, '权限不足,无法访问或操作', [], 200, 1); } return null; } private function can(string $perm): bool { if ((int) ($this->adminInfo['root'] ?? 0) === 1) { return true; } return PermissionService::isRegistered($perm) && isset(PermissionService::adminPerms($this->adminId())[PermissionService::normalize($perm)]); } private function adminId(): int { return (int) ($this->adminInfo['admin_id'] ?? 0); } private function page(array $params, int $max = 100): array { return [max(1, (int) ($params['page_no'] ?? 1)), max(1, min($max, (int) ($params['page_size'] ?? 15)))]; } private function lists(array $rows, int $count, int $pageNo, int $pageSize, array $extend = []): Response { return Guard::envelope(1, '', ['lists' => $rows, 'count' => $count, 'page_no' => $pageNo, 'page_size' => $pageSize, 'extend' => $extend ?: new \stdClass()]); } }