feat: 调整博客页以支持slug

This commit is contained in:
2025-12-27 13:48:48 +08:00
parent b9d09a16ec
commit b48ed4d903
5 changed files with 29 additions and 30 deletions

View File

@@ -31,12 +31,16 @@ export class BlogController {
return this.blogService.list(); return this.blogService.list();
} }
@Get(':id') @Get(':id/slug')
async getBlog( async getBlogBySlug(
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string, @Param('id') slug: string,
@Query('p') password?: string, @Query('p') password?: string,
) { ) {
const blog = await this.blogService.findById(id); if (slug.trim().length === 0) {
throw new BadRequestException('文章不存在');
}
const blog = await this.blogService.findBySlug(slug);
if (!blog) throw new BadRequestException('文章不存在或无权限访问'); if (!blog) throw new BadRequestException('文章不存在或无权限访问');
if (!blog.permissions.includes(BlogPermission.Public)) { if (!blog.permissions.includes(BlogPermission.Public)) {
@@ -46,7 +50,7 @@ export class BlogController {
} else { } else {
// 判断密码是否正确 // 判断密码是否正确
if ( if (
!password || typeof password !== 'string' ||
this.blogService.hashPassword(password) !== blog.password_hash this.blogService.hashPassword(password) !== blog.password_hash
) { ) {
throw new BadRequestException('文章不存在或无权限访问'); throw new BadRequestException('文章不存在或无权限访问');
@@ -57,7 +61,7 @@ export class BlogController {
const blogDataRes = await fetch(`${blog.contentUrl}`); const blogDataRes = await fetch(`${blog.contentUrl}`);
const blogContent = await blogDataRes.text(); const blogContent = await blogDataRes.text();
await this.blogService.incrementViewCount(id); this.blogService.incrementViewCount(blog.id).catch(() => null);
return { return {
id: blog.id, id: blog.id,
title: blog.title, title: blog.title,

View File

@@ -35,13 +35,14 @@ export class BlogService {
return i; return i;
} }
const { createdAt, updatedAt, deletedAt, id, title, viewCount, description } = i; const { createdAt, updatedAt, deletedAt, id, title, viewCount, description, slug } = i;
return { return {
createdAt, createdAt,
updatedAt, updatedAt,
deletedAt, deletedAt,
id, id,
title, title,
slug,
viewCount, viewCount,
description, description,
}; };
@@ -96,6 +97,12 @@ export class BlogService {
return await this.blogRepository.findOneBy({ id }); return await this.blogRepository.findOneBy({ id });
} }
async findBySlug(slug: string) {
return this.blogRepository.findOne({
where: { slug }
})
}
async incrementViewCount(id: string) { async incrementViewCount(id: string) {
await this.blogRepository.increment({ id }, 'viewCount', 1); await this.blogRepository.increment({ id }, 'viewCount', 1);
} }

View File

@@ -27,17 +27,8 @@ async function parseBlogParams({ params: paramsPromise, searchParams: searchPara
} }
} }
const hex = Array.from(base62.decode(params.id as string)).map(b => b.toString(16).padStart(2, '0')).join('');
const id = [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20, 32)
].join('-');
return { return {
id, id: params.id,
p: searchParams.p, p: searchParams.p,
} }
} }
@@ -50,7 +41,7 @@ async function getBlog(paramsResult: ReturnType<typeof parseBlogParams>) {
} }
} else { } else {
try { try {
const data = await BlogAPI.getBlog(`${id}`, p); const data = await BlogAPI.getBlogBySlug(`${id}`, p);
return { return {
data, data,
} }
@@ -83,7 +74,7 @@ export default async function Page({ params, searchParams }: PageRouteProps) {
let { errorMsg } = res; let { errorMsg } = res;
const data = errorMsg ? null const data = errorMsg ? null
: await BlogAPI.getBlog(`${id}`, p).catch(e => handleAPIError(e, ({ message }) => { errorMsg = message; return null })); : await BlogAPI.getBlogBySlug(`${id}`, p).catch(e => handleAPIError(e, ({ message }) => { errorMsg = message; return null }));
return ( return (
<div className="w-full overflow-x-hidden"> <div className="w-full overflow-x-hidden">

View File

@@ -4,7 +4,6 @@ import {
AlertTitle, AlertTitle,
} from "@/components/ui/alert"; } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { base62 } from "@/lib/utils";
import { BlogAPI } from "@/lib/api/server"; import { BlogAPI } from "@/lib/api/server";
import { handleAPIError } from "@/lib/api/common"; import { handleAPIError } from "@/lib/api/common";
@@ -18,13 +17,10 @@ const formatNumber = (num: number): string => {
return num.toString(); return num.toString();
}; };
const getBlogDetailUrl = (id: string): string => { const getBlogDetailUrl = (slug: string): string => {
const cleanId = id.replace(/-/g, ''); return `/blog/${slug}`;
const encoded = base62.encode(Buffer.from(cleanId, 'hex'));
return `/blog/${encoded}`;
}; };
export const metadata = { export const metadata = {
title: '日志 - 特恩的日志', title: '日志 - 特恩的日志',
description: '我随便发点,你也随便看看~', description: '我随便发点,你也随便看看~',
@@ -56,7 +52,7 @@ export default async function Blog() {
<h2 className="text-2xl font-medium"> <h2 className="text-2xl font-medium">
<a <a
className="hover:underline focus:outline-none focus:ring-2 focus:ring-zinc-400 rounded" className="hover:underline focus:outline-none focus:ring-2 focus:ring-zinc-400 rounded"
href={getBlogDetailUrl(blog.id)} href={getBlogDetailUrl(blog.slug)}
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{blog.title} {blog.title}

View File

@@ -3,16 +3,17 @@ import { serverFetch } from "../server";
export async function list() { export async function list() {
return serverFetch<Pick<Blog, return serverFetch<Pick<Blog,
'id' | 'title' | 'description' | 'viewCount' | 'createdAt' | 'updatedAt' | 'deletedAt' 'id' | 'title' | 'slug' | 'description' | 'viewCount' | 'createdAt' | 'updatedAt' | 'deletedAt'
>[]>('/api/blog') >[]>('/api/blog')
} }
export async function getBlog(id: string, password?: string) { export async function getBlogBySlug(slug: string, password?: string) {
return serverFetch<{ return serverFetch<{
id: string; id: string;
title: string; title: string;
description: string; description: string;
createdAt: string; createdAt: string;
content: string; content: string;
}>(`/api/blog/${id}` + (password ? `?p=${password}` : '')); }>(`/api/blog/${slug}/slug` + (password ? `?p=${password}` : ''));
} }