七十二家房客-观看平台v1.1

This commit is contained in:
云上贵猪 2026-03-11 05:14:48 +08:00
parent b77ff62c58
commit a20c6ad819
13 changed files with 154456 additions and 19423 deletions

261
README.md
View File

@ -1,42 +1,259 @@
# 72QishierPlayer
This template should help get you started developing with Vue 3 in Vite.
一个基于 **Vue3 + Vite + Node.js + Playwright + HLS.js** 的《七十二家房客》在线播放项目。
## Recommended IDE Setup
本项目通过 **浏览器自动采集官网数据 → 本地缓存 JSON → Node API 提供 → Vue 前端播放** 的方式,解决官网接口 **签名、401、分页限制** 等问题,实现稳定播放。
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
---
## Recommended Browser Setup
# 项目特点
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
- 自动采集《七十二家房客》节目列表
- 自动滚动加载更多节目
- 自动去重并缓存到 JSON
- Node API 提供数据
- Vue3 播放器展示
- HLS.js 播放 m3u8 视频
- Cookie 记录上次观看进度
- 前端分页加载,避免一次加载过多图片
## Type Support for `.vue` Imports in TS
---
获取的空数据
```json
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
{
"updatedAt": "2026-03-10T18:00:00Z",
"name": "七十二家房客",
"items": []
}
```
## Customize configuration
# 安装
See [Vite Configuration Reference](https://vite.dev/config/).
## 1 安装前端依赖
## Project Setup
```sh
```
npm install
```
### Compile and Hot-Reload for Development
安装播放器依赖:
```sh
```
npm install axios hls.js
```
---
## 2 安装后端依赖
进入后端目录:
```
cd backServer
```
安装依赖:
```
npm install
```
安装 Playwright 浏览器:
```
npx playwright install chromium
```
---
# 运行项目
## 1 采集节目数据
```
npm run capture
```
程序会自动:
1. 启动浏览器
2. 打开页面
```
https://www1.gdtv.cn/tvColumn/768
```
3. 自动滚动页面
4. 自动点击 **加载更多**
5. 捕获接口数据
6. 写入缓存
缓存文件:
```
backServer/data/qishier-cache.json
```
---
## 2 启动 Node API
`backServer` 目录运行:
```
npm run server
```
API 地址:
```
http://localhost:3000/api/qishier/all
```
返回示例:
```
{
"total": 1141,
"list": []
}
```
---
## 3 启动 Vue 前端
回到项目根目录地址http://localhost:5173
```
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
---
# 项目架构
```
官网接口
gdtv-api.gdtv.cn
Playwright 浏览器采集
本地 JSON 缓存
backServer/data/qishier-cache.json
Node API
http://localhost:3000/api/qishier/all
Vue 播放器
http://localhost:5173
```
---
# 项目目录
```
72QishierPlayer
├─ src
│ ├─ api
│ │ └─ qishier.ts
│ │
│ ├─ views
│ │ └─ QishierPlayer.vue
│ │
│ ├─ App.vue
│ ├─ main.ts
│ └─ assets
├─ backServer
│ │
│ ├─ data
│ │ └─ qishier-cache.json
│ │
│ ├─ capture.js
│ ├─ server.js
│ └─ package.json
├─ vite.config.ts
└─ package.json
```
---
# 播放器功能
播放器支持:
- m3u8 视频播放
- HLS.js 播放器
- 节目列表分页加载
- 懒加载封面图
- 自动播放
- 自动恢复观看进度
观看记录存储在 Cookie
```
qishier_last_episode
```
刷新页面会自动恢复上次观看。
---
# 数据缓存机制
缓存文件:
```
backServer/data/qishier-cache.json
```
缓存策略:
- 新数据自动追加
- 根据 `id` 自动去重
- 避免重复节目
---
# 技术栈
前端:
- Vue 3
- Vite
- TypeScript
- Axios
- HLS.js
后端:
- Node.js
- Express
- Playwright
数据:
- JSON 本地缓存
---
# 免责声明
本项目仅用于:
- 技术学习
- 数据采集研究
- 视频播放技术演示
请勿用于商业用途。

View File

@ -1,6 +1,11 @@
import { chromium } from 'playwright'
import readlineSync from 'readline-sync'
import { ensureDataFiles, readCache, writeCache, writeStatus } from './lib/cache.js'
import {
ensureDataFiles,
readYearData,
writeYearData,
writeStatus
} from './lib/cache.js'
const PAGE_URL = 'https://www1.gdtv.cn/tvColumn/768'
@ -25,41 +30,45 @@ function parseEpisodeItem(rawItem) {
}
}
function mergeCache(oldCache, responseUrl, data) {
const url = new URL(responseUrl)
const currentPage = Number(url.searchParams.get('currentPage') || 1)
const beginScore = Number(url.searchParams.get('beginScore') || 0)
function getYearByTimestamp(timestamp) {
if (!timestamp) return new Date().getFullYear()
return new Date(timestamp).getFullYear()
}
const parsedItems = (data.list || [])
.map(parseEpisodeItem)
.filter(Boolean)
function saveItemsByYear(items) {
const yearGroups = new Map()
const itemMap = new Map((oldCache.items || []).map(item => [item.id, item]))
for (const item of parsedItems) {
itemMap.set(item.id, item)
for (const item of items) {
const year = getYearByTimestamp(item.releasedAt)
if (!yearGroups.has(year)) {
yearGroups.set(year, [])
}
yearGroups.get(year).push(item)
}
return {
...oldCache,
updatedAt: new Date().toISOString(),
name: data.name || oldCache.name || '七十二家房客',
coverUrl: data.coverUrl || oldCache.coverUrl || '',
displayType: data.displayType ?? oldCache.displayType ?? 0,
beginScoreMap: {
...(oldCache.beginScoreMap || {}),
[currentPage]: beginScore
},
pages: {
...(oldCache.pages || {}),
[currentPage]: {
currentPage,
beginScore,
count: parsedItems.length,
capturedAt: new Date().toISOString()
}
},
items: Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
let totalUniqueAdded = 0
for (const [year, groupItems] of yearGroups.entries()) {
const oldData = readYearData(year)
const itemMap = new Map((oldData.items || []).map(item => [item.id, item]))
const beforeCount = itemMap.size
for (const item of groupItems) {
itemMap.set(item.id, item)
}
const nextItems = Array.from(itemMap.values()).sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
const afterCount = nextItems.length
totalUniqueAdded += afterCount - beforeCount
writeYearData(year, {
year,
updatedAt: new Date().toISOString(),
items: nextItems
})
}
return totalUniqueAdded
}
function log(...args) {
@ -270,23 +279,25 @@ async function main() {
try {
const data = await response.json()
const oldCache = readCache()
const nextCache = mergeCache(oldCache, url, data)
writeCache(nextCache)
const parsedItems = (data.list || [])
.map(parseEpisodeItem)
.filter(Boolean)
const addedCount = saveItemsByYear(parsedItems)
const totalItems = parsedItems.length
const currentPage = new URL(url).searchParams.get('currentPage')
const capturedPages = Object.keys(nextCache.pages || {}).length
const totalItems = nextCache.items?.length || 0
writeStatus({
running: true,
lastMessage: `已采集第 ${currentPage}`,
updatedAt: new Date().toISOString(),
capturedPages,
totalItems
currentPage,
pageItems: totalItems,
addedCount
})
log(`采集成功: page=${currentPage} totalItems=${totalItems}`)
log(`采集成功: page=${currentPage} 本页=${totalItems} 新增唯一=${addedCount}`)
} catch (error) {
console.error('[capture] 解析响应失败:', error)
}
@ -322,7 +333,7 @@ async function main() {
updatedAt: new Date().toISOString()
})
log('自动采集完成,数据已写入 data/qishier-cache.json')
log('自动采集完成,数据已按年份写入 data/*.json')
}
main().catch((error) => {

View File

@ -1,7 +0,0 @@
{
"running": true,
"lastMessage": "已采集第 2 页",
"updatedAt": "2026-03-10T19:14:36.004Z",
"capturedPages": 26,
"totalItems": 1141
}

0
backServer/data/.gitkeep Normal file
View File

45607
backServer/data/2022.json Normal file

File diff suppressed because it is too large Load Diff

49730
backServer/data/2023.json Normal file

File diff suppressed because it is too large Load Diff

12561
backServer/data/2025.json Normal file

File diff suppressed because it is too large Load Diff

347
backServer/data/2026.json Normal file
View File

@ -0,0 +1,347 @@
{
"year": 2026,
"updatedAt": "2026-03-10T21:02:47.172Z",
"items": [
{
"id": "5abbc88ea530b5db6438a4e584e80281",
"title": "七十二家房客疍家盘粉20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/679165294b806fc4ac81fc17994e3d52.jpg",
"releasedAt": 1771863480000,
"timeLength": 1255,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192283084.m3u8",
"raw": {
"id": "5abbc88ea530b5db6438a4e584e80281",
"title": "七十二家房客疍家盘粉20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/679165294b806fc4ac81fc17994e3d52.jpg",
"contentType": 3,
"releasedAt": 1771863480000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192283084.m3u8\"}",
"timeLength": 1255,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "322b2856a22948d27637c11292cae81f",
"title": "七十二家房客二五八遇险记20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/9d93bc4b040f63bfb58e452e18701b99.jpg",
"releasedAt": 1771862220000,
"timeLength": 1258,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192274092.m3u8",
"raw": {
"id": "322b2856a22948d27637c11292cae81f",
"title": "七十二家房客二五八遇险记20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/9d93bc4b040f63bfb58e452e18701b99.jpg",
"contentType": 3,
"releasedAt": 1771862220000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192274092.m3u8\"}",
"timeLength": 1258,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "173f132bd10460b2bc87518a749da720",
"title": "七十二家房客二五八遇险记20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/af20658e9ee1fc2426e5eb26fd27b3fa.jpg",
"releasedAt": 1771860960000,
"timeLength": 1250,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192240974.m3u8",
"raw": {
"id": "173f132bd10460b2bc87518a749da720",
"title": "七十二家房客二五八遇险记20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/af20658e9ee1fc2426e5eb26fd27b3fa.jpg",
"contentType": 3,
"releasedAt": 1771860960000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192240974.m3u8\"}",
"timeLength": 1250,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "ae071bfb70913eafff08119e47e7d2bf",
"title": "七十二家房客残疾的阿香20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/2850e51e6a4909b45cd6e6492d0fa31c.jpg",
"releasedAt": 1771859700000,
"timeLength": 1248,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192228993.m3u8",
"raw": {
"id": "ae071bfb70913eafff08119e47e7d2bf",
"title": "七十二家房客残疾的阿香20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/2850e51e6a4909b45cd6e6492d0fa31c.jpg",
"contentType": 3,
"releasedAt": 1771859700000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192228993.m3u8\"}",
"timeLength": 1248,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "6741c8888b69e273bc2fc9e402806bc3",
"title": "七十二家房客残疾的阿香20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/f628992ecb2e497c43499ac1194a14aa.jpg",
"releasedAt": 1771858380000,
"timeLength": 1254,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192199043.m3u8",
"raw": {
"id": "6741c8888b69e273bc2fc9e402806bc3",
"title": "七十二家房客残疾的阿香20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/f628992ecb2e497c43499ac1194a14aa.jpg",
"contentType": 3,
"releasedAt": 1771858380000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192199043.m3u8\"}",
"timeLength": 1254,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "fc2d7f23efac7c804113b0b5e651a0e1",
"title": "七十二家房客乱世飘萍20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/3ffb96c45b375a82f81da4a7fcc30e16.jpg",
"releasedAt": 1771857120000,
"timeLength": 1257,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192187034.m3u8",
"raw": {
"id": "fc2d7f23efac7c804113b0b5e651a0e1",
"title": "七十二家房客乱世飘萍20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/3ffb96c45b375a82f81da4a7fcc30e16.jpg",
"contentType": 3,
"releasedAt": 1771857120000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192187034.m3u8\"}",
"timeLength": 1257,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "fd2f79033153dd6b9e35d9f549a2875c",
"title": "七十二家房客乱世飘萍20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/0e80bd756f4d0102056aff562dae1db9.jpg",
"releasedAt": 1771855800000,
"timeLength": 1258,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192156995.m3u8",
"raw": {
"id": "fd2f79033153dd6b9e35d9f549a2875c",
"title": "七十二家房客乱世飘萍20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/0e80bd756f4d0102056aff562dae1db9.jpg",
"contentType": 3,
"releasedAt": 1771855800000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192156995.m3u8\"}",
"timeLength": 1258,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "46373fb5eaf3ec143b1f1c71d347ed3d",
"title": "七十二家房客打掩护20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/51388f66e6851aa9109dfdc7d683fe89.jpg",
"releasedAt": 1771854540000,
"timeLength": 1243,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192144981.m3u8",
"raw": {
"id": "46373fb5eaf3ec143b1f1c71d347ed3d",
"title": "七十二家房客打掩护20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/51388f66e6851aa9109dfdc7d683fe89.jpg",
"contentType": 3,
"releasedAt": 1771854540000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192144981.m3u8\"}",
"timeLength": 1243,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "6aa2c98312b92526dd5d725c8d08cc21",
"title": "七十二家房客她来了20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/005d61b7b991d89fc176a2a0fd2df635.jpg",
"releasedAt": 1771853100000,
"timeLength": 1386,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192111957.m3u8",
"raw": {
"id": "6aa2c98312b92526dd5d725c8d08cc21",
"title": "七十二家房客她来了20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/005d61b7b991d89fc176a2a0fd2df635.jpg",
"contentType": 3,
"releasedAt": 1771853100000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192111957.m3u8\"}",
"timeLength": 1386,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "ffa1e619d83eda52253b5ece8734ff13",
"title": "七十二家房客她来了20260223",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/96b6576501867170847372b111f3752f.jpg",
"releasedAt": 1771851540000,
"timeLength": 1359,
"videoUrl": "https://vod.gdtv.cn/m3u8/202603/177192102933.m3u8",
"raw": {
"id": "ffa1e619d83eda52253b5ece8734ff13",
"title": "七十二家房客她来了20260223",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/02/96b6576501867170847372b111f3752f.jpg",
"contentType": 3,
"releasedAt": 1771851540000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202603/177192102933.m3u8\"}",
"timeLength": 1359,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
},
{
"id": "805f300979f10859b525b2917208d668",
"title": "2026-01-03 七十二家房客:爱情万岁(下)",
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/01/716c83304d268942740c4b210a0a8cc0.jpg",
"releasedAt": 1767368100000,
"timeLength": 1228,
"videoUrl": "https://vod.gdtv.cn/m3u8/202601/176743509593.m3u8",
"raw": {
"id": "805f300979f10859b525b2917208d668",
"title": "2026-01-03 七十二家房客:爱情万岁(下)",
"titleStyle": null,
"titleColor": null,
"subTitle": "",
"preSubTitle": null,
"coverUrl": "https://img.grtn.cn/material/mediaserver/img/2026/01/716c83304d268942740c4b210a0a8cc0.jpg",
"contentType": 3,
"releasedAt": 1767368100000,
"readCount": 0,
"author": null,
"objectType": 0,
"memberNum": 0,
"videoUrl": "{\"hd\":\"https://vod.gdtv.cn/m3u8/202601/176743509593.m3u8\"}",
"timeLength": 1228,
"imageCount": 0,
"recentVisitTime": 0,
"keyword": null,
"serialsInfo": null,
"crawlUrl": null,
"newsList": null
}
}
]
}

View File

@ -1,5 +1,8 @@
{
"running": false,
"lastMessage": "自动采集完成",
"updatedAt": "2026-03-10T19:27:40.184Z"
"running": true,
"lastMessage": "已采集第 1 页",
"updatedAt": "2026-03-10T21:11:58.820Z",
"currentPage": "1",
"pageItems": 0,
"addedCount": 0
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dataDir = path.resolve(__dirname, '../data')
const cacheFile = path.join(dataDir, 'qishier-cache.json')
const statusFile = path.join(dataDir, 'capture-status.json')
function ensureDir() {
@ -18,26 +17,6 @@ function ensureDir() {
export function ensureDataFiles() {
ensureDir()
if (!fs.existsSync(cacheFile)) {
fs.writeFileSync(
cacheFile,
JSON.stringify(
{
updatedAt: null,
name: '七十二家房客',
coverUrl: '',
displayType: 0,
pages: {},
beginScoreMap: {},
items: []
},
null,
2
),
'utf-8'
)
}
if (!fs.existsSync(statusFile)) {
fs.writeFileSync(
statusFile,
@ -55,14 +34,69 @@ export function ensureDataFiles() {
}
}
export function readCache() {
ensureDataFiles()
return JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
export function getYearFile(year) {
ensureDir()
return path.join(dataDir, `${year}.json`)
}
export function writeCache(data) {
ensureDataFiles()
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf-8')
export function readYearData(year) {
ensureDir()
const file = getYearFile(year)
if (!fs.existsSync(file)) {
return {
year,
updatedAt: null,
items: []
}
}
return JSON.parse(fs.readFileSync(file, 'utf-8'))
}
export function writeYearData(year, data) {
ensureDir()
const file = getYearFile(year)
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8')
}
export function getAllYearFiles() {
ensureDir()
return fs
.readdirSync(dataDir)
.filter((file) => /^\d{4}\.json$/.test(file))
.sort()
}
export function readAllYearsData() {
ensureDir()
const files = getAllYearFiles()
const allItems = []
const years = []
for (const file of files) {
const year = Number(file.replace('.json', ''))
const data = readYearData(year)
years.push(year)
if (Array.isArray(data.items)) {
allItems.push(...data.items)
}
}
allItems.sort((a, b) => (b.releasedAt || 0) - (a.releasedAt || 0))
return {
updatedAt: new Date().toISOString(),
name: '七十二家房客',
coverUrl: '',
displayType: 0,
years,
items: allItems
}
}
export function readStatus() {

View File

@ -1,6 +1,11 @@
import express from 'express'
import cors from 'cors'
import { ensureDataFiles, readCache, readStatus } from './lib/cache.js'
import {
ensureDataFiles,
readAllYearsData,
readYearData,
readStatus
} from './lib/cache.js'
const app = express()
app.use(cors())
@ -10,15 +15,15 @@ ensureDataFiles()
app.get('/api/qishier/all', (_req, res) => {
try {
const cache = readCache()
const cache = readAllYearsData()
res.json({
updatedAt: cache.updatedAt,
name: cache.name || '七十二家房客',
coverUrl: cache.coverUrl || '',
displayType: cache.displayType || 0,
total: cache.items?.length || 0,
beginScoreMap: cache.beginScoreMap || {},
pages: cache.pages || {},
years: cache.years || [],
list: cache.items || []
})
} catch (error) {
@ -29,6 +34,28 @@ app.get('/api/qishier/all', (_req, res) => {
}
})
app.get('/api/qishier/year/:year', (req, res) => {
try {
const year = Number(req.params.year)
const data = readYearData(year)
res.json({
updatedAt: data.updatedAt,
name: '七十二家房客',
coverUrl: '',
displayType: 0,
total: data.items?.length || 0,
years: [year],
list: data.items || []
})
} catch (error) {
res.status(500).json({
message: '读取年度缓存失败',
error: error.message
})
}
})
app.get('/api/qishier/status', (_req, res) => {
try {
const status = readStatus()