From 3795b6989d65dc5b2f67f9c9e94e485d60f1f200 Mon Sep 17 00:00:00 2001 From: anime Date: Sun, 5 Jan 2025 22:47:23 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat(=E8=AE=BE=E7=BD=AEVercel=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E7=8E=AF=E5=A2=83=E4=B8=8B=E4=B8=8D=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E7=BC=93=E5=AD=98=E7=B1=BB=E5=9E=8B):=20=E9=92=88?= =?UTF-8?q?=E5=AF=B9=E7=89=B9=E6=80=A7=E7=BC=93=E5=AD=98=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=AE=BE=E7=BD=AE=EF=BC=8C=E4=B8=8D=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=E5=85=B6=E4=BB=96=E7=BC=93=E5=AD=98=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BE=BF=E4=BA=8E=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cache/cache_manager.js | 4 ++-- lib/cache/local_file_cache.js | 23 +++++++++++++++++++---- lib/cache/memory_cache.js | 10 ++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/cache/cache_manager.js b/lib/cache/cache_manager.js index f0c412abd5f..ad9586a3b5f 100644 --- a/lib/cache/cache_manager.js +++ b/lib/cache/cache_manager.js @@ -4,7 +4,7 @@ import MemoryCache from './memory_cache' import RedisCache from './redis_cache' // 配置是否开启Vercel环境中的缓存,因为Vercel中现有两种缓存方式在无服务环境下基本都是无意义的,纯粹的浪费资源 -const enableCacheInVercel = +export const isNotVercelProduction = process.env.npm_lifecycle_event === 'build' || process.env.npm_lifecycle_event === 'export' || !BLOG['isProd'] @@ -77,7 +77,7 @@ export async function getDataFromCache(key, force) { * @returns */ export async function setDataToCache(key, data, customCacheTime) { - if (!enableCacheInVercel || !data) { + if (!data) { return } // console.trace('[API-->>缓存写入]:', key) diff --git a/lib/cache/local_file_cache.js b/lib/cache/local_file_cache.js index c411d6e59d2..6422db0e007 100644 --- a/lib/cache/local_file_cache.js +++ b/lib/cache/local_file_cache.js @@ -1,4 +1,5 @@ import fs from 'fs' +import { isNotVercelProduction } from '@/lib/cache/cache_manager' const path = require('path') // 文件缓存持续10秒 @@ -6,7 +7,10 @@ const cacheInvalidSeconds = 1000000000 * 1000 // 文件名 const jsonFile = path.resolve('./data.json') -export async function getCache (key) { +export async function getCache(key) { + if (!isNotVercelProduction) { + return + } const exist = await fs.existsSync(jsonFile) if (!exist) return null const data = await fs.readFileSync(jsonFile) @@ -19,7 +23,9 @@ export async function getCache (key) { return null } // 缓存超过有效期就作废 - const cacheValidTime = new Date(parseInt(json[key + '_expire_time']) + cacheInvalidSeconds) + const cacheValidTime = new Date( + parseInt(json[key + '_expire_time']) + cacheInvalidSeconds + ) const currentTime = new Date() if (!cacheValidTime || cacheValidTime < currentTime) { return null @@ -33,7 +39,10 @@ export async function getCache (key) { * @param data * @returns {Promise} */ -export async function setCache (key, data) { +export async function setCache(key, data) { + if (!isNotVercelProduction) { + return + } const exist = await fs.existsSync(jsonFile) const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {} json[key] = data @@ -41,7 +50,10 @@ export async function setCache (key, data) { fs.writeFileSync(jsonFile, JSON.stringify(json)) } -export async function delCache (key) { +export async function delCache(key) { + if (!isNotVercelProduction) { + return + } const exist = await fs.existsSync(jsonFile) const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {} delete json.key @@ -53,6 +65,9 @@ export async function delCache (key) { * 清理缓存 */ export async function cleanCache() { + if (!isNotVercelProduction) { + return + } const json = {} fs.writeFileSync(jsonFile, JSON.stringify(json)) } diff --git a/lib/cache/memory_cache.js b/lib/cache/memory_cache.js index 001666c3fe0..183353b50dd 100644 --- a/lib/cache/memory_cache.js +++ b/lib/cache/memory_cache.js @@ -1,17 +1,27 @@ import cache from 'memory-cache' import BLOG from 'blog.config' +import { isNotVercelProduction } from '@/lib/cache/cache_manager' const cacheTime = BLOG.isProd ? 10 * 60 : 120 * 60 // 120 minutes for dev,10 minutes for prod export async function getCache(key, options) { + if (!isNotVercelProduction) { + return + } return await cache.get(key) } export async function setCache(key, data, customCacheTime) { + if (!isNotVercelProduction) { + return + } await cache.put(key, data, (customCacheTime || cacheTime) * 1000) } export async function delCache(key) { + if (!isNotVercelProduction) { + return + } await cache.del(key) } From 6ebf4e07b2bc48b13682d69fd51cceeb1cc23c49 Mon Sep 17 00:00:00 2001 From: anime Date: Mon, 6 Jan 2025 16:05:36 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat(UUID=5FREDIRECT=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81siteConfig=E8=8E=B7=E5=8F=96):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog.config.js | 2 +- lib/config.js | 1 - pages/index.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/blog.config.js b/blog.config.js index 97b66b9e4dd..1ad840ee87e 100644 --- a/blog.config.js +++ b/blog.config.js @@ -63,7 +63,7 @@ const BLOG = { process.env.NEXT_PUBLIC_GREETING_WORDS || 'Hi,我是一个程序员, Hi,我是一个打工人,Hi,我是一个干饭人,欢迎来到我的博客🎉', - // uuid重定向至 slug + // uuid重定向至 slug(不支持Notion配置!) UUID_REDIRECT: process.env.UUID_REDIRECT || false } diff --git a/lib/config.js b/lib/config.js index 54b043a7d57..4244a04eb62 100644 --- a/lib/config.js +++ b/lib/config.js @@ -43,7 +43,6 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { case 'AI_SUMMARY_KEY': case 'AI_SUMMARY_CACHE_TIME': case 'AI_SUMMARY_WORD_LIMIT': - case 'UUID_REDIRECT': // LINK比较特殊, if (key === 'LINK') { if (!extendConfig || Object.keys(extendConfig).length === 0) { diff --git a/pages/index.js b/pages/index.js index b66b56104d2..1ada9472a87 100644 --- a/pages/index.js +++ b/pages/index.js @@ -61,7 +61,7 @@ export async function getStaticProps(req) { generateRss(props) // 生成 generateSitemapXml(props) - if (siteConfig('UUID_REDIRECT', false, props?.NOTION_CONFIG)) { + if (BLOG['UUID_REDIRECT']) { // 生成重定向 JSON generateRedirectJson(props) } From 1f233e0d9417beee654254e91dd33410c8a69a91 Mon Sep 17 00:00:00 2001 From: anime Date: Mon, 6 Jan 2025 16:06:27 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat(=E4=BD=BF=E7=94=A8Redis=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=A4=9A=E8=AF=AD=E8=A8=80=E7=89=88UUID=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E5=90=91):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cache/redis_cache.js | 6 ++--- lib/redirect.js | 25 +++++++++++++++---- middleware.ts | 54 ++++++++++++++++++++++++++++------------ pages/api/redirect.js | 12 +++++++++ 4 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 pages/api/redirect.js diff --git a/lib/cache/redis_cache.js b/lib/cache/redis_cache.js index b35f472ad54..4212e176004 100644 --- a/lib/cache/redis_cache.js +++ b/lib/cache/redis_cache.js @@ -2,9 +2,9 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import Redis from 'ioredis' -export const redisClient = BLOG.REDIS_URL ? new Redis(BLOG.REDIS_URL) : {} +export const redisClient = BLOG.REDIS_URL ? new Redis(BLOG.REDIS_URL) : null -const cacheTime = Math.trunc( +export const redisCacheTime = Math.trunc( siteConfig('NEXT_REVALIDATE_SECOND', BLOG.NEXT_REVALIDATE_SECOND) * 1.5 ) @@ -23,7 +23,7 @@ export async function setCache(key, data, customCacheTime) { key, JSON.stringify(data), 'EX', - customCacheTime || cacheTime + customCacheTime || redisCacheTime ) } catch (e) { console.error('redisClient写入失败 ' + e) diff --git a/lib/redirect.js b/lib/redirect.js index 7a05dfee057..5bf5e404907 100644 --- a/lib/redirect.js +++ b/lib/redirect.js @@ -1,15 +1,30 @@ import fs from 'fs' +import { redisCacheTime, redisClient } from '@/lib/cache/redis_cache' -export function generateRedirectJson({ allPages }) { +export const redirectCacheKey = 'uuid_slug_map' + +export async function generateRedirectJson({ allPages }) { let uuidSlugMap = {} allPages.forEach(page => { if (page.type === 'Post' && page.status === 'Published') { uuidSlugMap[page.id] = page.slug } }) - try { - fs.writeFileSync('./public/redirect.json', JSON.stringify(uuidSlugMap)) - } catch (error) { - console.warn('无法写入文件', error) + if (redisClient) { + try { + await redisClient.hset( + redirectCacheKey, + uuidSlugMap, + async () => await redisClient.expire(redirectCacheKey, redisCacheTime) + ) + } catch (e) { + console.warn('写入Redis失败', e) + } + } else { + try { + fs.writeFileSync('./public/redirect.json', JSON.stringify(uuidSlugMap)) + } catch (error) { + console.warn('无法写入文件', error) + } } } diff --git a/middleware.ts b/middleware.ts index 550c6f8b08a..7a1947a0db8 100644 --- a/middleware.ts +++ b/middleware.ts @@ -36,26 +36,48 @@ const isTenantAdminRoute = createRouteMatcher([ const noAuthMiddleware = async (req: NextRequest, ev: any) => { // 如果没有配置 Clerk 相关环境变量,返回一个默认响应或者继续处理请求 if (BLOG['UUID_REDIRECT']) { - let redirectJson: Record = {} - try { - const response = await fetch(`${req.nextUrl.origin}/redirect.json`) - if (response.ok) { - redirectJson = (await response.json()) as Record - } - } catch (err) { - console.error('Error fetching static file:', err) - } let lastPart = getLastPartOfUrl(req.nextUrl.pathname) as string if (checkStrIsNotionId(lastPart)) { lastPart = idToUuid(lastPart) } - if (lastPart && redirectJson[lastPart]) { - const redirectToUrl = req.nextUrl.clone() - redirectToUrl.pathname = '/' + redirectJson[lastPart] - console.log( - `redirect from ${req.nextUrl.pathname} to ${redirectToUrl.pathname}` - ) - return NextResponse.redirect(redirectToUrl, 308) + if (lastPart) { + let redirectJson: Record = {} + if (BLOG.REDIS_URL) { + try { + const redisResponse = await fetch(`${req.nextUrl.origin}/api/redirect`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + lastPart: lastPart + }) + }) + const redisResult = await redisResponse.json() + redirectJson = { + [lastPart]: redisResult.data + } + } catch (e) { + console.warn('读取Redis失败', e) + } + } else { + try { + const response = await fetch(`${req.nextUrl.origin}/redirect.json`) + if (response.ok) { + redirectJson = (await response.json()) as Record + } + } catch (err) { + console.error('Error fetching static file:', err) + } + } + if (redirectJson[lastPart]) { + const redirectToUrl = req.nextUrl.clone() + redirectToUrl.pathname = '/' + redirectJson[lastPart] + console.log( + `redirect from ${req.nextUrl.pathname} to ${redirectToUrl.pathname}` + ) + return NextResponse.redirect(redirectToUrl, 308) + } } } return NextResponse.next() diff --git a/pages/api/redirect.js b/pages/api/redirect.js new file mode 100644 index 00000000000..df60b8056d2 --- /dev/null +++ b/pages/api/redirect.js @@ -0,0 +1,12 @@ +import { redisClient } from '@/lib/cache/redis_cache' +import { redirectCacheKey } from '@/lib/redirect' + +export default async function handler(req, res) { + const { lastPart } = req.body + try { + const result = (await redisClient.hget(redirectCacheKey, lastPart)) || null + res.status(200).json({ status: 'success', data: result }) + } catch (error) { + res.status(400).json({ status: 'error', message: 'failed!', error }) + } +} From 7af527d66f3bd6db8e54d8b19c91eaf1968098d9 Mon Sep 17 00:00:00 2001 From: anime Date: Mon, 6 Jan 2025 16:51:22 +0800 Subject: [PATCH 04/12] =?UTF-8?q?docs(=E6=B7=BB=E5=8A=A0Redis=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=97=B6=E9=97=B4=E8=AD=A6=E5=91=8A):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/dev.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/dev.config.js b/conf/dev.config.js index c34a8c6430a..6151ae5d8e6 100644 --- a/conf/dev.config.js +++ b/conf/dev.config.js @@ -8,7 +8,7 @@ module.exports = { BACKGROUND_LIGHT: '#eeeeee', // use hex value, don't forget '#' e.g #fffefc BACKGROUND_DARK: '#000000', // use hex value, don't forget '#' - // Redis 缓存数据库地址 + // Redis 缓存数据库地址 (警告:缓存时间使用了NEXT_REVALIDATE_SECOND,且无法从Notion获取) REDIS_URL: process.env.REDIS_URL || '', ENABLE_CACHE: From c86cd44ab71dee465bbe4a105243cd5d7ce111ff Mon Sep 17 00:00:00 2001 From: anime Date: Tue, 7 Jan 2025 01:38:52 +0800 Subject: [PATCH 05/12] =?UTF-8?q?perf(=E4=BC=98=E5=8C=96UUID=20Redirect?= =?UTF-8?q?=E5=8A=9F=E8=83=BD):=20=E9=9C=80=E8=A6=81=E4=B8=BAUUID=E6=89=8D?= =?UTF-8?q?=E8=B0=83=E7=94=A8=EF=BC=8C=E5=87=8F=E5=B0=91=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=AC=A1=E6=95=B0=EF=BC=8C=E5=9B=A0=E4=B8=BA=E5=8E=9F=E6=9C=AC?= =?UTF-8?q?=E6=98=AF=E6=89=93=E7=AE=97old=20slug=E4=B9=9F=E5=86=99?= =?UTF-8?q?=E5=85=A5=E8=BF=99=E9=87=8C=E7=9A=84=EF=BC=8C=E4=BD=86=E6=98=AF?= =?UTF-8?q?=E8=BF=98=E6=B2=A1=E6=9C=89=E5=81=9A=E5=A5=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/index.js | 11 +++++++++++ middleware.ts | 29 ++++++++++++++++++----------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 787dc90ddf7..e12f2f85fc3 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -104,6 +104,17 @@ export function checkStartWithHttp(str) { } } +// 检查一个字符串是否UUID https://ihateregex.io/expr/uuid/ +export function checkStrIsUuid(str) { + if (!str) { + return false + } + // 使用正则表达式进行匹配 + const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/ + return regex.test(str) +} + + // 检查一个字符串是否notionid : 32位,仅由数字英文构成 export function checkStrIsNotionId(str) { if (!str) { diff --git a/middleware.ts b/middleware.ts index 7a1947a0db8..fe5aaddbb32 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,6 +1,10 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' -import { checkStrIsNotionId, getLastPartOfUrl } from '@/lib/utils' +import { + checkStrIsNotionId, + checkStrIsUuid, + getLastPartOfUrl +} from '@/lib/utils' import { idToUuid } from 'notion-utils' import BLOG from './blog.config' @@ -40,19 +44,22 @@ const noAuthMiddleware = async (req: NextRequest, ev: any) => { if (checkStrIsNotionId(lastPart)) { lastPart = idToUuid(lastPart) } - if (lastPart) { + if (checkStrIsUuid(lastPart)) { let redirectJson: Record = {} if (BLOG.REDIS_URL) { try { - const redisResponse = await fetch(`${req.nextUrl.origin}/api/redirect`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - lastPart: lastPart - }) - }) + const redisResponse = await fetch( + `${req.nextUrl.origin}/api/redirect`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + lastPart: lastPart + }) + } + ) const redisResult = await redisResponse.json() redirectJson = { [lastPart]: redisResult.data From 6e47e418aab9ce0ddc1f3632ad28e0bef709eeb3 Mon Sep 17 00:00:00 2001 From: anime Date: Wed, 8 Jan 2025 15:55:47 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat(Redirect=20API=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=BC=93=E5=AD=98):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/api/redirect.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pages/api/redirect.js b/pages/api/redirect.js index df60b8056d2..9c01f7b1f91 100644 --- a/pages/api/redirect.js +++ b/pages/api/redirect.js @@ -1,10 +1,14 @@ -import { redisClient } from '@/lib/cache/redis_cache' +import { redisCacheTime, redisClient } from '@/lib/cache/redis_cache' import { redirectCacheKey } from '@/lib/redirect' export default async function handler(req, res) { const { lastPart } = req.body try { const result = (await redisClient.hget(redirectCacheKey, lastPart)) || null + res.setHeader( + 'Cache-Control', + `public, max-age=${redisCacheTime}, stale-while-revalidate=${redisCacheTime / 6}` + ) res.status(200).json({ status: 'success', data: result }) } catch (error) { res.status(400).json({ status: 'error', message: 'failed!', error }) From fb35c4435a90f5f16ac3f8974c5d7493c82b2694 Mon Sep 17 00:00:00 2001 From: anime Date: Sat, 11 Jan 2025 21:31:20 +0800 Subject: [PATCH 07/12] =?UTF-8?q?feat(=E8=B0=83=E6=95=B4=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=92=8C=E5=AF=BC=E5=87=BA):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cache/redis_cache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cache/redis_cache.js b/lib/cache/redis_cache.js index b35f472ad54..30d65a5f841 100644 --- a/lib/cache/redis_cache.js +++ b/lib/cache/redis_cache.js @@ -4,7 +4,7 @@ import Redis from 'ioredis' export const redisClient = BLOG.REDIS_URL ? new Redis(BLOG.REDIS_URL) : {} -const cacheTime = Math.trunc( +export const redisCacheTime = Math.trunc( siteConfig('NEXT_REVALIDATE_SECOND', BLOG.NEXT_REVALIDATE_SECOND) * 1.5 ) @@ -23,7 +23,7 @@ export async function setCache(key, data, customCacheTime) { key, JSON.stringify(data), 'EX', - customCacheTime || cacheTime + customCacheTime || redisCacheTime ) } catch (e) { console.error('redisClient写入失败 ' + e) From 65036bcebcad7996b8f74449d4bb1433e40c8f9e Mon Sep 17 00:00:00 2001 From: anime Date: Sat, 11 Jan 2025 22:19:27 +0800 Subject: [PATCH 08/12] =?UTF-8?q?feat(=E6=94=AF=E6=8C=81=20upstash=20Redis?= =?UTF-8?q?=20=E4=BE=BF=E4=BA=8E=E4=B8=AD=E9=97=B4=E4=BB=B6=E8=B0=83?= =?UTF-8?q?=E7=94=A8):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/dev.config.js | 4 +++ lib/cache/upstash_redis_cache.js | 47 ++++++++++++++++++++++++++++++++ package.json | 1 + yarn.lock | 10 ++++++- 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 lib/cache/upstash_redis_cache.js diff --git a/conf/dev.config.js b/conf/dev.config.js index 6151ae5d8e6..90ca0742c9b 100644 --- a/conf/dev.config.js +++ b/conf/dev.config.js @@ -11,6 +11,10 @@ module.exports = { // Redis 缓存数据库地址 (警告:缓存时间使用了NEXT_REVALIDATE_SECOND,且无法从Notion获取) REDIS_URL: process.env.REDIS_URL || '', + // UpStash Redis 缓存数据库地址(支持RESTful API调用,中间件可用) + UPSTASH_REDIS_URL: process.env.UPSTASH_REDIS_URL || '', + UPSTASH_REDIS_TOKEN: process.env.UPSTASH_REDIS_TOKEN || '', + ENABLE_CACHE: process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build' || diff --git a/lib/cache/upstash_redis_cache.js b/lib/cache/upstash_redis_cache.js new file mode 100644 index 00000000000..10a5f06a696 --- /dev/null +++ b/lib/cache/upstash_redis_cache.js @@ -0,0 +1,47 @@ +import BLOG from '@/blog.config' +import { Redis } from '@upstash/redis' +import { siteConfig } from '@/lib/config' + +export const upstashRedisClient = + BLOG.UPSTASH_REDIS_URL && BLOG.UPSTASH_REDIS_TOKEN + ? new Redis({ + url: BLOG.UPSTASH_REDIS_URL, + token: BLOG.UPSTASH_REDIS_TOKEN + }) + : null + +export const upstashRedisCacheTime = Math.trunc( + siteConfig('NEXT_REVALIDATE_SECOND', BLOG.NEXT_REVALIDATE_SECOND) * 1.5 +) + +export async function getCache(key) { + try { + const data = await upstashRedisClient.get(key) + return data ? JSON.parse(data) : null + } catch (e) { + console.error('upstash 读取失败 ' + e) + } +} + +export async function setCache(key, data, customCacheTime) { + try { + await upstashRedisClient.set( + key, + JSON.stringify(data), + 'EX', + customCacheTime || upstashRedisCacheTime + ) + } catch (e) { + console.error('upstash 写入失败 ' + e) + } +} + +export async function delCache(key) { + try { + await upstashRedisClient.del(key) + } catch (e) { + console.error('upstash 删除失败 ' + e) + } +} + +export default { getCache, setCache, delCache } diff --git a/package.json b/package.json index f9d7ec20a4c..142904aa8f2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@clerk/nextjs": "^5.1.5", "@headlessui/react": "^1.7.15", "@next/bundle-analyzer": "^12.1.1", + "@upstash/redis": "^1.34.3", "@vercel/analytics": "^1.0.0", "algoliasearch": "^4.18.0", "axios": "^1.7.2", diff --git a/yarn.lock b/yarn.lock index 74fd3138b00..becbd788080 100644 --- a/yarn.lock +++ b/yarn.lock @@ -706,6 +706,13 @@ resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@upstash/redis@^1.34.3": + version "1.34.3" + resolved "https://mirrors.cloud.tencent.com/npm/@upstash/redis/-/redis-1.34.3.tgz#df0338f4983bba5141878e851be4fced494b44a0" + integrity sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ== + dependencies: + crypto-js "^4.2.0" + "@vercel/analytics@^1.0.0": version "1.4.1" resolved "https://registry.npmmirror.com/@vercel/analytics/-/analytics-1.4.1.tgz#a28a93133d68b6e3d86884a52fa7893f5ecaa381" @@ -1320,7 +1327,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@4.2.0: +crypto-js@4.2.0, crypto-js@^4.2.0: version "4.2.0" resolved "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== @@ -3985,6 +3992,7 @@ streamsearch@^1.1.0: integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== From a3ebccfbaa80fc0e3766b48b1b86831ecf3c92d6 Mon Sep 17 00:00:00 2001 From: anime Date: Sat, 11 Jan 2025 22:41:30 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat(=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20upstash=20Redis=20=E8=BF=9B=E8=A1=8C=20UUID=20=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E5=90=91):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog.config.js | 3 ++- lib/redirect.js | 25 ++++++++++++++++++++----- middleware.ts | 17 +++++++++++------ pages/api/redirect.js | 5 +++-- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/blog.config.js b/blog.config.js index 1ad840ee87e..cafd06ca610 100644 --- a/blog.config.js +++ b/blog.config.js @@ -64,7 +64,8 @@ const BLOG = { 'Hi,我是一个程序员, Hi,我是一个打工人,Hi,我是一个干饭人,欢迎来到我的博客🎉', // uuid重定向至 slug(不支持Notion配置!) - UUID_REDIRECT: process.env.UUID_REDIRECT || false + UUID_REDIRECT: process.env.UUID_REDIRECT || false, + REDIRECT_CACHE_KEY: process.env.REDIRECT_CACHE_KEY || 'uuid_slug_map' } module.exports = BLOG diff --git a/lib/redirect.js b/lib/redirect.js index 5bf5e404907..f3ca89097ac 100644 --- a/lib/redirect.js +++ b/lib/redirect.js @@ -1,7 +1,10 @@ import fs from 'fs' import { redisCacheTime, redisClient } from '@/lib/cache/redis_cache' - -export const redirectCacheKey = 'uuid_slug_map' +import { + upstashRedisCacheTime, + upstashRedisClient +} from '@/lib/cache/upstash_redis_cache' +import BLOG from '@/blog.config' export async function generateRedirectJson({ allPages }) { let uuidSlugMap = {} @@ -10,12 +13,24 @@ export async function generateRedirectJson({ allPages }) { uuidSlugMap[page.id] = page.slug } }) - if (redisClient) { + if (upstashRedisClient) { + try { + await upstashRedisClient.hset(BLOG.REDIRECT_CACHE_KEY, uuidSlugMap) + + await upstashRedisClient.expire( + BLOG.REDIRECT_CACHE_KEY, + upstashRedisCacheTime + ) + } catch (e) { + console.warn('写入 upstashRedis 失败', e) + } + } else if (redisClient) { try { await redisClient.hset( - redirectCacheKey, + BLOG.REDIRECT_CACHE_KEY, uuidSlugMap, - async () => await redisClient.expire(redirectCacheKey, redisCacheTime) + async () => + await redisClient.expire(BLOG.REDIRECT_CACHE_KEY, redisCacheTime) ) } catch (e) { console.warn('写入Redis失败', e) diff --git a/middleware.ts b/middleware.ts index fe5aaddbb32..9e771ca7382 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,12 +1,9 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' -import { - checkStrIsNotionId, - checkStrIsUuid, - getLastPartOfUrl -} from '@/lib/utils' +import { checkStrIsNotionId, checkStrIsUuid, getLastPartOfUrl } from '@/lib/utils' import { idToUuid } from 'notion-utils' import BLOG from './blog.config' +import { upstashRedisClient } from '@/lib/cache/upstash_redis_cache' /** * Clerk 身份验证中间件 @@ -46,7 +43,15 @@ const noAuthMiddleware = async (req: NextRequest, ev: any) => { } if (checkStrIsUuid(lastPart)) { let redirectJson: Record = {} - if (BLOG.REDIS_URL) { + if (upstashRedisClient) { + const redisResult = (await upstashRedisClient.hget( + BLOG.REDIRECT_CACHE_KEY, + lastPart + )) as string + redirectJson = { + [lastPart]: redisResult + } + } else if (BLOG.REDIS_URL) { try { const redisResponse = await fetch( `${req.nextUrl.origin}/api/redirect`, diff --git a/pages/api/redirect.js b/pages/api/redirect.js index 9c01f7b1f91..5ed28e1e779 100644 --- a/pages/api/redirect.js +++ b/pages/api/redirect.js @@ -1,10 +1,11 @@ import { redisCacheTime, redisClient } from '@/lib/cache/redis_cache' -import { redirectCacheKey } from '@/lib/redirect' +import BLOG from '@/blog.config' export default async function handler(req, res) { const { lastPart } = req.body try { - const result = (await redisClient.hget(redirectCacheKey, lastPart)) || null + const result = + (await redisClient.hget(BLOG.REDIRECT_CACHE_KEY, lastPart)) || null res.setHeader( 'Cache-Control', `public, max-age=${redisCacheTime}, stale-while-revalidate=${redisCacheTime / 6}` From 4860dc09beb46d1f3f704e29adf3509ff35bd212 Mon Sep 17 00:00:00 2001 From: anime Date: Sat, 11 Jan 2025 22:58:01 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fix(=E9=81=BF=E5=85=8D=E8=B0=83=E7=94=A8s?= =?UTF-8?q?iteConfig=E8=8E=B7=E5=8F=96=E9=85=8D=E7=BD=AE):=20=E5=9B=A0?= =?UTF-8?q?=E4=B8=BA=E5=86=85=E9=83=A8=E8=B0=83=E7=94=A8=E4=BA=86=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BD=86=E4=B8=AD?= =?UTF-8?q?=E9=97=B4=E4=BB=B6=E4=B8=8D=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Error: Attempted to call siteConfig() from the server but siteConfig is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component. --- lib/cache/upstash_redis_cache.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cache/upstash_redis_cache.js b/lib/cache/upstash_redis_cache.js index 10a5f06a696..7ec8537a7e5 100644 --- a/lib/cache/upstash_redis_cache.js +++ b/lib/cache/upstash_redis_cache.js @@ -1,6 +1,5 @@ import BLOG from '@/blog.config' import { Redis } from '@upstash/redis' -import { siteConfig } from '@/lib/config' export const upstashRedisClient = BLOG.UPSTASH_REDIS_URL && BLOG.UPSTASH_REDIS_TOKEN @@ -11,7 +10,7 @@ export const upstashRedisClient = : null export const upstashRedisCacheTime = Math.trunc( - siteConfig('NEXT_REVALIDATE_SECOND', BLOG.NEXT_REVALIDATE_SECOND) * 1.5 + BLOG.NEXT_REVALIDATE_SECOND * 1.5 ) export async function getCache(key) { From 6afdfd982d466b8ca9dc20a3dfdc52c2a1789815 Mon Sep 17 00:00:00 2001 From: anime Date: Sun, 12 Jan 2025 02:37:19 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat(=E5=8F=96=E6=B6=88=20Redis=20?= =?UTF-8?q?=E4=B8=AD=20uuidSlugMap=20=E7=BC=93=E5=AD=98=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E8=AE=BE=E7=BD=AE):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/redirect.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/redirect.js b/lib/redirect.js index f3ca89097ac..d452af7e2ed 100644 --- a/lib/redirect.js +++ b/lib/redirect.js @@ -1,9 +1,6 @@ import fs from 'fs' -import { redisCacheTime, redisClient } from '@/lib/cache/redis_cache' -import { - upstashRedisCacheTime, - upstashRedisClient -} from '@/lib/cache/upstash_redis_cache' +import { redisClient } from '@/lib/cache/redis_cache' +import { upstashRedisClient } from '@/lib/cache/upstash_redis_cache' import BLOG from '@/blog.config' export async function generateRedirectJson({ allPages }) { @@ -16,22 +13,12 @@ export async function generateRedirectJson({ allPages }) { if (upstashRedisClient) { try { await upstashRedisClient.hset(BLOG.REDIRECT_CACHE_KEY, uuidSlugMap) - - await upstashRedisClient.expire( - BLOG.REDIRECT_CACHE_KEY, - upstashRedisCacheTime - ) } catch (e) { console.warn('写入 upstashRedis 失败', e) } } else if (redisClient) { try { - await redisClient.hset( - BLOG.REDIRECT_CACHE_KEY, - uuidSlugMap, - async () => - await redisClient.expire(BLOG.REDIRECT_CACHE_KEY, redisCacheTime) - ) + await redisClient.hset(BLOG.REDIRECT_CACHE_KEY, uuidSlugMap) } catch (e) { console.warn('写入Redis失败', e) } From 931b8f875b6334a8b6db57abe0fef6d2c09a28fb Mon Sep 17 00:00:00 2001 From: anime Date: Sun, 12 Jan 2025 02:39:06 +0800 Subject: [PATCH 12/12] =?UTF-8?q?feat(=E5=AE=8C=E5=96=84UUID=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E5=90=91=E5=BC=82=E5=B8=B8=E5=80=BC=E5=A4=84=E7=90=86?= =?UTF-8?q?):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- middleware.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/middleware.ts b/middleware.ts index 9e771ca7382..7d20e98a7d4 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,6 +1,10 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' -import { checkStrIsNotionId, checkStrIsUuid, getLastPartOfUrl } from '@/lib/utils' +import { + checkStrIsNotionId, + checkStrIsUuid, + getLastPartOfUrl +} from '@/lib/utils' import { idToUuid } from 'notion-utils' import BLOG from './blog.config' import { upstashRedisClient } from '@/lib/cache/upstash_redis_cache' @@ -65,9 +69,9 @@ const noAuthMiddleware = async (req: NextRequest, ev: any) => { }) } ) - const redisResult = await redisResponse.json() + const redisResult = await redisResponse?.json() redirectJson = { - [lastPart]: redisResult.data + [lastPart]: redisResult?.data } } catch (e) { console.warn('读取Redis失败', e)