添加接口
This commit is contained in:
parent
e9e156b785
commit
583f7f4b4f
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
test.md
|
13
.hintrc
Normal file
13
.hintrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": [
|
||||
"development"
|
||||
],
|
||||
"hints": {
|
||||
"meta-viewport": "off",
|
||||
"disown-opener": "off",
|
||||
"button-type": "off",
|
||||
"axe/aria": "off",
|
||||
"no-inline-styles": "off",
|
||||
"axe/text-alternatives": "off"
|
||||
}
|
||||
}
|
154
README.md
Normal file
154
README.md
Normal file
@ -0,0 +1,154 @@
|
||||
<div align="center">
|
||||
<img alt="logo" height="120" src="./public/favicon.png" width="120"/>
|
||||
<h2>今日热榜</h2>
|
||||
<p>一个聚合热门数据的 API 接口</p>
|
||||
</div>
|
||||
|
||||
## 示例
|
||||
|
||||
> 这里是使用该 API 的示例站点
|
||||
|
||||
- [今日热榜 - https://hot.imsyy.top/](https://hot.imsyy.top/)
|
||||
|
||||
## 总览
|
||||
|
||||
> 🟢 状态正常
|
||||
> 🟠 可能失效
|
||||
> 🔴 无法使用
|
||||
|
||||
| **站点** | **类别** | **调用名称** | **状态** |
|
||||
|--------|---------|-----------|--------|
|
||||
| 哔哩哔哩 | 热门榜 | bilibili | 🟢 |
|
||||
| 知乎 | 热榜 | zhihu | 🟢 |
|
||||
| 百度 | 热搜榜 | baidu | 🟢 |
|
||||
| 百度贴吧 | 热议榜 | tieba | 🟢 |
|
||||
| 少数派 | 热榜 | sspai | 🟢 |
|
||||
| IT之家 | 热榜 | ithome | 🟠 |
|
||||
| 澎湃新闻 | 热榜 | thepaper | 🟢 |
|
||||
| 今日头条 | 热榜 | toutiao | 🟢 |
|
||||
| 微博热搜 | 热搜榜 | weibo | 🟢 |
|
||||
| 36氪 | 热榜 | 36kr | 🟢 |
|
||||
| 腾讯新闻 | 热点榜 | newsqq | 🔴 |
|
||||
|
||||
## 调用
|
||||
|
||||
### 获取榜单数据
|
||||
|
||||
> 获取数据只需在域名后面加上上方列表中的调用名称即可
|
||||
|
||||
```http
|
||||
GET https://api-hot.imsyy.top/bilibili/
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>调用示例</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"title": "哔哩哔哩", // 榜单名称
|
||||
"subtitle": "热门榜", // 榜单类别
|
||||
"from": "server", // 此处返回是最新数据还是缓存
|
||||
"total": 100, // 数据总数
|
||||
"updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间
|
||||
"data": [
|
||||
{
|
||||
"id": "BV1E84y1A7z2",
|
||||
"title": "假如我的校园是一款RPG游戏!",
|
||||
"desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!",
|
||||
"pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg",
|
||||
"owner": {
|
||||
"mid": 424658638,
|
||||
"name": "四夕小田木_已黑化_",
|
||||
"face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg"
|
||||
},
|
||||
"data": {
|
||||
"aid": 610872610,
|
||||
"view": 4178745,
|
||||
"danmaku": 4229,
|
||||
"reply": 5317,
|
||||
"favorite": 91020,
|
||||
"coin": 133596,
|
||||
"share": 46227,
|
||||
"now_rank": 0,
|
||||
"his_rank": 1,
|
||||
"like": 616519,
|
||||
"dislike": 0,
|
||||
"vt": 0,
|
||||
"vv": 0
|
||||
},
|
||||
"url": "https://b23.tv/BV1E84y1A7z2",
|
||||
"mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### 获取榜单最新数据
|
||||
|
||||
> 获取最新数据只需在原链接后面加上 `/new`,这样就会直接从服务端拉取最新数据,不会从本地缓存中读取
|
||||
|
||||
```http
|
||||
GET https://api-hot.imsyy.top/bilibili/new
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>调用示例</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"title": "哔哩哔哩", // 榜单名称
|
||||
"subtitle": "热门榜", // 榜单类别
|
||||
"total": 100, // 数据总数
|
||||
"updateTime": "2023-03-14T07:40:51.846Z", // 数据获取时间
|
||||
"data": [
|
||||
{
|
||||
"id": "BV1E84y1A7z2",
|
||||
"title": "假如我的校园是一款RPG游戏!",
|
||||
"desc": "所有取景都是在学校里面拍的,都是真实存在的场景哦!",
|
||||
"pic": "http://i2.hdslb.com/bfs/archive/a24e442d0aae6d488db023c4ddcb450e9f2bf5f3.jpg",
|
||||
"owner": {
|
||||
"mid": 424658638,
|
||||
"name": "四夕小田木_已黑化_",
|
||||
"face": "https://i1.hdslb.com/bfs/face/afd9ba47933edc4842ccbeba2891a25465d1cf77.jpg"
|
||||
},
|
||||
"data": {
|
||||
"aid": 610872610,
|
||||
"view": 4178745,
|
||||
"danmaku": 4229,
|
||||
"reply": 5317,
|
||||
"favorite": 91020,
|
||||
"coin": 133596,
|
||||
"share": 46227,
|
||||
"now_rank": 0,
|
||||
"his_rank": 1,
|
||||
"like": 616519,
|
||||
"dislike": 0,
|
||||
"vt": 0,
|
||||
"vv": 0
|
||||
},
|
||||
"url": "https://b23.tv/BV1E84y1A7z2",
|
||||
"mobileUrl": "https://m.bilibili.com/video/BV1E84y1A7z2"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## 其他
|
||||
|
||||
- 本项目为了避免频繁请求官方数据,默认对数据做了缓存处理,默认为 `30` 分钟,如需更改,请自行前往 `utils\cacheData.js` 文件修改
|
||||
- 本项目部分接口使用了 **页面爬虫**,若违反对应页面的相关规则,请 **及时通知我去除该接口**
|
||||
|
||||
## 免责声明
|
||||
|
||||
- 本项目提供的 `API` 仅供开发者进行技术研究和开发测试使用。使用该 `API` 获取的信息仅供参考,不代表本项目对信息的准确性、可靠性、合法性、完整性作出任何承诺或保证。本项目不对任何因使用该 `API` 获取信息而导致的任何直接或间接损失负责。本项目保留随时更改 `API` 接口地址、接口协议、接口参数及其他相关内容的权利。本项目对使用者使用 `API` 的行为不承担任何直接或间接的法律责任
|
||||
- 本项目并未与相关信息提供方建立任何关联或合作关系,获取的信息均来自公开渠道,如因使用该 `API` 获取信息而产生的任何法律责任,由使用者自行承担
|
||||
- 本项目对使用 `API` 获取的信息进行了最大限度的筛选和整理,但不保证信息的准确性和完整性。使用 `API` 获取信息时,请务必自行核实信息的真实性和可靠性,谨慎处理相关事项
|
||||
- 本项目保留对 `API` 的随时更改、停用、限制使用等措施的权利。任何因使用本 `API` 产生的损失,本项目不负担任何赔偿和责任
|
89
app.js
Normal file
89
app.js
Normal file
@ -0,0 +1,89 @@
|
||||
require("dotenv").config();
|
||||
const Koa = require("koa");
|
||||
const bodyParser = require("koa-bodyparser");
|
||||
const cors = require("koa2-cors");
|
||||
const serve = require("koa-static");
|
||||
const views = require("koa-views");
|
||||
|
||||
const app = new Koa();
|
||||
const net = require("net");
|
||||
const router = require("./routes");
|
||||
|
||||
// 配置信息
|
||||
let domain = process.env.ALLOWED_DOMAIN || "*";
|
||||
let port = process.env.PORT || 6688;
|
||||
|
||||
// 解析请求体
|
||||
app.use(bodyParser());
|
||||
|
||||
// 静态文件目录
|
||||
app.use(serve(__dirname + "/public"));
|
||||
app.use(views(__dirname + "/public"));
|
||||
|
||||
// 跨域
|
||||
app.use(
|
||||
cors({
|
||||
origin: domain,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
if (domain === "*") {
|
||||
await next();
|
||||
} else {
|
||||
if (ctx.headers.referer !== domain) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
code: 400,
|
||||
message: "请通过正确的域名访问",
|
||||
};
|
||||
} else {
|
||||
await next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 使用路由中间件
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
// 启动应用程序并监听端口
|
||||
const startApp = (port) => {
|
||||
app.listen(port, () => {
|
||||
console.log(`成功在 ${port} 端口上运行`);
|
||||
});
|
||||
};
|
||||
|
||||
// 检测端口是否被占用
|
||||
const checkPort = (port) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net
|
||||
.createServer()
|
||||
.once("error", (err) => {
|
||||
if (err.code === "EADDRINUSE") {
|
||||
console.log(`端口 ${port} 已被占用, 正在尝试其他端口...`);
|
||||
server.close();
|
||||
resolve(false);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
.once("listening", () => {
|
||||
server.close();
|
||||
resolve(true);
|
||||
})
|
||||
.listen(port);
|
||||
});
|
||||
};
|
||||
|
||||
// 尝试启动应用程序
|
||||
const tryStartApp = async (port) => {
|
||||
let isPortAvailable = await checkPort(port);
|
||||
while (!isPortAvailable) {
|
||||
port++;
|
||||
isPortAvailable = await checkPort(port);
|
||||
}
|
||||
startApp(port);
|
||||
};
|
||||
|
||||
tryStartApp(port);
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "dailyhot_api",
|
||||
"version": "0.0.1",
|
||||
"description": "一个今日热榜 .",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js"
|
||||
},
|
||||
"author": "imsyy",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dotenv": "^16.0.3",
|
||||
"koa": "^2.14.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"koa-router": "^12.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa-views": "^8.0.0",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"node-cache": "^5.1.2"
|
||||
}
|
||||
}
|
1131
pnpm-lock.yaml
Normal file
1131
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
195
public/404.html
Normal file
195
public/404.html
Normal file
@ -0,0 +1,195 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>404 | Linkbook API</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="https://img.imsyy.top/logo/imsyy.png"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--text-color: #000;
|
||||
--text-color-gray: #888;
|
||||
--text-color-hover: #fff;
|
||||
--icon-color: #444;
|
||||
}
|
||||
html.dark-mode {
|
||||
--text-color: #fff;
|
||||
--text-color-gray: #888;
|
||||
--text-color-hover: #3c3c3c;
|
||||
--icon-color: #cbcbcb;
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: var(--text-color-hover);
|
||||
color: var(--text-color);
|
||||
font-family: "PingFang SC", "Open Sans", "Microsoft YaHei", sans-serif;
|
||||
transition: background-color 0.5s, color 0.5s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
.dark-mode body {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--text-color-gray);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--text-color);
|
||||
}
|
||||
.ico {
|
||||
margin: 4rem 2rem;
|
||||
font-size: 6rem;
|
||||
color: var(--text-color-gray);
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text {
|
||||
margin: 1rem;
|
||||
}
|
||||
.control button {
|
||||
background-color: var(--text-color-hover);
|
||||
border: var(--text-color) solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.5s ease;
|
||||
margin: 1rem 0.2rem;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.control button:hover {
|
||||
border: var(--text-color) solid;
|
||||
background: var(--text-color);
|
||||
color: var(--text-color-hover);
|
||||
}
|
||||
.control button i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
footer {
|
||||
line-height: 1.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 140px;
|
||||
}
|
||||
footer .social {
|
||||
color: var(--icon-color);
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
footer .social i {
|
||||
margin: 0 12px;
|
||||
}
|
||||
footer .point::before {
|
||||
content: "·";
|
||||
color: var(--text-color-gray);
|
||||
}
|
||||
.power,
|
||||
.icp {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="ico"><i class="fa-solid fa-circle-exclamation"></i></div>
|
||||
<div class="title">404 Not Found</div>
|
||||
<div class="text">请检查您的路径</div>
|
||||
<div class="control">
|
||||
<button onclick="window.location.href = '/'">
|
||||
<i class="fa-solid fa-house"></i>
|
||||
<span>回到首页</span>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="social">
|
||||
<i class="fa-brands fa-github" onclick="socialJump('github')"></i>
|
||||
<div class="point"></div>
|
||||
<i class="fa-solid fa-house" onclick="socialJump('home')"></i>
|
||||
<div class="point"></div>
|
||||
<i class="fa-solid fa-envelope" onclick="socialJump('email')"></i>
|
||||
</div>
|
||||
<div class="power">
|
||||
Copyright © 2020
|
||||
<script>
|
||||
document.write(" - " + new Date().getFullYear());
|
||||
</script>
|
||||
<a href="https://imsyy.top/" target="_blank">無名</a>
|
||||
</div>
|
||||
<div class="icp">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank"
|
||||
>豫ICP备2022018134号-1</a
|
||||
>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
// 跟随系统主题
|
||||
const darkModeMediaQuery = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
);
|
||||
const toggleDarkMode = (darkModeMediaQuery) => {
|
||||
if (darkModeMediaQuery.matches) {
|
||||
document.documentElement.classList.add("dark-mode");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark-mode");
|
||||
}
|
||||
};
|
||||
darkModeMediaQuery.addListener(toggleDarkMode);
|
||||
toggleDarkMode(darkModeMediaQuery);
|
||||
|
||||
// 按钮事件
|
||||
const clickFunction = () => {
|
||||
window.location.href = "/api/links";
|
||||
};
|
||||
|
||||
// 社交链接跳转
|
||||
const socialJump = (type) => {
|
||||
switch (type) {
|
||||
case "github":
|
||||
window.location.href = "https://github.com/imsyy/";
|
||||
break;
|
||||
case "home":
|
||||
window.location.href = "https://www.imsyy.top/";
|
||||
break;
|
||||
case "email":
|
||||
window.location.href = "mailto:one@imsyy.top";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
11
public/favicon.svg
Normal file
11
public/favicon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
||||
<title>favicon-svg</title>
|
||||
<defs>
|
||||
<image width="1024" height="1024" id="img1" href="data:image/png;base64,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"/>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: #ffffff }
|
||||
</style>
|
||||
<use id="Layer" href="#img1" x="0" y="0"/>
|
||||
<path id="Layer" class="s0" d="m408.6 448.2v-0.5q0-0.8-0.1-1.7 0-0.9 0-1.7 0-0.9 0-1.7-0.1-0.9-0.1-1.8v0.3-1.1q0-0.3 0-0.5 0-0.3 0-0.6 0-0.3-0.1-0.6 0-0.2 0-0.5 0.1-1 0.1-2 0-0.9 0-1.9 0.1-1 0.1-1.9 0.1-1 0.2-1.9l-0.2 1.7 0.2-2.7c1.2-14 5-31.4 10.8-49.2 9.6-27.6 26.1-57.3 52.3-79.2 35.1-29.4 85.5-44.7 135.9-44.7 5.7 0 11 2.9 13.8 7.5 2.9 4.6 2.9 10.3 0.1 15-2 3.2-86.3 81.7 11.5 137.3 38.8 22.1 96.5 79.9 96.5 148.8 0 54-22.6 105-63.7 143.5-41.2 38.6-95.9 59.9-153.9 59.9-57.8 0-112.5-21.6-153.9-60.7q-7.3-6.9-14-14.4-6.6-7.6-12.4-15.7-5.9-8.2-10.9-16.8-5.1-8.7-9.3-17.8-4.2-9.2-7.4-18.7-3.2-9.6-5.3-19.4-2.2-9.8-3.3-19.8-1.1-10-1.1-20.1c0-52.3 26.3-110.3 62.9-119.4l0.2 11c0.1 3.4 0.2 6.7 0.5 10.1l0.5 6.8c0.4 3.5 0.8 7 1.3 10.7l1.2 7.8 1.4 8.5 0.8 4.6 1.8 10c1.6 8.8 4.2 18.8 7.7 30.1 2 6.5 6.5 12 12.5 15.1 6 3.2 13 3.9 19.5 1.9 6.5-2 11.9-6.5 15.1-12.5 3.2-6 3.8-13 1.8-19.5q-0.9-3-1.8-6-0.9-3.1-1.7-6.2-0.7-3-1.4-6.1-0.7-3.1-1.4-6.2l-2.3-13-1.3-7.5-1-6.8q0-0.4-0.1-0.8-0.1-0.4-0.1-0.8-0.1-0.4-0.1-0.8-0.1-0.4-0.1-0.8l-0.7-6-0.5-5.7q0-0.3-0.1-0.6 0-0.3 0-0.6 0-0.3 0-0.6-0.1-0.3-0.1-0.6l-0.2-5-0.1-6.6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
201
public/index.html
Normal file
201
public/index.html
Normal file
@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>DailyHot API</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="./favicon.svg"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--text-color: #000;
|
||||
--text-color-gray: #888;
|
||||
--text-color-hover: #fff;
|
||||
--icon-color: #444;
|
||||
}
|
||||
html.dark-mode {
|
||||
--text-color: #fff;
|
||||
--text-color-gray: #888;
|
||||
--text-color-hover: #3c3c3c;
|
||||
--icon-color: #cbcbcb;
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: var(--text-color-hover);
|
||||
color: var(--text-color);
|
||||
font-family: "PingFang SC", "Open Sans", "Microsoft YaHei", sans-serif;
|
||||
transition: background-color 0.5s, color 0.5s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
.dark-mode body {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--text-color-gray);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--text-color);
|
||||
}
|
||||
.ico {
|
||||
margin: 4rem 2rem;
|
||||
font-size: 6rem;
|
||||
color: var(--text-color-gray);
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text {
|
||||
margin: 1rem;
|
||||
}
|
||||
.control button {
|
||||
background-color: var(--text-color-hover);
|
||||
border: var(--text-color) solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.5s ease;
|
||||
margin: 1rem 0.2rem;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.control button:hover {
|
||||
border: var(--text-color) solid;
|
||||
background: var(--text-color);
|
||||
color: var(--text-color-hover);
|
||||
}
|
||||
.control button i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
footer {
|
||||
line-height: 1.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 140px;
|
||||
}
|
||||
footer .social {
|
||||
color: var(--icon-color);
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
footer .social i {
|
||||
margin: 0 12px;
|
||||
}
|
||||
footer .point::before {
|
||||
content: "·";
|
||||
color: var(--text-color-gray);
|
||||
}
|
||||
.power,
|
||||
.icp {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="ico"><i class="fa-solid fa-code"></i></div>
|
||||
<div class="title">HotDaily API</div>
|
||||
<div class="text">服务已正常运行</div>
|
||||
<div class="control">
|
||||
<button onclick="clickFunction()">
|
||||
<i class="fa-solid fa-vial"></i>
|
||||
<span>测试接口</span>
|
||||
</button>
|
||||
<button
|
||||
onclick="window.open(`https://www.apifox.cn/apidoc/shared-152aff39-4070-4ef7-8a53-d03940c5e2f3`)"
|
||||
>
|
||||
<i class="fa-solid fa-book"></i>
|
||||
<span>接口文档</span>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="social">
|
||||
<i class="fa-brands fa-github" onclick="socialJump('github')"></i>
|
||||
<div class="point"></div>
|
||||
<i class="fa-solid fa-house" onclick="socialJump('home')"></i>
|
||||
<div class="point"></div>
|
||||
<i class="fa-solid fa-envelope" onclick="socialJump('email')"></i>
|
||||
</div>
|
||||
<div class="power">
|
||||
Copyright © 2020
|
||||
<script>
|
||||
document.write(" - " + new Date().getFullYear());
|
||||
</script>
|
||||
<a href="https://imsyy.top/" target="_blank">無名</a>
|
||||
</div>
|
||||
<div class="icp">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank"
|
||||
>豫ICP备2022018134号-1</a
|
||||
>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
// 跟随系统主题
|
||||
const darkModeMediaQuery = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
);
|
||||
const toggleDarkMode = (darkModeMediaQuery) => {
|
||||
if (darkModeMediaQuery.matches) {
|
||||
document.documentElement.classList.add("dark-mode");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark-mode");
|
||||
}
|
||||
};
|
||||
darkModeMediaQuery.addListener(toggleDarkMode);
|
||||
toggleDarkMode(darkModeMediaQuery);
|
||||
|
||||
// 按钮事件
|
||||
const clickFunction = () => {
|
||||
window.location.href = "/bilibili";
|
||||
};
|
||||
|
||||
// 社交链接跳转
|
||||
const socialJump = (type) => {
|
||||
switch (type) {
|
||||
case "github":
|
||||
window.location.href = "https://github.com/imsyy/";
|
||||
break;
|
||||
case "home":
|
||||
window.location.href = "https://www.imsyy.top/";
|
||||
break;
|
||||
case "email":
|
||||
window.location.href = "mailto:one@imsyy.top";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
129
routes/36kr.js
Normal file
129
routes/36kr.js
Normal file
@ -0,0 +1,129 @@
|
||||
const Router = require("koa-router");
|
||||
const krRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "krData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.itemId,
|
||||
title: v.templateMaterial.widgetTitle,
|
||||
pic: v.templateMaterial.widgetImage,
|
||||
owner: v.templateMaterial.authorName,
|
||||
hot: v.templateMaterial.statRead,
|
||||
data: v.templateMaterial,
|
||||
url: `https://www.36kr.com/p/${v.itemId}`,
|
||||
mobileUrl: `https://www.36kr.com/p/${v.itemId}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 36氪热榜
|
||||
krRouter.get("/36kr", async (ctx) => {
|
||||
console.log("获取36氪热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取36氪热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.post(url, {
|
||||
partner_id: "wap",
|
||||
param: {
|
||||
siteId: 1,
|
||||
platformId: 2,
|
||||
},
|
||||
});
|
||||
data = getData(response.data.data.hotRankList);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "36氪",
|
||||
subtitle: "热榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 36氪热榜 - 获取最新数据
|
||||
krRouter.get("/36kr/new", async (ctx) => {
|
||||
console.log("获取36氪热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.post(url, {
|
||||
partner_id: "wap",
|
||||
param: {
|
||||
siteId: 1,
|
||||
platformId: 2,
|
||||
},
|
||||
});
|
||||
const newData = getData(response.data.data.hotRankList);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取36氪热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "36氪",
|
||||
subtitle: "热榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "36氪",
|
||||
subtitle: "热榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = krRouter;
|
132
routes/baidu.js
Normal file
132
routes/baidu.js
Normal file
@ -0,0 +1,132 @@
|
||||
const Router = require("koa-router");
|
||||
const baiduRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "baiduData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://top.baidu.com/board?tab=realtime";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
const dataList = [];
|
||||
try {
|
||||
const pattern = /<\!--s-data:(.*?)-->/s;
|
||||
const matchResult = data.match(pattern);
|
||||
const jsonObject = JSON.parse(matchResult[1]).cards[0].content;
|
||||
jsonObject.forEach((v) => {
|
||||
dataList.push({
|
||||
title: v.query,
|
||||
desc: v.desc,
|
||||
pic: v.img,
|
||||
hot: v.hotScore,
|
||||
url: `https://www.baidu.com/s?wd=${encodeURIComponent(v.query)}`,
|
||||
mobileUrl: v.url,
|
||||
});
|
||||
});
|
||||
return dataList;
|
||||
} catch (error) {
|
||||
console.error("数据处理出错" + error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 百度热搜
|
||||
baiduRouter.get("/baidu", async (ctx) => {
|
||||
console.log("获取百度热搜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取百度热搜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
if (!data) {
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
return false;
|
||||
}
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度",
|
||||
subtitle: "热搜榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 百度热搜 - 获取最新数据
|
||||
baiduRouter.get("/baidu/new", async (ctx) => {
|
||||
console.log("获取百度热搜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取百度热搜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度",
|
||||
subtitle: "热搜榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度",
|
||||
subtitle: "热搜榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = baiduRouter;
|
117
routes/bilibili.js
Normal file
117
routes/bilibili.js
Normal file
@ -0,0 +1,117 @@
|
||||
const Router = require("koa-router");
|
||||
const bilibiliRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "bilibiliData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://api.bilibili.com/x/web-interface/ranking/v2";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.bvid,
|
||||
title: v.title,
|
||||
desc: v.desc,
|
||||
pic: v.pic,
|
||||
owner: v.owner,
|
||||
data: v.stat,
|
||||
url: v.short_link,
|
||||
mobileUrl: `https://m.bilibili.com/video/${v.bvid}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 哔哩哔哩热门榜
|
||||
bilibiliRouter.get("/bilibili", async (ctx) => {
|
||||
console.log("获取哔哩哔哩热门榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取哔哩哔哩热门榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data.list);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "哔哩哔哩",
|
||||
subtitle: "热门榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "哔哩哔哩热门榜获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 哔哩哔哩热门榜 - 获取最新数据
|
||||
bilibiliRouter.get("/bilibili/new", async (ctx) => {
|
||||
console.log("获取哔哩哔哩热门榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data.list);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取哔哩哔哩热门榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "哔哩哔哩",
|
||||
subtitle: "热门榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "哔哩哔哩",
|
||||
subtitle: "热门榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "哔哩哔哩热门榜获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = bilibiliRouter;
|
37
routes/index.js
Normal file
37
routes/index.js
Normal file
@ -0,0 +1,37 @@
|
||||
const Router = require("koa-router");
|
||||
|
||||
const bilibiliRouter = require("./bilibili");
|
||||
const zhihuRouter = require("./zhihu");
|
||||
const baiduRouter = require("./baidu");
|
||||
const weiboRouter = require("./weibo");
|
||||
const itHomeRouter = require("./ithome");
|
||||
const krRouter = require("./36kr");
|
||||
const sspaiRouter = require("./sspai");
|
||||
const tiebaRouter = require("./tieba");
|
||||
const toutiaoRouter = require("./toutiao");
|
||||
const thepaperRouter = require("./thepaper");
|
||||
|
||||
const router = new Router();
|
||||
|
||||
// 根目录
|
||||
router.get("/", async (ctx) => {
|
||||
await ctx.render("index");
|
||||
});
|
||||
|
||||
router.use(bilibiliRouter.routes());
|
||||
router.use(zhihuRouter.routes());
|
||||
router.use(baiduRouter.routes());
|
||||
router.use(weiboRouter.routes());
|
||||
router.use(itHomeRouter.routes());
|
||||
router.use(krRouter.routes());
|
||||
router.use(sspaiRouter.routes());
|
||||
router.use(tiebaRouter.routes());
|
||||
router.use(toutiaoRouter.routes());
|
||||
router.use(thepaperRouter.routes());
|
||||
|
||||
// 404 路由
|
||||
router.use(async (ctx) => {
|
||||
await ctx.render("404");
|
||||
});
|
||||
|
||||
module.exports = router;
|
150
routes/ithome.js
Normal file
150
routes/ithome.js
Normal file
@ -0,0 +1,150 @@
|
||||
const Router = require("koa-router");
|
||||
const itHomeRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const cheerio = require("cheerio");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "itHomeData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://m.ithome.com/rankm/";
|
||||
const headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
|
||||
};
|
||||
|
||||
// it之家特殊处理 - url
|
||||
const replaceLink = (url) => {
|
||||
const match = url.match(/html\/(\d+)\.htm/)[1];
|
||||
return `https://www.ithome.com/0/${match.slice(0, 3)}/${match.slice(3)}.htm`;
|
||||
};
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return false;
|
||||
const dataList = {};
|
||||
const $ = cheerio.load(data);
|
||||
try {
|
||||
$(".rank-name").each(function () {
|
||||
const type = $(this).data("rank-type");
|
||||
const newListHtml = $(this).next(".rank-box").html();
|
||||
const newsList = cheerio
|
||||
.load(newListHtml)(".placeholder")
|
||||
.get()
|
||||
.map((v) => {
|
||||
return {
|
||||
title: $(v).find(".plc-title").text(),
|
||||
img: $(v).find("img").attr("data-original"),
|
||||
time: $(v).find(".post-time").text(),
|
||||
hot: $(v).find(".review-num").text(),
|
||||
url: replaceLink($(v).find("a").attr("href")),
|
||||
mobileUrl: $(v).find("a").attr("href"),
|
||||
};
|
||||
});
|
||||
dataList[type] = {
|
||||
name: $(this).text(),
|
||||
total: newsList.length,
|
||||
list: newsList,
|
||||
};
|
||||
});
|
||||
return dataList;
|
||||
} catch (error) {
|
||||
console.error("数据处理出错" + error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// IT之家热榜
|
||||
itHomeRouter.get("/ithome", async (ctx) => {
|
||||
console.log("获取IT之家热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取IT之家热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url, { headers });
|
||||
data = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
if (!data) {
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
return false;
|
||||
}
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "IT之家",
|
||||
subtitle: "热榜",
|
||||
from,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// IT之家热榜 - 获取最新数据
|
||||
itHomeRouter.get("/ithome/new", async (ctx) => {
|
||||
console.log("获取IT之家热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url, { headers });
|
||||
const newData = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取IT之家热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "IT之家",
|
||||
subtitle: "热榜",
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "IT之家",
|
||||
subtitle: "热榜",
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = itHomeRouter;
|
117
routes/sspai.js
Normal file
117
routes/sspai.js
Normal file
@ -0,0 +1,117 @@
|
||||
const Router = require("koa-router");
|
||||
const sspaiRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "krData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = `https://sspai.com/api/v1/article/tag/page/get?limit=40&tag=热门文章`;
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.id,
|
||||
title: v.title,
|
||||
desc: v.summary,
|
||||
pic: `https://cdn.sspai.com/${v.banner}`,
|
||||
owner: v.author,
|
||||
hot: v.like_count,
|
||||
url: `https://sspai.com/post/${v.id}`,
|
||||
mobileUrl: `https://sspai.com/post/${v.itemId}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 少数派热榜
|
||||
sspaiRouter.get("/sspai", async (ctx) => {
|
||||
console.log("获取少数派热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取少数派热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "少数派",
|
||||
subtitle: "最热",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 少数派热榜 - 获取最新数据
|
||||
sspaiRouter.get("/sspai/new", async (ctx) => {
|
||||
console.log("获取少数派热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取少数派热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "少数派",
|
||||
subtitle: "最热",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "少数派",
|
||||
subtitle: "最热",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = sspaiRouter;
|
115
routes/thepaper.js
Normal file
115
routes/thepaper.js
Normal file
@ -0,0 +1,115 @@
|
||||
const Router = require("koa-router");
|
||||
const thepaperRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "thepaperData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.contId,
|
||||
title: v.name,
|
||||
pic: v.pic,
|
||||
time: v.pubTime,
|
||||
url: `https://www.thepaper.cn/newsDetail_forward_${v.contId}`,
|
||||
mobileUrl: `https://m.thepaper.cn/newsDetail_forward_${v.contId}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 澎湃热榜
|
||||
thepaperRouter.get("/thepaper", async (ctx) => {
|
||||
console.log("获取澎湃热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取澎湃热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data.hotNews);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "澎湃新闻",
|
||||
subtitle: "热榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 澎湃热榜 - 获取最新数据
|
||||
thepaperRouter.get("/thepaper/new", async (ctx) => {
|
||||
console.log("获取澎湃热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data.hotNews);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取澎湃热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "澎湃新闻",
|
||||
subtitle: "热榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "澎湃新闻",
|
||||
subtitle: "热榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = thepaperRouter;
|
116
routes/tieba.js
Normal file
116
routes/tieba.js
Normal file
@ -0,0 +1,116 @@
|
||||
const Router = require("koa-router");
|
||||
const tiebaRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "tiebaData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://tieba.baidu.com/hottopic/browse/topicList";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.topic_id,
|
||||
title: v.topic_name,
|
||||
desc: v.topic_desc,
|
||||
pic: v.topic_pic,
|
||||
hot: v.discuss_num,
|
||||
url: v.topic_url,
|
||||
mobileUrl: v.topic_url,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 贴吧热议榜
|
||||
tiebaRouter.get("/tieba", async (ctx) => {
|
||||
console.log("获取贴吧热议榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取贴吧热议榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data.bang_topic.topic_list);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度贴吧",
|
||||
subtitle: "热议榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 贴吧热议榜 - 获取最新数据
|
||||
tiebaRouter.get("/tieba/new", async (ctx) => {
|
||||
console.log("获取贴吧热议榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data.bang_topic.topic_list);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取贴吧热议榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度贴吧",
|
||||
subtitle: "热议榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "百度贴吧",
|
||||
subtitle: "热议榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = tiebaRouter;
|
115
routes/toutiao.js
Normal file
115
routes/toutiao.js
Normal file
@ -0,0 +1,115 @@
|
||||
const Router = require("koa-router");
|
||||
const toutiaoRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "toutiaoData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
return data.map((v) => {
|
||||
return {
|
||||
id: v.ClusterId,
|
||||
title: v.Title,
|
||||
pic: v.Image.url,
|
||||
hot: v.HotValue,
|
||||
url: `https://www.toutiao.com/trending/${v.ClusterIdStr}/`,
|
||||
mobileUrl: `https://api.toutiaoapi.com/feoffline/amos_land/new/html/main/index.html?topic_id=${v.ClusterIdStr}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 头条热榜
|
||||
toutiaoRouter.get("/toutiao", async (ctx) => {
|
||||
console.log("获取头条热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取头条热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data);
|
||||
updateTime = new Date().toISOString();
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "今日头条",
|
||||
subtitle: "热榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 头条热榜 - 获取最新数据
|
||||
toutiaoRouter.get("/toutiao/new", async (ctx) => {
|
||||
console.log("获取头条热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取头条热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "今日头条",
|
||||
subtitle: "热榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "今日头条",
|
||||
subtitle: "热榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = toutiaoRouter;
|
128
routes/weibo.js
Normal file
128
routes/weibo.js
Normal file
@ -0,0 +1,128 @@
|
||||
const Router = require("koa-router");
|
||||
const weiboRouter = new Router();
|
||||
const axios = require("axios");
|
||||
// const cheerio = require("cheerio");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "weiboData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://weibo.com/ajax/side/hotSearch";
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
// return data;
|
||||
return data.map((v) => {
|
||||
const key = v.word_scheme ? v.word_scheme : `#${v.word}`;
|
||||
return {
|
||||
title: v.word,
|
||||
desc: key,
|
||||
hot: v.raw_hot,
|
||||
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(
|
||||
key
|
||||
)}&t=31&band_rank=1&Refer=top`,
|
||||
mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent(
|
||||
key
|
||||
)}&t=31&band_rank=1&Refer=top`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 微博热搜
|
||||
weiboRouter.get("/weibo", async (ctx) => {
|
||||
console.log("获取微博热搜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取微博热搜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url);
|
||||
data = getData(response.data.data.realtime);
|
||||
updateTime = new Date().toISOString();
|
||||
if (!data) {
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
return false;
|
||||
}
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "微博",
|
||||
subtitle: "热搜榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 微博热搜 - 获取最新数据
|
||||
weiboRouter.get("/weibo/new", async (ctx) => {
|
||||
console.log("获取微博热搜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url);
|
||||
const newData = getData(response.data.data.realtime);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取微博热搜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "微博",
|
||||
subtitle: "热搜榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "微博",
|
||||
subtitle: "热搜榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = weiboRouter;
|
137
routes/zhihu.js
Normal file
137
routes/zhihu.js
Normal file
@ -0,0 +1,137 @@
|
||||
const Router = require("koa-router");
|
||||
const zhihuRouter = new Router();
|
||||
const axios = require("axios");
|
||||
const { get, set, del } = require("../utils/cacheData");
|
||||
|
||||
// 缓存键名
|
||||
const cacheKey = "zhihuData";
|
||||
|
||||
// 调用时间
|
||||
let updateTime = new Date().toISOString();
|
||||
|
||||
// 调用路径
|
||||
const url = "https://www.zhihu.com/hot";
|
||||
const headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
|
||||
};
|
||||
|
||||
// 数据处理
|
||||
const getData = (data) => {
|
||||
if (!data) return [];
|
||||
const dataList = [];
|
||||
try {
|
||||
const pattern =
|
||||
/<script id="js-initialData" type="text\/json">(.*?)<\/script>/;
|
||||
const matchResult = data.match(pattern);
|
||||
const jsonObject = JSON.parse(matchResult[1]).initialState.topstory.hotList;
|
||||
jsonObject.forEach((v) => {
|
||||
dataList.push({
|
||||
title: v.target.titleArea.text,
|
||||
desc: v.target.excerptArea.text,
|
||||
pic: v.target.imageArea.url,
|
||||
hot: v.target.metricsArea.text,
|
||||
url: v.target.link.url,
|
||||
mobileUrl: v.target.link.url,
|
||||
});
|
||||
});
|
||||
return dataList;
|
||||
} catch (error) {
|
||||
console.error("数据处理出错" + error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 知乎热榜
|
||||
zhihuRouter.get("/zhihu", async (ctx) => {
|
||||
console.log("获取知乎热榜");
|
||||
try {
|
||||
// 从缓存中获取数据
|
||||
let data = await get(cacheKey);
|
||||
const from = data ? "cache" : "server";
|
||||
if (!data) {
|
||||
// 如果缓存中不存在数据
|
||||
console.log("从服务端重新获取知乎热榜");
|
||||
// 从服务器拉取数据
|
||||
const response = await axios.get(url, { headers });
|
||||
data = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
if (!data) {
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
return false;
|
||||
}
|
||||
// 将数据写入缓存
|
||||
await set(cacheKey, data);
|
||||
}
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "知乎",
|
||||
subtitle: "热榜",
|
||||
from,
|
||||
total: data.length,
|
||||
updateTime,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 知乎热榜 - 获取最新数据
|
||||
zhihuRouter.get("/zhihu/new", async (ctx) => {
|
||||
console.log("获取知乎热榜 - 最新数据");
|
||||
try {
|
||||
// 从服务器拉取最新数据
|
||||
const response = await axios.get(url, { headers });
|
||||
const newData = getData(response.data);
|
||||
updateTime = new Date().toISOString();
|
||||
console.log("从服务端重新获取知乎热榜");
|
||||
|
||||
// 返回最新数据
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "知乎",
|
||||
subtitle: "热榜",
|
||||
total: newData.length,
|
||||
updateTime,
|
||||
data: newData,
|
||||
};
|
||||
|
||||
// 删除旧数据
|
||||
await del(cacheKey);
|
||||
// 将最新数据写入缓存
|
||||
await set(cacheKey, newData);
|
||||
} catch (error) {
|
||||
// 如果拉取最新数据失败,尝试从缓存中获取数据
|
||||
console.error(error);
|
||||
const cachedData = await get(cacheKey);
|
||||
if (cachedData) {
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: "获取成功",
|
||||
title: "知乎",
|
||||
subtitle: "热榜",
|
||||
total: cachedData.length,
|
||||
updateTime,
|
||||
data: cachedData,
|
||||
};
|
||||
} else {
|
||||
// 如果缓存中也没有数据,则返回错误信息
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: "获取失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = zhihuRouter;
|
41
utils/cacheData.js
Normal file
41
utils/cacheData.js
Normal file
@ -0,0 +1,41 @@
|
||||
const NodeCache = require("node-cache");
|
||||
|
||||
const cache = new NodeCache({
|
||||
stdTTL: 1800, // 缓存默认过期时间(单位秒)
|
||||
checkperiod: 60, // 定期检查过期缓存的时间(单位秒)
|
||||
});
|
||||
|
||||
/**
|
||||
* 从缓存中获取数据
|
||||
* @param {string} key 缓存键值
|
||||
* @return {Promise<any>} 数据
|
||||
*/
|
||||
const get = async (key) => {
|
||||
return cache.get(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* 将数据写入缓存
|
||||
* @param {string} key 缓存键值
|
||||
* @param {any} value 数据
|
||||
* @param {number} ttl 有效期,单位秒,默认为300秒
|
||||
* @return {Promise<void>} 无返回值
|
||||
*/
|
||||
const set = async (key, value, ttl = 300) => {
|
||||
return cache.set(key, value, ttl);
|
||||
};
|
||||
|
||||
/**
|
||||
* 从缓存中删除数据
|
||||
* @param {string} key 缓存键值
|
||||
* @return {Promise<void>} 无返回值
|
||||
*/
|
||||
const del = async (key) => {
|
||||
return cache.del(key);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
get,
|
||||
set,
|
||||
del,
|
||||
};
|
Loading…
Reference in New Issue
Block a user