KioskApi.php 8.07 KB
<?php
declare(strict_types=1);

namespace app\controller;

use app\BaseController;
use app\service\KioskService;
use think\App;
use think\file\UploadedFile;
use think\Response;

class KioskApi extends BaseController
{
    protected KioskService $svc;

    public function __construct(App $app)
    {
        parent::__construct($app);
        $this->svc = new KioskService();
    }

    public function bundle(): Response
    {
        try {
            $data = $this->svc->getBundle();
            return json(['code' => 0, 'data' => $data]);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    /** 天文知识库专用列表(与 bundle 内 knowledge 同源,便于页面独立刷新) */
    public function knowledge(): Response
    {
        try {
            return json(['code' => 0, 'data' => ['entries' => $this->svc->getKnowledgeList()]]);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function dataDisplay(): Response
    {
        try {
            return json(['code' => 0, 'data' => $this->svc->getDataDisplay()]);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function observatoryHistory(): Response
    {
        try {
            return json(['code' => 0, 'data' => $this->svc->getObservatoryHistory()]);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function saveHomeBackgrounds(): Response
    {
        $urls = $this->request->post('urls');
        if (!is_array($urls)) {
            return json(['code' => 400, 'msg' => 'urls must be array'], 400);
        }
        $clean = array_values(array_filter($urls, fn ($u) => is_string($u) && $u !== ''));
        try {
            $this->svc->setJsonKv(\app\common\KioskDefaults::KEY_HOME_BG, $clean);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function saveWelcome(): Response
    {
        $w = $this->request->post();
        $payload = [
            'zh-CN' => (string) ($w['zh-CN'] ?? ''),
            'en'    => (string) ($w['en'] ?? ''),
            'bo'    => (string) ($w['bo'] ?? ''),
        ];
        try {
            $this->svc->setJsonKv(\app\common\KioskDefaults::KEY_WELCOME, $payload);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function saveGuide(): Response
    {
        $g = $this->request->post('guide');
        if (!is_array($g)) {
            return json(['code' => 400, 'msg' => 'guide must be object'], 400);
        }
        try {
            $this->svc->setJsonKv(\app\common\KioskDefaults::KEY_GUIDE, $g);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    /** 共享天文台视频源(HLS/WebRTC/RTSP + url + note) */
    public function saveVideoSource(): Response
    {
        $body = $this->request->post();
        $protocol = (string) ($body['protocol'] ?? 'HLS');
        if (!in_array($protocol, ['HLS', 'WebRTC', 'RTSP'], true)) {
            return json(['code' => 400, 'msg' => 'protocol must be HLS, WebRTC or RTSP'], 400);
        }
        $payload = [
            'protocol' => $protocol,
            'url'      => (string) ($body['url'] ?? ''),
            'note'     => (string) ($body['note'] ?? ''),
        ];
        try {
            $this->svc->setJsonKv(\app\common\KioskDefaults::KEY_VIDEO_SOURCE, $payload);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    /** 轮播背景 URL 列表(与首页背景分开存储) */
    public function saveCarouselBackgrounds(): Response
    {
        $urls = $this->request->post('urls');
        if (!is_array($urls)) {
            return json(['code' => 400, 'msg' => 'urls must be array'], 400);
        }
        $clean = array_values(array_filter($urls, fn ($u) => is_string($u) && $u !== ''));
        try {
            $this->svc->setJsonKv(\app\common\KioskDefaults::KEY_CAROUSEL_BG, $clean);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    public function saveKnowledge(): Response
    {
        $entries = $this->request->post('entries');
        if (!is_array($entries)) {
            return json(['code' => 400, 'msg' => 'entries must be array'], 400);
        }
        try {
            $this->svc->replaceKnowledge($entries);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }

    /** 知识库封面 / 视频 multipart 上传,返回 `/uploads/knowledge/...` 供 sync 写入库表 */
    public function uploadKnowledgeMedia(): Response
    {
        /** @var UploadedFile|null $file */
        $file = $this->request->file('file');
        if (!$file) {
            return json([
                'code' => 400,
                'msg'  => '未收到文件(大文件请调大 PHP post_max_size、upload_max_filesize 与 Nginx client_max_body_size)',
            ], 400);
        }

        $maxBytes = 2147483648; // 2GiB,约可覆盖多数 10 分钟以内压缩视频

        if (!$file->isValid()) {
            return json([
                'code' => 400,
                'msg'  => '上传未成功(常见原因:超过 PHP upload_max_filesize / post_max_size)',
            ], 400);
        }

        if ($file->getSize() > $maxBytes) {
            return json(['code' => 400, 'msg' => '文件超过 2GB 上限'], 400);
        }

        $ext = strtolower($file->getOriginalExtension());
        if ($ext === 'jpeg') {
            $ext = 'jpg';
        }
        if ($ext === '') {
            $mime = strtolower($file->getOriginalMime());
            $ext = match ($mime) {
                'image/jpeg' => 'jpg',
                'image/png'  => 'png',
                'image/gif'  => 'gif',
                'image/webp' => 'webp',
                'video/mp4'  => 'mp4',
                'video/webm' => 'webm',
                'video/quicktime' => 'mov',
                'video/x-matroska' => 'mkv',
                default      => '',
            };
        }

        $allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'mov', 'mkv'];
        if (!in_array($ext, $allowed, true)) {
            return json(['code' => 400, 'msg' => '不支持的文件类型'], 400);
        }

        $dir = public_path('uploads/knowledge');
        if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
            return json(['code' => 500, 'msg' => '无法创建上传目录'], 500);
        }

        $name = bin2hex(random_bytes(12)) . '.' . $ext;

        try {
            $file->move($dir, $name);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }

        $url = '/uploads/knowledge/' . $name;

        return json(['code' => 0, 'msg' => 'ok', 'data' => ['url' => $url]]);
    }

    public function seedKnowledge(): Response
    {
        $entries = \app\common\KioskDefaults::knowledge();
        if ($entries === []) {
            return json([
                'code' => 400,
                'msg'  => '演示知识种子已关闭,请使用 knowledge/sync 同步真实条目,勿调用本接口清空库表',
            ], 400);
        }
        try {
            $this->svc->replaceKnowledge($entries);
            return json(['code' => 0, 'msg' => 'ok']);
        } catch (\Throwable $e) {
            return json(['code' => 500, 'msg' => $e->getMessage()], 500);
        }
    }
}