$app($child); } /** * 首页 * @return \Illuminate\Http\Response */ public function main() { $hotFile = public_path('hot'); $manifestFile = public_path('manifest.json'); if (file_exists($hotFile)) { $array = Base::json2array(file_get_contents($hotFile)); $style = null; $script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js")); $proxyUri = Base::liveEnv('VSCODE_PROXY_URI'); if (is_string($proxyUri) && preg_match('/^https?:\/\//i', $proxyUri)) { $script = preg_replace('/^(https?:\/\/|\/\/)[^\/]+/', rtrim($proxyUri, '/'), $script, 1); } } else { $array = Base::json2array(file_get_contents($manifestFile)); $style = asset_main($array['resources/assets/js/app.js']['css'][0]); $script = asset_main($array['resources/assets/js/app.js']['file']); } return response()->view('main', [ 'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'), 'version' => Base::getVersion(), 'style' => $style, 'script' => $script, ]); } /** * 获取版本号 * @return \Illuminate\Http\RedirectResponse */ public function version() { return Redirect::to(Base::fillUrl('api/system/version'), 301); } /** * 健康检查 * @return string */ public function health() { return "ok"; } /** * 头像 * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse */ public function avatar() { $segment = Request::segment(2); if ($segment && preg_match('/.*?\.png$/i', $segment)) { $name = substr($segment, 0, -4); } else { $name = Request::input('name', 'D'); } $size = Request::input('size', 128); $color = Request::input('color'); $background = Request::input('background'); // 移除各种括号及其内容 $pattern = '/[((\[【{[<<『「](.*?)[))\]】}]>>』」]/u'; $name = preg_replace($pattern, '', $name) ?: preg_replace($pattern, '$1', $name); // 移除常见标识词(不区分大小写) $filterWords = [ // 测试相关 '测试', '测试号', '测试账号', '内测', '体验', '试用', 'test', 'testing', 'beta', // 账号相关 '账号', '帐号', '账户', '帐户', 'account', 'acc', 'id', 'uid', // 临时标识 '临时', '暂用', '备用', '主号', '副号', '小号', '大号', 'temp', 'temporary', 'backup', // 系统相关 '系统', '管理员', 'admin', 'administrator', 'system', 'sys', 'root', // 用户相关 '用户', 'user', '会员', 'member', 'vip', 'svip', 'mvip', 'premium', // 官方相关 '官方', '正式', '认证', 'official', 'verified', 'auth', // 客服相关 '客服', '售后', '服务', 'service', 'support', 'helper', 'assistant', // 游戏相关 'game', 'gaming', 'player', 'gamer', // 社交媒体相关 'ins', 'instagram', 'fb', 'facebook', 'tiktok', 'tweet', 'weibo', 'wechat', // 常见后缀 'official', 'real', 'fake', 'copy', 'channel', 'studio', 'team', 'group', // 职业相关 'dev', 'developer', 'designer', 'artist', 'writer', 'editor', // 其他 'bot', 'robot', 'auto', 'anonymous', 'guest', 'default', 'new', 'old' ]; $filterWords = array_map(function ($word) { return preg_quote($word, '/'); }, $filterWords); $name = preg_replace('/' . implode('|', $filterWords) . '/iu', '', $name) ?: $name; // 移除分隔符和特殊字符 $filterSymbols = [ // 常见分隔符 '-', '_', '=', '+', '/', '\\', '|', '~', '@', '#', '$', '%', '^', '&', '*', // 空格类字符 ' ', ' ', "\t", "\n", "\r", // 标点符号(中英文) '。', ',', '、', ';', ':', '?', '!', '.', '…', '‥', '′', '″', '℃', '.', ',', ';', ':', '?', '!', // 引号类(修正版) '"', "'", '‘', '’', '“', '”', '`', // 特殊符号 '★', '☆', '○', '●', '◎', '◇', '◆', '□', '■', '△', '▲', '▽', '▼', '♀', '♂', '♪', '♫', '♯', '♭', '♬', '→', '←', '↑', '↓', '↖', '↗', '↙', '↘', '√', '×', '÷', '±', '∵', '∴', '♠', '♥', '♣', '♦', // emoji 表情符号范围 '\x{1F300}-\x{1F9FF}', '\x{2600}-\x{26FF}', '\x{2700}-\x{27BF}', '\x{1F900}-\x{1F9FF}', '\x{1F600}-\x{1F64F}' ]; $filterSymbols = array_map(function ($symbol) { return preg_quote($symbol, '/'); }, $filterSymbols); $name = preg_replace('/[' . implode('', $filterSymbols) . ']/u', '', $name) ?: $name; // if (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $name)) { $name = mb_substr($name, mb_strlen($name) - 2); } if (empty($name)) { $name = 'D'; } if (empty($color)) { $color = '#ffffff'; $cacheKey = "avatarBackgroundColor::" . md5($name); $background = Cache::rememberForever($cacheKey, function () { return RandomColor::one(['luminosity' => 'dark']); }); } // $path = public_path('uploads/tmp/avatar/' . substr(md5($name), 0, 2)); $file = Base::joinPath($path, md5($name) . '.png'); if (file_exists($file)) { return response()->file($file, [ 'Pragma' => 'public', 'Cache-Control' => 'max-age=1814400', 'Content-type' => 'image/png', 'Expires' => gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400), ]); } Base::makeDir($path); // $avatar = new Avatar([ 'shape' => 'square', 'width' => $size, 'height' => $size, 'chars' => 2, 'fontSize' => $size / 2.9, 'uppercase' => true, 'fonts' => [resource_path('assets/statics/fonts/Source_Han_Sans_SC_Regular.otf')], 'foregrounds' => [$color], 'backgrounds' => [$background], 'border' => [ 'size' => 0, 'color' => 'foreground', 'radius' => 0, ], ]); return response($avatar->create($name)->save($file)) ->header('Pragma', 'public') ->header('Cache-Control', 'max-age=1814400') ->header('Content-type', 'image/png') ->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400)); } /** * 接口文档 * @return \Illuminate\Http\RedirectResponse */ public function api() { return Redirect::to(Base::fillUrl('docs/index.html'), 301); } /** * 系统定时任务,限制内网访问(1分钟/次) * @return string */ public function crontab() { if (!Base::is_internal_ip(Base::getIp())) { // 限制内网访问 return "Forbidden Access"; } // 自动归档 Task::deliver(new AutoArchivedTask()); // 邮件通知 Task::deliver(new EmailNoticeTask()); // App推送 Task::deliver(new AppPushTask()); // 删除过期的临时表数据 Task::deliver(new DeleteTmpTask('tmp_msgs', 1)); Task::deliver(new DeleteTmpTask('tmp')); Task::deliver(new DeleteTmpTask('task_worker', 12)); Task::deliver(new DeleteTmpTask('file')); Task::deliver(new DeleteTmpTask('tmp_file', 24)); Task::deliver(new DeleteTmpTask('user_device', 24)); // 删除机器人消息 Task::deliver(new DeleteBotMsgTask()); // 周期任务 Task::deliver(new LoopTask()); // 签到提醒 Task::deliver(new CheckinRemindTask()); // 获取笑话/心灵鸡汤 Task::deliver(new JokeSoupTask()); // 未领取任务通知 Task::deliver(new UnclaimedTaskRemindTask()); // 关闭会议室 Task::deliver(new CloseMeetingRoomTask()); // ZincSearch 同步 Task::deliver(new ZincSearchSyncTask()); return "success"; } /** * 桌面客户端发布 */ public function desktop__publish($name = '') { $publishVersion = Request::header('publish-version'); $latestFile = public_path("uploads/desktop/latest"); $latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1"; if (strtolower($name) === 'latest') { $name = $latestVersion; } // 上传(header 中包含 publish-version) if (preg_match("/^\d+\.\d+\.\d+$/", $publishVersion)) { // 判断密钥 $publishKey = Request::header('publish-key'); if ($publishKey !== env('APP_KEY')) { return Base::retError("key error"); } // 判断版本 $action = Request::get('action'); $draftPath = "uploads/desktop-draft/{$publishVersion}/"; if ($action === 'release') { // 将草稿版本发布为正式版本 $draftPath = public_path($draftPath); $releasePath = public_path("uploads/desktop/{$publishVersion}/"); if (!file_exists($draftPath)) { return Base::retError("draft version not exists"); } if (file_exists($releasePath)) { Base::deleteDirAndFile($releasePath); } Base::copyDirectory($draftPath, $releasePath); file_put_contents($latestFile, $publishVersion); // 删除旧版本 Base::deleteDirAndFile(public_path("uploads/desktop-draft")); $dirs = Base::recursiveDirs(public_path("uploads/desktop"), false); sort($dirs); $num = 0; foreach ($dirs as $dir) { if (!preg_match("/\/\d+\.\d+\.\d+$/", $dir)) { continue; } $num++; if ($num < 5) { continue; // 保留最新的5个版本 } if (filemtime($dir) > time() - 3600 * 24 * 30) { continue; // 保留最近30天的版本 } Base::deleteDirAndFile($dir); } return Base::retSuccess('success'); } // 上传草稿版本 return Base::upload([ "file" => Request::file('file'), "type" => 'publish', "path" => $draftPath, "saveName" => true, ]); } // 列表(访问路径 desktop/publish/{version}) if (preg_match("/^v*(\d+\.\d+\.\d+)$/", $name, $match)) { $paths = [ "uploads/desktop/{$match[1]}/", "uploads/desktop/v{$match[1]}/", "uploads/desktop-draft/{$match[1]}/", "uploads/desktop-draft/v{$match[1]}/", ]; $avaiPath = null; foreach ($paths as $path) { $dirPath = public_path($path); $isDraft = str_contains($path, 'draft'); if (is_dir($dirPath)) { $avaiPath = $path; break; } } abort_if(empty($avaiPath), 404); $lists = Base::recursiveFiles($dirPath, false); $files = []; foreach ($lists as $file) { if (preg_match('/\.(zip|yml|yaml|blockmap)$/i', $file) || str_ends_with($file, '-win.exe')) { continue; } $fileName = basename($file, $dirPath); $fileSize = filesize($file); $files[] = [ 'name' => $fileName, 'time' => date("Y-m-d H:i:s", filemtime($file)), 'size' => $fileSize > 0 ? Base::readableBytes($fileSize) : 0, 'url' => Base::fillUrl(Base::joinPath($avaiPath, $fileName)), ]; } $otherVersion = []; $dirs = Base::recursiveDirs(public_path("uploads/desktop"), false); foreach ($dirs as $dir) { if (!preg_match("/\/\d+\.\d+\.\d+$/", $dir)) { continue; } $version = basename($dir); if ($version === $match[1]) { continue; } $otherVersion[] = [ 'version' => $version, 'url' => Base::fillUrl("desktop/publish/{$version}"), ]; } // return view('desktop', [ 'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'), 'version' => $match[1], 'files' => $files, 'is_draft' => $isDraft, 'latest_version' => $latestVersion, 'other_version' => array_reverse($otherVersion), ]); } // 下载(Latest 版本内的文件,访问路径 desktop/publish/{fileName}) if ($name) { $filePath = public_path("uploads/desktop/{$latestVersion}/{$name}"); if (file_exists($filePath)) { return Response::download($filePath); } } // 404 abort(404); } /** * Drawio 图标搜索 * @return array|mixed */ public function drawio__iconsearch() { $query = trim(Request::input('q')); $page = trim(Request::input('p')); $size = trim(Request::input('c')); return Extranet::drawioIconSearch($query, $page, $size); } /** * 预览文件 * @return array|mixed */ public function online__preview() { $key = trim(Request::input('key')); // $data = parse_url($key); $path = Arr::get($data, 'path'); $file = public_path($path); // 防止 ../ 穿越获取到系统文件 abort_if(!str_starts_with(realpath($file), public_path()), 404); // 如果文件不存在,直接返回 404 abort_if(!file_exists($file), 404); // parse_str($data['query'], $query); $name = Arr::get($query, 'name'); $ext = strtolower(Arr::get($query, 'ext')); $userAgent = strtolower(Request::server('HTTP_USER_AGENT')); if ($ext === 'pdf') { // 文件超过 10m 不支持在线预览,提示下载 if (filesize($file) > 10 * 1024 * 1024) { return view('download', [ 'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'), 'name' => $name, 'size' => Base::readableBytes(filesize($file)), 'url' => Base::fillUrl($path), 'button' => Doo::translate('点击下载'), ]); } // 浏览器类型 $browser = 'none'; if (str_contains($userAgent, 'chrome') || str_contains($userAgent, 'android_kuaifan_eeui')) { $browser = str_contains($userAgent, 'android_kuaifan_eeui') ? 'android-mobile' : 'chrome-desktop'; } elseif (str_contains($userAgent, 'safari') || str_contains($userAgent, 'ios_kuaifan_eeui')) { $browser = str_contains($userAgent, 'ios_kuaifan_eeui') ? 'safari-mobile' : 'safari-desktop'; } // electron 直接在线预览查看 if (str_contains($userAgent, 'electron') || str_contains($browser, 'desktop')) { return Response::download($file, $name, [ 'Content-Type' => 'application/pdf' ], 'inline'); } // EEUI App 直接在线预览查看 if (Base::isEEUIApp() && Base::judgeClientVersion("0.34.47")) { if ($browser === 'safari-mobile') { $redirectUrl = Base::fillUrl($path); return << window.top.postMessage({ action: "eeuiAppSendMessage", data: [ { action: 'setPageData', // 设置页面数据 data: { showProgress: true, titleFixed: true, urlFixed: true, } }, { action: 'createTarget', // 创建目标(访问新地址) url: "{$redirectUrl}", } ] }, "*") EOF; } } } // if (in_array($ext, File::localExt)) { $url = Base::fillUrl($path); } else { $url = 'http://nginx/' . $path; } $url = Base::urlAddparameter($url, [ 'fullfilename' => Base::rightDelete($name, '.' . $ext) . '_' . filemtime($file) . '.' . $ext ]); $redirectUrl = Base::fillUrl("fileview/onlinePreview?url=" . urlencode(base64_encode($url))); return Redirect::to($redirectUrl, 301); } }