前言
在写一个自己的桌面应用程序的时候想要往首页上加个一言的调用API来彰显自己的代码水平之高超,然后自然要先研究研究API的调用规则、接口返回的数据结构嘛……
然后浏览器在v1.hitokoto.cn接口页面多刷新了几次,直接给我干出临时屏蔽了,看得出来我们需要自建一个接口来更加彰显我们的技术水平之无敌。
开整
芒果选择的技术路线是Docker安装,用的是宝塔面板,大体上还算是比较简单的,一个小时就能搞定。
注意事项:需要从Docker拉取两个镜像,本机端口8000和6379不得占用,8000端口需要在防火墙放行,以及本机最好没有正在运行的Redis实例。
从官方源获取镜像
宝塔面板安装过程省略,点开这个教程的大家想必都有正在线上的宝塔面板吧!
通过面板傻瓜式安装Docker,安装方式默认即可,然后在「线上镜像」中搜索。目标镜像全名是「hitokoto/api」,找到后选择「拉取」。
欣赏弹出命令行的拉取命令刷屏,一边等待拉取完毕。
若无问题的话,拉取应当不会花费过多时间,然后我们可以在本地镜像中找到刚刚拉取成功的镜像。
获取Redis镜像
为了避免Docker容器与主机之间剪不断理还乱的网络问题,我们可以通过Docker安装Redis,然后使用Docker的桥接网络模式来实现Hitokoto和Redis之间的网络通信。
- 优点:避免配置主机与容器之间的复杂网络连接;
- 缺点:Redis实例可以通过宝塔面板的软件商店安装和管理,通过Docker安装则无法使用宝塔的工具管理Redis。
在线镜像中搜索Redis,最前面的那个一万多的官方镜像就是我们需要的,拉取下来即可。
创建容器,启动镜像
转到「容器」,选择「创建容器」,先谁后谁并没有强制要求,比如我们先创建Hitokoto容器。
容器名称可以任意填,只要自己能认识就行;端口需要将8000暴露出来,镜像选择hitokoto的镜像,重启规则默认,更多设置中网络选择bridge(桥接),其他参数默认,下方有个环境变量暂时如下填写即可:
NODE_ENV=production
url=https://api-hitokoto.mangofanfan.cn
redis.host=172.17.0.2
分别说明环境为生产环境,访问接口的URL和Redis服务的地址,后两个可以自行更改,URL需要根据实际更改,Redis地址稍后再来改。
现在创建容器,然后查看容器日志,是一定会遇到报错的,报错信息是Redis无法连接。
接下来我们建立Redis容器,继续一波「创建」,镜像选择redis,端口开放6379,网络选择bridge,其他全部默认,Redis创建成功。
现在查看Redis在容器网络中的网络设置,把这个172.1.0.2扔到hitokoto的环境变量里的对应位置,现在hitokoto容器的日志应当能看出来已经正常运行。
部署验证
确认在防火墙中放行8000端口,访问服务器IP地址:8000,应当得到类似如下响应:
你可以随意刷新啦,反正这是你自己的API接口,是绝对不会翻脸不认人的()
进阶设置
下面的内容就是进阶设置了。
域名+无端口号作为调用接口
需要配合反向代理实用,在宝塔面板中操作如下:
- 新建一个网站,纯静态即可,无需任何环境,设置你想要的接口域名然后直接创建。
- 不要忘记在域名控制台解析哈,或者直接泛域名*一下。
- 为域名配置SSL证书(可选),大多数同学应该都有或付费或免费的证书然后开了强制HTTPS的,所以提醒一下。
- 设置反向代理:
访问域名,应当能够返回与访问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)
项目示例
- https://api-hitokoto.mangofanfan.cn API调用示例
- https://hitokoto.mangofanfan.cn API展示页示例
请登录后查看评论内容