pref: server end

This commit is contained in:
Ou 2024-10-05 17:20:49 +08:00
parent 9389f23ad7
commit 8ca82048de
9 changed files with 94 additions and 94 deletions

View File

@ -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<CacheInfo> {
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,

View File

@ -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<SourceResponse> => {
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) {

View File

@ -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<SourceInfo> {
export async function fallback(id: string): Promise<NewsItem[]> {
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,
}))
}

View File

@ -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<SourceID, () => Promise<SourceInfo>>
} as Record<SourceID, () => Promise<NewsItem[]>>

View File

@ -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<NewsItem[]>): () => Promise<SourceInfo> {
return async () => ({
updatedTime: Date.now(),
items: await source(),
})
export function defineSource(source: () => Promise<NewsItem[]>): () => Promise<NewsItem[]> {
return source
}
export function defineRSSSource(url: string): () => Promise<SourceInfo> {
export function defineRSSSource(url: string): () => Promise<NewsItem[]> {
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<SourceInfo> {
export function defineRSSHubSource(route: string, option?: Option): () => Promise<NewsItem[]> {
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,
},
}))
}
}

View File

@ -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"

View File

@ -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
}>

View File

@ -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 {

View File

@ -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 时显示原有的数据