mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
pref: server end
This commit is contained in:
parent
9389f23ad7
commit
8ca82048de
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
}))
|
||||
}
|
||||
|
@ -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[]>>
|
||||
|
@ -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,
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}>
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 时显示原有的数据
|
||||
|
Loading…
x
Reference in New Issue
Block a user