* @Date: 2026-04-29 02:32:33 * @LastEditors: YwxApp * @LastEditTime: 2026-07-21 22:29:51 * @Description: * @FilePath: \ywxapp_dev\app\backend\controller\Console.php * @CustomString: Copyright (c) 2026 YwxApp */ declare (strict_types = 1); namespace app\backend\controller; use app\backend\library\SystemInfo; use think\Request; use think\facade\View; use think\facade\Db; use ywxapp\controller\BackendBase; /** * Console 类 * * @author ywxapp */ class Console extends BackendBase { /** * Summary of needLogin * @var array */ protected $noNeedLogin = []; /** * Summary of needRight * @var array */ protected $noNeedVerify = []; /** * 控制器初始化 initialize * @return void */ protected function initialize() {} /** * 显示资源列表 * * @param \think\Request $request * @return \think\Response */ public function index() { // 控制台首页带一份系统概览(与「系统信息」页共用 SystemInfo,避免两套实现) if ($this->request->isAjax()) { $this->result->success(SystemInfo::collect()); } View::assign('title', '控制台'); View::assign('sys', SystemInfo::collect()); // 控制台内的「框架升级」卡片用(与 Upgrade 页同源:config('ywxapp.version')) View::assign('version', (string) config('ywxapp.version')); // 首页下半区:真实统计(替换 layuiAdmin 模板自带的演示数据) View::assign('stats', $this->collectStats()); return View::fetch(); } /** * 清理缓存:cache / temp / all(不动 log) * * 由原 System::clean() 迁来(/admin/system/clean → /admin/console/clean): * 「系统信息」页已随 System 控制器一起下线,但首页顶部的三个清理按钮要留着, * 所以能力搬来这里;清理实现仍在 SystemInfo::cleanRuntime(),权限节点同步改成 console:clean。 * * @return void */ public function clean() { if (! $this->request->isPost()) { $this->result->error('请求方式错误', 405); } $type = trim((string) $this->request->param('type', 'cache')); try { $files = SystemInfo::cleanRuntime($type); } catch (\Throwable $e) { $this->result->error($e->getMessage()); } $this->result->success(['files' => $files], '已清理 ' . $files . ' 项缓存'); } /* ====================== 首页统计(真实数据,替换演示数据) ====================== */ /** * 采集首页下半区要用的统计 * * 数据源只有三张核心表:会员 member_user / 后台日志 backend_log / 蜘蛛统计 common_spider_stat。 * 任意一张缺失或查询失败都退化为 0——首页是最容易被人访问的页面, * 不能因为某张插件表没建就整体 500。 * * @return array */ protected function collectStats(): array { $todayStart = strtotime('today'); $todayEnd = $todayStart + 86399; $weekStart = strtotime('-6 days', $todayStart); // 近 7 天先铺好 0 值骨架:某天没有数据也不会把曲线挤错位 $bucket = []; $dates = []; for ($i = 6; $i >= 0; $i--) { $ts = strtotime("-{$i} days", $todayStart); $key = date('Y-m-d', $ts); $bucket[$key] = ['member' => 0, 'log' => 0]; $dates[] = date('m-d', $ts); } // 近 7 天:一次取回时间戳列,在 PHP 侧按天归桶(省掉 14 次 COUNT) foreach ($this->safeColumn('member_user', 'create_at', $weekStart, $todayEnd) as $ts) { $key = date('Y-m-d', (int) $ts); if (isset($bucket[$key])) { $bucket[$key]['member']++; } } foreach ($this->safeColumn('backend_log', 'create_at', $weekStart, $todayEnd) as $ts) { $key = date('Y-m-d', (int) $ts); if (isset($bucket[$key])) { $bucket[$key]['log']++; } } $memberWeek = 0; foreach ($bucket as $row) { $memberWeek += $row['member']; } return [ 'dates' => $dates, 'series' => [ 'member' => array_column($bucket, 'member'), 'log' => array_column($bucket, 'log'), ], 'today' => [ 'member' => SystemInfo::safeCount('member_user', function ($q) use ($todayStart, $todayEnd) { $q->where('create_at', '>=', $todayStart)->where('create_at', '<=', $todayEnd); }), 'log' => SystemInfo::safeCount('backend_log', function ($q) use ($todayStart, $todayEnd) { $q->where('create_at', '>=', $todayStart)->where('create_at', '<=', $todayEnd); }), // 蜘蛛按日聚合表:今日那行的 count 就是当天抓取量 'spider' => $this->safeSum('common_spider_stat', 'count', function ($q) use ($todayStart) { $q->where('stat_date', date('Y-m-d', $todayStart)); }), ], 'total' => [ 'member' => SystemInfo::safeCount('member_user'), 'member_week' => $memberWeek, 'log' => SystemInfo::safeCount('backend_log'), 'spider' => $this->safeSum('common_spider_stat', 'count'), ], 'members' => $this->latestMembers(), 'logs' => $this->latestLogs(), ]; } /** * 最新注册的会员 * * @return array */ protected function latestMembers(): array { try { return Db::name('member_user') ->field('uid, account, nickname, create_at, update_at') ->order('uid', 'desc') ->limit(8) ->select() ->toArray(); } catch (\Throwable $e) { return []; } } /** * 最近的后台操作日志(附带管理员昵称) * * @return array */ protected function latestLogs(): array { $admins = []; try { $admins = Db::name('backend_admin')->column('nickname', 'id'); } catch (\Throwable $e) { $admins = []; } try { $rows = Db::name('backend_log') ->field('id, aid, action, ip, create_at') ->order('id', 'desc') ->limit(8) ->select() ->toArray(); } catch (\Throwable $e) { return []; } $list = []; foreach ($rows as $row) { $list[] = [ 'id' => (int) $row['id'], 'admin' => $admins[$row['aid']] ?? ('#' . $row['aid']), 'action' => (string) $row['action'], 'ip' => (string) $row['ip'], 'time' => (int) $row['create_at'], ]; } return $list; } /** * 安全取单列:表缺失 / 查询异常时返回空数组 * * @param string $table * @param string $field * @param int $since * @param int|null $until * @return array */ protected function safeColumn(string $table, string $field, int $since, int $until = null): array { try { $q = Db::name($table)->where($field, '>=', $since); if ($until !== null) { $q->where($field, '<=', $until); } return $q->column($field); } catch (\Throwable $e) { return []; } } /** * 安全求和:表缺失 / 查询异常时返回 0 * * @param string $table * @param string $field * @param callable|null $where * @return int */ protected function safeSum(string $table, string $field, callable $where = null): int { try { $q = Db::name($table); if ($where) { $where($q); } return (int) $q->sum($field); } catch (\Throwable $e) { return 0; } } /** * 热门统计(真实数据) * * @return \think\Response */ public function hotsearch() { $admin = Db::name('backend_admin')->count(); $user = Db::name('member_user')->count(); $article = Db::name('articles_article')->count(); $links = Db::name('common_links')->count(); $addon = Db::name('common_addon')->count(); $data = [ ['keywords' => '管理员', 'frequency' => $admin, 'userNums' => $admin], ['keywords' => '会员', 'frequency' => $user, 'userNums' => $user], ['keywords' => '文章', 'frequency' => $article, 'userNums' => $article], ['keywords' => '友链', 'frequency' => $links, 'userNums' => $links], ['keywords' => '插件', 'frequency' => $addon, 'userNums' => $addon], ]; $this->result->success($data); } /** * 热门内容(真实数据:最新文章) * * @return \think\Response */ public function hotTopic() { $list = Db::name('articles_article') ->field('id, title, author, cid, create_at') ->order('id', 'desc') ->limit(10) ->select() ->toArray(); $data = array_map(function ($item) { return [ 'id' => $item['id'], 'title' => $item['title'], 'username' => $item['author'] ?? '', 'channel' => $item['cid'] ?? '', 'href' => '', 'crt' => $item['create_at'] ?? 0, ]; }, $list); $this->result->success($data); } /** * 系统进度 / 环境信息(真实数据) * * @return \think\Response */ public function prograss() { $data = [ ['prograss' => 'PHP 版本', 'time' => PHP_VERSION, 'complete' => '已完成'], ['prograss' => 'ThinkPHP', 'time' => \think\facade\App::version(), 'complete' => '已完成'], ['prograss' => '安装状态', 'time' => is_file(root_path() . 'install.lock') ? '已安装' : '未安装', 'complete' => '已完成'], ['prograss' => '运行环境', 'time' => php_sapi_name(), 'complete' => '进行中'], ]; $this->result->success($data); } }