mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-09-03 06:24:24 +08:00
refactor(api): 更新缓存标签处理和API调用逻辑
- 在 revalidate 路由中引入 isAllowedCacheTag 函数,优化无效标签过滤逻辑。 - 更新评论和点赞相关组件,替换 API 调用为新的 action 函数,提升代码一致性。 - 在多个页面中使用缓存 API 以提高数据获取效率,简化 API 请求逻辑。
This commit is contained in:
36
src/actions/article.ts
Normal file
36
src/actions/article.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
'use server';
|
||||
|
||||
import { refresh, updateTag } from 'next/cache';
|
||||
|
||||
import { Request } from '@/utils/request';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
import { Comment } from '@/types/app/comment';
|
||||
|
||||
export async function likeArticleAction(id: number, count: number) {
|
||||
const result = await Request<number>('POST', `/article/${id}/like`, { count });
|
||||
|
||||
if (result.code === 200) {
|
||||
updateTag(`${CACHE_TAGS.article}-${id}`);
|
||||
updateTag(CACHE_TAGS.articles);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addArticleCommentAction(data: Comment) {
|
||||
const result = await Request('POST', '/comment', data);
|
||||
|
||||
// 如果请求成功则更新缓存
|
||||
if (data.articleId && result.code === 200) {
|
||||
// 更新文章详情缓存
|
||||
updateTag(`${CACHE_TAGS.article}-${data.articleId}`);
|
||||
// 更新文章列表缓存
|
||||
updateTag(CACHE_TAGS.articles);
|
||||
// 更新文章评论列表缓存
|
||||
updateTag(`${CACHE_TAGS.comments}-${data.articleId}`);
|
||||
// 触发页面重新渲染
|
||||
refresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
37
src/actions/record.ts
Normal file
37
src/actions/record.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
'use server';
|
||||
|
||||
import { refresh, updateTag } from 'next/cache';
|
||||
|
||||
import { Request } from '@/utils/request';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
import { RecordComment } from '@/types/app/recordComment';
|
||||
|
||||
export async function likeRecordAction(id: number, count: number) {
|
||||
const result = await Request<number>('POST', `/record/${id}/like`, { count });
|
||||
|
||||
if (result.code === 200) {
|
||||
updateTag(CACHE_TAGS.records);
|
||||
updateTag(`${CACHE_TAGS.record}-${id}`);
|
||||
refresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addRecordCommentAction(data: RecordComment) {
|
||||
const result = await Request('POST', '/record/comment', data);
|
||||
|
||||
// 如果请求成功则更新缓存
|
||||
if (data.recordId && result.code === 200) {
|
||||
// 更新说说列表缓存
|
||||
updateTag(CACHE_TAGS.records);
|
||||
// 更新说说详情缓存
|
||||
updateTag(`${CACHE_TAGS.record}-${data.recordId}`);
|
||||
// 更新说说评论列表缓存
|
||||
updateTag(`${CACHE_TAGS.comments}-${data.recordId}`);
|
||||
// 触发页面重新渲染
|
||||
refresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
25
src/actions/wall.ts
Normal file
25
src/actions/wall.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
'use server';
|
||||
|
||||
import { refresh, updateTag } from 'next/cache';
|
||||
|
||||
import { Request } from '@/utils/request';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
import { Wall } from '@/types/app/wall';
|
||||
|
||||
export async function addWallAction(data: Wall) {
|
||||
const result = await Request('POST', '/wall', data);
|
||||
|
||||
// 如果请求成功则更新缓存
|
||||
if (result.code === 200) {
|
||||
// 更新留言列表缓存
|
||||
updateTag(CACHE_TAGS.walls);
|
||||
// 更新留言分类缓存
|
||||
if (data.cateId) {
|
||||
updateTag(`${CACHE_TAGS.wall}-${data.cateId}`);
|
||||
}
|
||||
// 触发页面重新渲染
|
||||
refresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
21
src/actions/web.ts
Normal file
21
src/actions/web.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
'use server';
|
||||
|
||||
import { refresh, updateTag } from 'next/cache';
|
||||
|
||||
import { Request } from '@/utils/request';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
import { Web } from '@/types/app/web';
|
||||
|
||||
export async function addWebAction(data: Web) {
|
||||
const result = await Request('POST', '/link', data);
|
||||
|
||||
// 如果请求成功则更新缓存
|
||||
if (result.code === 200) {
|
||||
// 更新友链列表缓存
|
||||
updateTag(CACHE_TAGS.webs);
|
||||
// 触发页面重新渲染
|
||||
refresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -6,9 +6,7 @@
|
||||
import { revalidateTag } from 'next/cache';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
|
||||
const ALLOWED_TAGS = new Set(Object.values(CACHE_TAGS));
|
||||
import { CACHE_TAGS, isAllowedCacheTag } from '@/lib/cache-tags';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const secret = req.headers.get('x-revalidate-secret');
|
||||
@@ -33,8 +31,8 @@ export async function POST(req: NextRequest) {
|
||||
// 无 body 时使用默认 tags
|
||||
}
|
||||
|
||||
// 过滤掉无效的标签
|
||||
const invalidTags = tags.filter((tag) => !ALLOWED_TAGS.has(tag as (typeof CACHE_TAGS)[keyof typeof CACHE_TAGS]) && !tag.startsWith(`${CACHE_TAGS.article}-`) && !tag.startsWith(`${CACHE_TAGS.articlesList}-`));
|
||||
// 过滤掉无效的标签 使用 isAllowedCacheTag 判断是否是允许的缓存标签
|
||||
const invalidTags = tags.filter((tag) => !isAllowedCacheTag(tag));
|
||||
// 如果无效的标签,则返回 400 错误
|
||||
if (invalidTags.length) {
|
||||
return NextResponse.json({ message: 'Invalid tags', invalidTags }, { status: 400 });
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { addCommentDataAPI } from '@/api/comment';
|
||||
import { addArticleCommentAction } from '@/actions/article';
|
||||
import { Bounce, ToastOptions, toast } from 'react-toastify';
|
||||
import { Spinner, Popover, PopoverTrigger, PopoverContent } from '@/ThriveUI';
|
||||
import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
@@ -89,7 +89,7 @@ const CommentForm = ({ articleId }: Props) => {
|
||||
if (!isNaN(+qq)) data.avatar = `https://q1.qlogo.cn/g?b=qq&nk=${qq}&s=640`;
|
||||
}
|
||||
|
||||
const { code, message } = await addCommentDataAPI({
|
||||
const { code, message } = await addArticleCommentAction({
|
||||
...data,
|
||||
articleId,
|
||||
commentId: commentId === articleId ? 0 : commentId,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { createContext, useContext, type ReactNode } from 'react';
|
||||
import useDebouncedLike from '@/hooks/useDebouncedLike';
|
||||
import { likeArticleAPI } from '@/api/article';
|
||||
import { likeArticleAction } from '@/actions/article';
|
||||
import LikeButtonCore from '@/components/LikeButton/LikeButtonCore';
|
||||
|
||||
interface ArticleLikeContextValue {
|
||||
@@ -27,7 +27,7 @@ export function ArticleLikeProvider({
|
||||
initialCount?: number;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const value = useDebouncedLike(articleId, initialCount, likeArticleAPI);
|
||||
const value = useDebouncedLike(articleId, initialCount, likeArticleAction);
|
||||
return <ArticleLikeContext.Provider value={value}>{children}</ArticleLikeContext.Provider>;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
} from '@/ThriveUI';
|
||||
import { type SubmitHandler } from 'react-hook-form';
|
||||
import { Web, WebType } from '@/types/app/web';
|
||||
import { addWebDataAPI, getWebTypeListAPI } from '@/api/web';
|
||||
import { addWebAction } from '@/actions/web';
|
||||
import { getWebTypeListAPI } from '@/api/web';
|
||||
import { Bounce, toast, ToastOptions } from 'react-toastify';
|
||||
import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
@@ -50,12 +51,6 @@ export default () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const message = localStorage.getItem('toastMessage');
|
||||
if (message) {
|
||||
toast.success(message, toastConfig);
|
||||
localStorage.removeItem('toastMessage');
|
||||
}
|
||||
|
||||
getWebTypeList();
|
||||
}, []);
|
||||
|
||||
@@ -68,7 +63,7 @@ export default () => {
|
||||
if (hasHCaptcha && !captchaToken) return setCaptchaError('请完成人机验证');
|
||||
|
||||
setLoading(true);
|
||||
const { code, message } = await addWebDataAPI({
|
||||
const { code, message } = await addWebAction({
|
||||
...data,
|
||||
createTime: Date.now().toString(),
|
||||
h_captcha_response: captchaToken!,
|
||||
@@ -83,9 +78,9 @@ export default () => {
|
||||
setCaptchaError('');
|
||||
setCaptchaToken(null);
|
||||
captchaRef.current?.resetCaptcha();
|
||||
methods.reset({} as Web);
|
||||
|
||||
localStorage.setItem('toastMessage', '🎉 提交成功, 请等待审核!');
|
||||
window.location.reload();
|
||||
toast.success('🎉 提交成功, 请等待审核!', toastConfig);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { getWebListAPI, getWebTypeListAPI } from '@/api/web';
|
||||
import { getWebListCacheAPI } from '@/lib/web';
|
||||
import { getWebTypeListAPI } from '@/api/web';
|
||||
import { Web as WebLink } from '@/types/app/web';
|
||||
|
||||
import Friend from './index';
|
||||
@@ -11,7 +12,7 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
const linkRes = await getWebListAPI();
|
||||
const linkRes = await getWebListCacheAPI();
|
||||
const typeRes = await getWebTypeListAPI();
|
||||
const linkList = linkRes?.data ?? [];
|
||||
const typeList = typeRes?.data ?? [];
|
||||
|
||||
@@ -10,7 +10,8 @@ import { RiMessage3Line } from 'react-icons/ri';
|
||||
import RandomAvatar from '@/components/RandomAvatar';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
import Show from '@/components/Show';
|
||||
import { addRecordCommentDataAPI, getRecordCommentListAPI } from '@/api/recordComment';
|
||||
import { addRecordCommentAction } from '@/actions/record';
|
||||
import { getRecordCommentListAPI } from '@/api/recordComment';
|
||||
import { RecordComment } from '@/types/app/recordComment';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
|
||||
@@ -122,7 +123,7 @@ export default function RecordCommentPanel({ recordId, onCountChange }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const { code, message } = await addRecordCommentDataAPI({
|
||||
const { code, message } = await addRecordCommentAction({
|
||||
...data,
|
||||
recordId,
|
||||
commentId,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RiChat3Line } from 'react-icons/ri';
|
||||
import ImageList from './ImageList';
|
||||
import RecordCommentPanel from './Comment';
|
||||
import LikeButton from '@/components/LikeButton';
|
||||
import { likeRecordAPI } from '@/api/record';
|
||||
import { likeRecordAction } from '@/actions/record';
|
||||
import { getRecordCommentListAPI } from '@/api/recordComment';
|
||||
import { getRelativeTimeLabel } from '@/utils';
|
||||
import { User } from '@/types/app/user';
|
||||
@@ -73,7 +73,7 @@ export default function RecordCard({ id, content, images, likeCount, mood, locat
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-3 rounded-lg bg-[#f4f6f9] px-2 py-0.5 dark:bg-white/5">
|
||||
<LikeButton entityId={Number(id)} initialCount={likeCount ?? 0} likeAPI={likeRecordAPI} variant="inline" className="gap-1" />
|
||||
<LikeButton entityId={Number(id)} initialCount={likeCount ?? 0} likeAPI={likeRecordAction} variant="inline" className="gap-1" />
|
||||
<span className="h-3.5 w-px bg-slate-200 dark:bg-white/10" aria-hidden />
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { getRecordListAPI } from '@/api/record';
|
||||
import { getRecordListCacheAPI } from '@/lib/record';
|
||||
import { getAuthorDataAPI } from '@/api/user';
|
||||
import RecordPageClient from './components/RecordPageClient';
|
||||
|
||||
@@ -11,7 +11,7 @@ export const metadata: Metadata = {
|
||||
export default async () => {
|
||||
const [userRes, recordRes] = await Promise.all([
|
||||
getAuthorDataAPI(),
|
||||
getRecordListAPI({ pageNum: 1, pageSize: 8 }),
|
||||
getRecordListCacheAPI({ pageNum: 1, pageSize: 8 }),
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Metadata } from 'next';
|
||||
import { getCateListAPI, getCateWallListAPI } from '@/api/wall';
|
||||
import { getCateListAPI } from '@/api/wall';
|
||||
import { getCateWallListCacheAPI } from '@/lib/wall';
|
||||
import WallPageClient from '../components/WallPageClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '💌 留言墙',
|
||||
description: '💌 留言墙',
|
||||
@@ -17,7 +17,7 @@ export default async (props: Props) => {
|
||||
const sorted = [...(cateList ?? [])].sort((a, b) => a.order - b.order);
|
||||
const activeCate = cate || sorted[0]?.mark || '';
|
||||
const cateId = sorted.find((item) => item.mark === activeCate)?.id ?? sorted[0]?.id ?? 0;
|
||||
const { data: wallsData } = await getCateWallListAPI(cateId);
|
||||
const { data: wallsData } = await getCateWallListCacheAPI(cateId);
|
||||
|
||||
return (
|
||||
<WallPageClient
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
} from '@/ThriveUI';
|
||||
import { type SubmitHandler } from 'react-hook-form';
|
||||
import { Cate, Wall } from '@/types/app/wall';
|
||||
import { addWallDataAPI, getCateListAPI } from '@/api/wall';
|
||||
import { addWallAction } from '@/actions/wall';
|
||||
import { getCateListAPI } from '@/api/wall';
|
||||
import { Bounce, toast, ToastContainer, ToastOptions } from 'react-toastify';
|
||||
import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
@@ -52,12 +53,6 @@ export default () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const message = localStorage.getItem('toastMessage');
|
||||
if (message) {
|
||||
toast.success(message, toastConfig);
|
||||
localStorage.removeItem('toastMessage');
|
||||
}
|
||||
|
||||
getCateList();
|
||||
}, []);
|
||||
|
||||
@@ -69,7 +64,7 @@ export default () => {
|
||||
|
||||
if (hasHCaptcha && !captchaToken) return setCaptchaError('请完成人机验证');
|
||||
|
||||
const { code, message } = await addWallDataAPI({
|
||||
const { code, message } = await addWallAction({
|
||||
...data,
|
||||
createTime: Date.now().toString(),
|
||||
h_captcha_response: captchaToken!,
|
||||
@@ -83,9 +78,9 @@ export default () => {
|
||||
setCaptchaError('');
|
||||
setCaptchaToken(null);
|
||||
captchaRef.current?.resetCaptcha();
|
||||
methods.reset({ color: '#ffe3944d' } as Wall);
|
||||
|
||||
localStorage.setItem('toastMessage', '🎉 提交成功, 请等待审核!');
|
||||
window.location.reload();
|
||||
toast.success('🎉 提交成功, 请等待审核!', toastConfig);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,30 @@ export const CACHE_TAGS = {
|
||||
articles: 'articles',
|
||||
article: 'article',
|
||||
articlesList: 'articles-list',
|
||||
} as const;
|
||||
records: 'records',
|
||||
record: 'record',
|
||||
walls: 'walls',
|
||||
wall: 'wall',
|
||||
webs: 'webs',
|
||||
comments: 'comments',
|
||||
} as const;
|
||||
|
||||
// 动态缓存标签前缀
|
||||
const DYNAMIC_PREFIXES = [
|
||||
`${CACHE_TAGS.article}-`,
|
||||
`${CACHE_TAGS.articlesList}-`,
|
||||
`${CACHE_TAGS.record}-`,
|
||||
`${CACHE_TAGS.wall}-`,
|
||||
`${CACHE_TAGS.comments}-`,
|
||||
] as const;
|
||||
|
||||
// 判断是否是允许的缓存标签
|
||||
export function isAllowedCacheTag(tag: string) {
|
||||
// 如果标签是 CACHE_TAGS 中的值,则返回 true
|
||||
return (
|
||||
(Object.values(CACHE_TAGS) as string[]).includes(tag) ||
|
||||
// 如果标签是 DYNAMIC_PREFIXES 中的值,则返回 true
|
||||
DYNAMIC_PREFIXES.some((prefix) => tag.startsWith(prefix))
|
||||
// 如果标签是 CACHE_TAGS 中的值,或者 DYNAMIC_PREFIXES 中的值,则返回 true
|
||||
);
|
||||
}
|
||||
|
||||
15
src/lib/record.ts
Normal file
15
src/lib/record.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { cacheLife, cacheTag } from 'next/cache';
|
||||
|
||||
import { getRecordListAPI } from '@/api/record';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
|
||||
export async function getRecordListCacheAPI(params: Page = {}) {
|
||||
'use cache';
|
||||
const pageNum = params.pageNum ?? 1;
|
||||
const pageSize = params.pageSize ?? 8;
|
||||
|
||||
cacheLife('blog');
|
||||
cacheTag(CACHE_TAGS.records);
|
||||
|
||||
return getRecordListAPI(params);
|
||||
}
|
||||
13
src/lib/wall.ts
Normal file
13
src/lib/wall.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { cacheLife, cacheTag } from 'next/cache';
|
||||
|
||||
import { getCateWallListAPI } from '@/api/wall';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
|
||||
export async function getCateWallListCacheAPI(cateId: number) {
|
||||
'use cache';
|
||||
|
||||
cacheLife('blog');
|
||||
cacheTag(CACHE_TAGS.walls, `${CACHE_TAGS.wall}-${cateId}`);
|
||||
|
||||
return getCateWallListAPI(cateId);
|
||||
}
|
||||
13
src/lib/web.ts
Normal file
13
src/lib/web.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { cacheLife, cacheTag } from 'next/cache';
|
||||
|
||||
import { getWebListAPI } from '@/api/web';
|
||||
import { CACHE_TAGS } from '@/lib/cache-tags';
|
||||
|
||||
export async function getWebListCacheAPI() {
|
||||
'use cache';
|
||||
|
||||
cacheLife('blog');
|
||||
cacheTag(CACHE_TAGS.webs);
|
||||
|
||||
return getWebListAPI();
|
||||
}
|
||||
Reference in New Issue
Block a user