diff --git a/server/cache.ts b/server/cache.ts index ef2d44e..8a4d135 100644 --- a/server/cache.ts +++ b/server/cache.ts @@ -1,5 +1,4 @@ -import { TTL } from "@shared/consts" -import type { CacheInfo } from "@shared/types" +import type { CacheInfo, NewsItem } from "@shared/types" import type { Database } from "db0" export class Cache { @@ -13,26 +12,25 @@ export class Cache { await this.db.prepare(` CREATE TABLE IF NOT EXISTS cache ( id TEXT PRIMARY KEY, - data TEXT, updated INTEGER, - expires INTEGER + data TEXT ); `).run() console.log(`init: `, performance.now() - last) } - async set(key: string, value: any) { + async set(key: string, value: NewsItem[]) { const now = Date.now() const last = performance.now() await this.db.prepare( - `INSERT OR REPLACE INTO cache (id, data, updated, expires) VALUES (?, ?, ?, ?)`, - ).run(key, JSON.stringify(value), now, now + TTL) + `INSERT OR REPLACE INTO cache (id, data, updated) VALUES (?, ?, ?)`, + ).run(key, JSON.stringify(value), now) console.log(`set ${key}: `, performance.now() - last) } async get(key: string): Promise { const last = performance.now() - const row: any = await this.db.prepare(`SELECT id, data, updated, expires FROM cache WHERE id = ?`).get(key) + const row: any = await this.db.prepare(`SELECT id, data, updated FROM cache WHERE id = ?`).get(key) const r = row ? { ...row, diff --git a/server/routes/[id].ts b/server/routes/[id].ts index 1bfa9dc..318ae85 100644 --- a/server/routes/[id].ts +++ b/server/routes/[id].ts @@ -1,50 +1,70 @@ -import { fallback, sources } from "#/sources" +import { Interval, TTL } from "@shared/consts" +import type { SourceResponse } from "@shared/types" +import { sources } from "@shared/data" +import { fallback, sourcesFn } from "#/sources" import { Cache } from "#/cache" -export default defineEventHandler(async (event) => { +export default defineEventHandler(async (event): Promise => { try { - const id = getRouterParam(event, "id") as keyof typeof sources + const id = getRouterParam(event, "id") as keyof typeof sourcesFn const query = getQuery(event) const latest = query.latest !== undefined && query.latest !== "false" - if (!id) throw new Error("Invalid source id") + if (!id || !sources[id]) throw new Error("Invalid source id") const db = useDatabase() const cacheStore = db ? new Cache(db) : undefined + const now = Date.now() if (cacheStore) { // await cacheStore.init() const cache = await cacheStore.get(id) if (cache) { - if (!latest && cache.expires > Date.now()) { + if (!latest && now - cache.updated < TTL) { return { status: "cache", - data: cache.data, + data: { + updatedTime: cache.updated, + items: cache.data, + }, } - } else if (latest && Date.now() - cache.updated < 60 * 1000) { - return { - status: "success", - data: cache.data, + } else if (latest) { + let interval = Interval + if ("interval" in sources[id]) interval = sources[id].interval as number + if (now - cache.updated < interval) { + return { + status: "success", + data: { + updatedTime: now, + items: cache.data, + }, + } } } } } - if (!sources[id]) { + if (sourcesFn[id]) { const last = performance.now() - const data = await fallback(id) + const data = await sourcesFn[id]() console.log(`fetch: ${id}`, performance.now() - last) - if (cacheStore) await cacheStore.set(id, data) + if (cacheStore) event.waitUntil(cacheStore.set(id, data)) return { status: "success", - data, + data: { + updatedTime: now, + items: data, + }, } } else { const last = performance.now() - const data = await (await sources[id])() + const data = await fallback(id) console.log(`fetch: ${id}`, performance.now() - last) - if (cacheStore) await cacheStore.set(id, data) + if (cacheStore) event.waitUntil(cacheStore.set(id, data)) return { status: "success", - data, + data: { + updatedTime: now, + items: data, + }, } } } catch (e: any) { diff --git a/server/sources/fallback.ts b/server/sources/fallback.ts index 83a6721..8ba571e 100644 --- a/server/sources/fallback.ts +++ b/server/sources/fallback.ts @@ -1,4 +1,4 @@ -import type { SourceInfo } from "@shared/types" +import type { NewsItem } from "@shared/types" export interface Res { code: number @@ -17,20 +17,17 @@ export interface Res { }[] } -export async function fallback(id: string): Promise { +export async function fallback(id: string): Promise { const url = `https://smzdk.top/api/${id}/new` const res: Res = await $fetch(url) if (res.code !== 200 || !res.data) throw new Error(res.message) - return { - updatedTime: res.updateTime, - items: res.data.map(item => ({ - extra: { - date: item.time, - }, - id: item.url, - title: item.title, - url: item.url, - mobileUrl: item.mobileUrl, - })), - } + return res.data.map(item => ({ + extra: { + date: item.time, + }, + id: item.url, + title: item.title, + url: item.url, + mobileUrl: item.mobileUrl, + })) } diff --git a/server/sources/index.ts b/server/sources/index.ts index 37b493d..ff4d113 100644 --- a/server/sources/index.ts +++ b/server/sources/index.ts @@ -1,4 +1,4 @@ -import type { SourceID, SourceInfo } from "@shared/types" +import type { NewsItem, SourceID } from "@shared/types" import peopledaily from "./peopledaily" import weibo from "./weibo" import zaobao from "./zaobao" @@ -8,11 +8,11 @@ import wallstreetcn from "./wallstreetcn" export { fallback } from "./fallback" -export const sources = { +export const sourcesFn = { peopledaily, weibo, zaobao, wallstreetcn, "36kr-quick": krQ, // "36kr": kr, -} as Record Promise> +} as Record Promise> diff --git a/server/utils/index.ts b/server/utils/index.ts index ac34503..d2399e3 100644 --- a/server/utils/index.ts +++ b/server/utils/index.ts @@ -1,28 +1,22 @@ import { RSSHubBase } from "@shared/consts" -import type { NewsItem, RSSHubInfo, SourceInfo } from "@shared/types" +import type { NewsItem, RSSHubInfo } from "@shared/types" -export function defineSource(source: () => Promise): () => Promise { - return async () => ({ - updatedTime: Date.now(), - items: await source(), - }) +export function defineSource(source: () => Promise): () => Promise { + return source } -export function defineRSSSource(url: string): () => Promise { +export function defineRSSSource(url: string): () => Promise { return async () => { const data = await rss2json(url) if (!data?.items.length) throw new Error("Cannot fetch data") - return { - updatedTime: data.updatedTime ?? Date.now(), - items: data.items.slice(0, 20).map(item => ({ - title: item.title, - url: item.link, - id: item.link, - extra: { - date: item.created, - }, - })), - } + return data.items.slice(0, 20).map(item => ({ + title: item.title, + url: item.link, + id: item.link, + extra: { + date: item.created, + }, + })) } } @@ -32,7 +26,7 @@ interface Option { // default: 20 limit?: number } -export function defineRSSHubSource(route: string, option?: Option): () => Promise { +export function defineRSSHubSource(route: string, option?: Option): () => Promise { return async () => { const url = new URL(route, RSSHubBase) url.searchParams.set("format", "json") @@ -45,16 +39,13 @@ export function defineRSSHubSource(route: string, option?: Option): () => Promis url.searchParams.set(key, value.toString()) }) const data: RSSHubInfo = await $fetch(url) - return { - updatedTime: Date.now(), - items: data.items.slice(0, 20).map(item => ({ - title: item.title, - url: item.url, - id: item.id ?? item.url, - extra: { - date: item.date_published, - }, - })), - } + return data.items.slice(0, 20).map(item => ({ + title: item.title, + url: item.url, + id: item.id ?? item.url, + extra: { + date: item.date_published, + }, + })) } } diff --git a/shared/consts.ts b/shared/consts.ts index 9e1c384..b390571 100644 --- a/shared/consts.ts +++ b/shared/consts.ts @@ -1,8 +1,11 @@ -export const TTL = 15 * 60 * 1000 /** - * 默认刷新间隔,否则复用缓存 + * 缓存过期时间 */ -export const Interval = 30 * 60 * 1000 +export const TTL = 30 * 60 * 1000 +/** + * 默认刷新间隔 + */ +export const Interval = 10 * 60 * 1000 export const RSSHubBase = "https://rsshub.rssforever.com" // export const RSSHubBase = "https://rsshub.pseudoyu.com" diff --git a/shared/data.ts b/shared/data.ts index 6bdd5ef..0406091 100644 --- a/shared/data.ts +++ b/shared/data.ts @@ -5,8 +5,8 @@ export const sectionIds = ["focus", "social", "china", "world", "digital"] as co export const sources = { "wallstreetcn": { name: "华尔街见闻", - interval: 10, home: "https://wallstreetcn.com/", + interval: 3 * 60 * 1000, type: "快讯", }, // "36kr": { @@ -18,12 +18,12 @@ export const sources = { "36kr-quick": { name: "36氪", type: "快讯", - interval: 3, + interval: 3 * 60 * 1000, home: "https://36kr.com", }, "douyin": { name: "抖音", - interval: 1, + interval: 3 * 60 * 1000, home: "https://www.douyin.com", }, "hupu": { @@ -32,24 +32,21 @@ export const sources = { }, "zhihu": { name: "知乎", - interval: 10, home: "https://www.zhihu.com", }, "weibo": { name: "微博", type: "实时热搜", - interval: 1, + interval: 1 * 60 * 1000, home: "https://weibo.com", }, "tieba": { name: "百度贴吧", - interval: 2, home: "https://tieba.baidu.com", }, "zaobao": { name: "联合早报", type: "实时新闻", - interval: 10, home: "https://www.zaobao.com", }, "thepaper": { @@ -58,7 +55,6 @@ export const sources = { }, "toutiao": { name: "今日头条", - interval: 2, home: "https://www.toutiao.com", }, "cankaoxiaoxi": { @@ -73,13 +69,9 @@ export const sources = { name: string type?: string /** - * 分钟,刷新的间隔时间,复用缓存 + * 刷新的间隔时间,复用缓存 */ interval?: number - /** - * 每天刷新一次 - */ - once?: number home: string }> diff --git a/shared/types.ts b/shared/types.ts index 1c5973f..727dc8a 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -23,7 +23,7 @@ export interface SourceInfo { items: NewsItem[] } -export type OResponse = { +export type SourceResponse = { status: "success" | "cache" data: SourceInfo } | { @@ -48,9 +48,8 @@ export interface RSSItem { export interface CacheInfo { id: SourceID - data: SourceInfo + data: NewsItem[] updated: number - expires: number } export interface RSSHubInfo { diff --git a/src/components/section/Card.tsx b/src/components/section/Card.tsx index a5ecead..24d8ec0 100644 --- a/src/components/section/Card.tsx +++ b/src/components/section/Card.tsx @@ -1,4 +1,4 @@ -import type { NewsItem, SourceID, SourceInfo } from "@shared/types" +import type { NewsItem, SourceID, SourceInfo, SourceResponse } from "@shared/types" import { OverlayScrollbarsComponent } from "overlayscrollbars-react" import type { UseQueryResult } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query" @@ -71,11 +71,11 @@ export function NewsCard({ id, inView, isOverlay, handleListeners }: NewsCardPro if (Date.now() - _refetchTime < 1000) { url = `/api/${_id}?latest` } - const response = await fetch(url).then(res => res.json()) + const response: SourceResponse = await fetch(url).then(res => res.json()) if (response.status === "error") { throw new Error(response.message) } else { - return response.data as SourceInfo + return response.data } }, // refetch 时显示原有的数据