帆域

[宝塔面板+Docker]自建Hitokoto一言API

前言

在写一个自己的桌面应用程序的时候想要往首页上加个一言的调用API来彰显自己的代码水平之高超,然后自然要先研究研究API的调用规则、接口返回的数据结构嘛……

然后浏览器在v1.hitokoto.cn接口页面多刷新了几次,直接给我干出临时屏蔽了,看得出来我们需要自建一个接口来更加彰显我们的技术水平之无敌

开整

芒果选择的技术路线是Docker安装,用的是宝塔面板,大体上还算是比较简单的,一个小时就能搞定。

注意事项:需要从Docker拉取两个镜像,本机端口8000和6379不得占用,8000端口需要在防火墙放行,以及本机最好没有正在运行的Redis实例。

从官方源获取镜像

宝塔面板安装过程省略,点开这个教程的大家想必都有正在线上的宝塔面板吧!

通过面板傻瓜式安装Docker,安装方式默认即可,然后在「线上镜像」中搜索。目标镜像全名是「hitokoto/api」,找到后选择「拉取」。

图片[1]-[宝塔面板+Docker]自建Hitokoto一言API
第二个就是我们需要的镜像。

欣赏弹出命令行的拉取命令刷屏,一边等待拉取完毕。

此时如果不断提示「Retry …… second(s)」刷屏,则可能是由于服务器的地缘位置问题导致Docker无法拉取成功,即使花费远超过正常时间之后,命令行提示拉取成功,但实际上依然是失败的。

若无问题的话,拉取应当不会花费过多时间,然后我们可以在本地镜像中找到刚刚拉取成功的镜像。

获取Redis镜像

为了避免Docker容器与主机之间剪不断理还乱的网络问题,我们可以通过Docker安装Redis,然后使用Docker的桥接网络模式来实现Hitokoto和Redis之间的网络通信。

  • 优点:避免配置主机与容器之间的复杂网络连接;
  • 缺点:Redis实例可以通过宝塔面板的软件商店安装和管理,通过Docker安装则无法使用宝塔的工具管理Redis。

在线镜像中搜索Redis,最前面的那个一万多的官方镜像就是我们需要的,拉取下来即可。

创建容器,启动镜像

转到「容器」,选择「创建容器」,先谁后谁并没有强制要求,比如我们先创建Hitokoto容器。

图片[2]-[宝塔面板+Docker]自建Hitokoto一言API
容器hitokoto创建参数

容器名称可以任意填,只要自己能认识就行;端口需要将8000暴露出来,镜像选择hitokoto的镜像,重启规则默认,更多设置中网络选择bridge(桥接),其他参数默认,下方有个环境变量暂时如下填写即可:

NODE_ENV=production
url=https://api-hitokoto.mangofanfan.cn
redis.host=172.17.0.2

分别说明环境为生产环境访问接口的URLRedis服务的地址,后两个可以自行更改,URL需要根据实际更改,Redis地址稍后再来改。

现在创建容器,然后查看容器日志,是一定会遇到报错的,报错信息是Redis无法连接。

接下来我们建立Redis容器,继续一波「创建」,镜像选择redis,端口开放6379,网络选择bridge,其他全部默认,Redis创建成功。

现在查看Redis在容器网络中的网络设置,把这个172.1.0.2扔到hitokoto的环境变量里的对应位置,现在hitokoto容器的日志应当能看出来已经正常运行。

图片[3]-[宝塔面板+Docker]自建Hitokoto一言API
容器Redis的容器网络

部署验证

确认在防火墙中放行8000端口,访问服务器IP地址:8000,应当得到类似如下响应:

图片[4]-[宝塔面板+Docker]自建Hitokoto一言API
这都能有原!!!()

你可以随意刷新啦,反正这是你自己的API接口,是绝对不会翻脸不认人的()

进阶设置

下面的内容就是进阶设置了。

域名+无端口号作为调用接口

需要配合反向代理实用,在宝塔面板中操作如下:

  1. 新建一个网站,纯静态即可,无需任何环境,设置你想要的接口域名然后直接创建。
  2. 不要忘记在域名控制台解析哈,或者直接泛域名*一下。
  3. 为域名配置SSL证书(可选),大多数同学应该都有或付费或免费的证书然后开了强制HTTPS的,所以提醒一下。
  4. 设置反向代理:
图片[5]-[宝塔面板+Docker]自建Hitokoto一言API
反向代理设置

访问域名,应当能够返回与访问IP地址:8000结构一致的数据。

配套一个HTML的展示页

宝塔面板再新建一个静态网站,扔个index.html进去,里面调用一下我们的接口,然后稍微写点样式就行。

我为什么说得这么轻松是因为我也不是很懂,这边是我根据参考资料第二行的大佬分享的源码改的。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>帆域镜像一言接口 | Fan Mirror Hitokoto</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #e8e8ff;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }

        #hitokoto {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            text-align: center;
        }

        #hitokoto_text {
            text-decoration: none;
            color: #007bff;
            font-size: 1.2em;
            transition: color 0.3s ease;
        }

        #hitokoto_text:hover {
            color: #0056b3;
        }

        #provider {
            margin-top: 10px;
            font-size: 0.9em;
            color: #666;
        }
    </style>
</head>

<body>
<div id="hitokoto">
    <a href="#" id="hitokoto_text">:D 获取中...</a>
    <p id="provider">接口数据来自<a href="https://hitokoto.cn/" style="text-underline: #e8e8ff; color: #666666">一言官方</a></p>
</div>

<script>
    const xhr = new XMLHttpRequest();
    xhr.open('get', 'https://api-hitokoto.mangofanfan.cn/');
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            const data = JSON.parse(xhr.responseText);
            const hitokoto = document.querySelector('#hitokoto_text');
            hitokoto.href = `https://hitokoto.cn/?uuid=${data.uuid}`;
            hitokoto.innerText = data.hitokoto;
        }
    };
    xhr.send();
</script>
</body>

</html>

怎么「作弊」??

我……我有两个服务器……一个在新加坡,一个在南京……

然后在新加坡的服务器用宝塔面板拉取镜像,导出,下载到本地,上传到南京的服务器,导入……

……

关于为宝塔面板设置Docker镜像加速,可参阅官方论坛的【教程贴】如何在宝塔面板更换Docker加速站 – Linux面板 – 宝塔面板论坛 (bt.cn),可恶为什么我昨天没想着先搜搜看

为什么要用Python调用这个API?

我也不知道,可能显得我NB吧。

import requests
import json
import logging

from PySide2.QtCore import QObject, QThread, Signal

from widget.function_setting import cfg

logger = logging.getLogger("FanTools.Hitokoto")


class yi_yan(QObject):
    GUIUpdateSignal = Signal(dict)

    def __init__(self):
        """
        需要在调用self.setApi()之后,self.api才能获得值。
        或直接调用get方法。
        """
        super().__init__()
        self.dict = {"official": "https://v1.hitokoto.cn/",
                      "hitokoto": "https://v1.hitokoto.cn/",
                      "fan_mirror": "https://api-hitokoto.mangofanfan.cn/"}
        self.api: str = None
        self._name: str = None
        self.Thread_Timer = QThread()
        self.Worker_Timer = Worker_Timer()
        self.Worker_Timer.updateSignal.connect(self.get)
        self.Worker_Timer.moveToThread(self.Thread_Timer)
        self.Thread_Timer.start()

    def start(self):
        """
        启用一言循环,定时获取新的一言并通过信号发送到GUI。
        :return: None
        """
        self.Worker_Timer.runSignal.emit()
        logger.debug("已启动一言API更新的定时线程。")
        return None

    def setApi(self, name: str):
        """
        设置一言API的调用地址,设置之后self.api会获得值。
        无需手动调用,可以直接调用分装好的get方法。
        :param name: API名称,多个单词之间需要用下划线连接,所有字母不区分大小写。
        :return: None
        """
        self._name = name.lower()
        self.api = self.dict[self._name]
        return None

    def get(self):
        self.setApi(cfg.get(cfg.YiYanAPI))
        self.GUIUpdateSignal.emit(self._get())
        return None

    def _get(self):
        """
        (内部方法)获取一条新的一言,避免直接外部调用此方法。
        :return: None
        """
        if cfg.get(cfg.ProxyEnable):
            proxies = {
                'http': cfg.get(cfg.ProxyHttp),
                'https': cfg.get(cfg.ProxyHttps),
            }
        else:
            proxies = {}

        res = requests.get(self.api, proxies=proxies)
        data = json.loads(res.content)

        _from = data["from"]
        _from_who = data["from_who"]
        if _from_who != "null" and _from_who is not None and _from_who != "None":
            origin = f"{_from_who} - {_from}"
        else:
            origin = _from

        # 处理返回的数据
        result = {"content": data["hitokoto"],
                  "origin": origin,
                  "id": str(data["id"])}

        return result


class Worker_Timer(QObject):
    runSignal = Signal()
    updateSignal = Signal()

    def __init__(self):
        super().__init__()

        self.runSignal.connect(self.run)
        self.keepRunning = True

    def stopRunning(self):
        self.keepRunning = False
        return None

    def run(self):
        from time import sleep
        while self.keepRunning:
            self.updateSignal.emit()
            logger.debug("发送一次一言更新信号。")
            _time = cfg.get(cfg.TimeSleep)
            sleep(_time)
图片[6]-[宝塔面板+Docker]自建Hitokoto一言API
调用示例

项目示例

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞30赞赏 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容