学完前面9期内容,是时候展示真正的技术了!本期我们将启动 大型全栈项目实战 - 从零构建一个知乎风格的社区平台,融合NestJS后端、前端Next.js和云原生部署,打造支持百万用户的高性能知识社区系统。
一、项目全景图:技术选型与系统架构
1. 技术矩阵方案
graph TD
A[NestJS 后端] --> B[Next.js 前台]
A --> C[Next.js 后台]
A --> D[移动端API]
A --> E[MySQL 结构数据]
A --> F[ElasticSearch 搜索]
A --> G[Redis 缓存]
A --> H[MinIO 文件储存]
I[Docker] --> J[Kubernetes]
K[Prometheus] --> L[Grafana]2. 业务模块划分
| 模块 | 功能点 | 关键技术 |
|---|---|---|
| 用户中心 | 注册/登录/个人资料 | JWT+OAuth2.0 |
| 内容管理 | 文章/问答/富文本编辑 | Quill+SSR渲染 |
| 互动系统 | 评论/点赞/收藏/关注 | WebSocket推送 |
| 推荐引擎 | 个性化内容推荐 | Redis+协同过滤 |
| 实时搜索 | 全文检索+搜索建议 | ElasticSearch |
| 通知中心 | 消息推送/订阅提醒 | RabbitMQ延迟队列 |
| 后台管理 | 内容审核/用户分析 | Ant Design Pro |
3. 微服务架构设计
graph LR
API_Gateway --> Auth_Service
API_Gateway --> Content_Service
API_Gateway --> Notification_Service
API_Gateway --> Search_Service
Content_Service --> MySQL((MySQL))
Search_Service --> ES((ElasticSearch))
Notification_Service --> RabbitMQ二、工程化先行:从单体到微服务的渐进方案
1. 项目初始化与Monorepo架构
# 创建项目骨架
mkdir zhihu-clone && cd zhihu-clone
# NPM Workspace 配置
package.json:
{
"workspaces": [
"apps/*",
"services/*",
"libs/*"
]
}
# 目录结构
.
├── apps
│ ├── web-front # Next.js 前台
│ ├── web-admin # Next.js 后台
│ └── mobile-app # React Native 项目
├── services
│ ├── auth # 认证服务
│ ├── content # 内容服务
│ └── gateway # API网关
├── libs
│ ├── shared # 共享代码
│ └── database # 数据库连接池2. 核心共享库开发
// libs/shared/src/interfaces/user.interface.ts
export interface UserProfile {
id: string;
username: string;
bio?: string;
followers: number;
following: number;
// 多端一致的类型定义
}三、核心模块深度设计
1. 用户系统模型设计
type User {
id: ID!
username: String! @unique
email: String! @unique
password: String!
avatar: Url
biography: String
role: Role! @default(USER)
# 统计字段
followersCount: Int! @default(0)
followingCount: Int! @default(0)
# 时间戳
createdAt: DateTime!
updatedAt: DateTime!
}
enum Role {
USER
ADMIN
EDITOR
}2. 内容发布系统实现方案
富文本编辑器集成
// apps/web-front/components/Editor.tsx
import dynamic from 'next/dynamic'
const QuillEditor = dynamic(() => import('react-quill'), { ssr: false });
const modules = {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline', 'code'],
['link', 'image'],
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
],
clipboard: {
matchVisual: false,
}
};
export const Editor = ({ content, onChange }) => (
<QuillEditor
value={content}
onChange={onChange}
modules={modules}
theme="snow"
/>
);后端内容安全审查
// services/content/src/pipes/content-sanitize.pipe.ts
import * as sanitizeHtml from 'sanitize-html';
import { pipeTransform } from '@nestjs/common';
@Injectable()
export class ContentSanitizePipe implements PipeTransform {
async transform(value: string) {
const clean = sanitizeHtml(value, {
allowedTags: [...],
allowedAttributes: {
a: ['href', 'rel', 'target'],
img: ['src', 'alt', 'width']
},
allowedIframeHostnames: ['youtube.com', 'vimeo.com']
});
// 敏感词过滤
const banned = await this.wordFilter.detect(clean);
if (banned.length > 0) {
throw new BadRequestException(`含违禁词: ${banned.join(',')}`);
}
return clean;
}
}四、基础服务实现:用户与认证中心
1. JWT+RefreshToken 双令牌实现
// services/auth/src/auth.service.ts
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async login(user: User) {
const payload = {
sub: user.id,
username: user.username,
role: user.role
};
return {
access_token: await this.jwtService.signAsync(payload),
refresh_token: await this.jwtService.signAsync(payload, {
expiresIn: '7d',
}),
expires_in: 3600 // 1小时有效期
};
}
async rotateToken(refreshToken: string) {
// 1. 验证refreshToken
// 2. 从数据库加载用户
// 3. 生成新的access_token
}
}2. 社交账号登录集成
// 策略配置
export const googleOAuthConfig = () => ({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOUGLE_CLIENT_SECRET,
callbackURL: `${HOST}/auth/google/callback`,
scope: ['email', 'profile'],
});
// 回调控制器
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth() {}
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleAuthRedirect(@Req() req) {
return this.authService.loginSocial(req.user);
}五、存储系统:文件上传与云集成
1. MinIO云存储集成
// services/content/src/storage/minio.service.ts
import * as Minio from 'minio';
@Injectable()
export class MinioService {
private client: Minio.Client;
constructor() {
this.client = new Minio.Client({
endPoint: 'minio.yourdomain.com',
port: 9000,
useSSL: true,
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY
});
}
async uploadFile(bucket: string, objectName: string, file: Buffer) {
return this.client.putObject(bucket, objectName, file);
}
getFileUrl(bucket: string, objectName: string) {
return this.client.presignedGetObject(bucket, objectName, 24*60*60);
}
}2. 头像上传API实现
// controllers/users.controller.ts
@Post(':id/avatar')
@UseInterceptors(FileInterceptor('file'))
async updateAvatar(
@Param('id') userId: string,
@UploadedFile(new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }), // 5MB
new FileTypeValidator({ fileType: 'image/(png|jpg|jpeg)' }),
],
}),) file: Express.Multer.File,
) {
const bucket = 'user-avatars';
const ext = extname(file.originalname);
const objectName = `${userId}${ext}`;
await this.storageService.uploadFile(bucket, objectName, file.buffer);
// 更新用户头像URL
return this.userService.updateAvatar(userId, {
provider: 'minio',
path: `${bucket}/${objectName}`
});
}六、后台管理系统:双平台统一权限(演示代码)
1. 角色访问控制模型
// libs/shared/src/constants.rbac
export enum Actions {
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export const ROLES_PERMISSIONS = {
[UserRole.ADMIN]: {
[Resources.USER]: [Action.Manage], // 所有权限
[Resources.ARTICLE]: [Action.Manage],
},
[UserRole.EDITOR]: {
[Resources.ARTICLE]: [Action.Create, Action.Update, Action.Read],
[Resources.COMMENT]: [Action.Delete],
},
};2. NestJS后台RBAC守卫
// services/auth/src/guards/rbac.guard.ts
@Injectable()
export class RbacGuard implements CanActivate {
constructor(
private reflector: Reflector,
private permissionService: PermissionService
) {}
canActivate(context: ExecutionContext): boolean {
const [
requiredPermission,
resource
] = this.reflector.get(
PERMISSION_KEY,
context.getHandler()
);
const request = context.switchToHttp().getRequest();
const user = request.user;
return this.permissionService.can(user, resource, requiredPermission);
}
}3. Next.js管理后台实现
// apps/web-admin/pages/articles/[id]/edit.jsx
import { RbacProtected } from '@/components/Rbac';
import ArticleForm from '@/components/ArticleForm';
const EditArticlePage = ({ article }) => (
<RbacProtected permission="update" resource="article">
<ArticleForm initialData={article} />
</RbacProtected>
);
export const getServerSideProps = async (ctx) => {
// 根据权限过滤可编辑数据(服务端验证)
if (!await checkArticlePermission(ctx)) {
return { notFound: true };
}
return { props: { article } };
})七、DevOps基础设施:CI/CD与监控集成
1. GitLab 多环境流水线
# .gitlab-ci.yml
stages:
- build
- test
- deploy-dev
- deploy-prod
build-monorepo:
stage: build
script:
- npm install
- npm run build:all
artifacts:
paths:
- node_modules
- dist
run-tests:
stage: test
script:
- docker-compose -f docker-compose.test.yml up -d
- npm run test:all
needs: ["build-monorepo"]
deploy-dev:
stage: deploy-dev
script:
- kubectl apply -f k8s/dev --namespace=zhihu-dev
environment: dev
when: manual2. Grafana 监控大盘预置
// 请求耗时监控面板
{
"targets": [{
"expr": 'histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[1m]) by (le, route))',
"legendFormat": "{{route}} @ P99"
}],
"title": "接口响应延时分布",
"type": "graph",
"yaxes": [{ "format": "s" }]
}八、避坑指南:大型项目常见陷阱
❌ 分布式事务问题
场景:用户发布文章后增加经验值,需要保证原子性
解决方案
1. Saga事务模式:
- 步骤1: 创建文章记录 (状态:Pending)
- 步骤2: 用户加经验值
- 成功: 更新文章状态为Published
- 失败: 补偿机制回滚经验值
2. 业务主表设计:
article表冗余经验值变更,通过异步任务解耦❌ 聚合查询性能
// 高性能用户信息聚合查询
async getUserProfile(id: string) {
const [user, stats, articles] = await Promise.all([
this.userRepo.findOneBy(id), // 基础信息
this.statsRepo.findUserStats(id), // 统计信息
this.articlesRepo.findByUser(id, { limit: 5 }) // 最新文章
]);
return { ...user, ...stats, articles };
}❌ 全局配置管理混乱
// 统一配置服务
// libs/config/src/app.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 3306,
}
}));
// 全项目统一访问
constructor(@Inject('app') private config) {}下期预告:《综合实战(下):高并发互动与搜索系统》
➠ WebSocket实时问答应用
➠ ElasticSearch全文检索优化
➠ Redis实现个性化推荐
➠ RabbitMQ异步通知中心
➠ 百万级数据压力测试
➠ 混合云部署策略
实战挑战:
✍️ 在你的项目中实现双令牌认证流程,并在评论中分享登录接口截图!
通过本期实战,你已完成平台核心架构:
✅ Monorepo 工程化组织方案
✅ 用户中心完整实现
✅ 内容管理与富文本集成
✅ RBAC 权限控制系统
✅ 文件存储服务封装
✅ DevOps 基础流水线
项目价值:这个项目框架可快速转换为问答社区、博客平台、知识库系统等多种产品形态,具备直接商用的基础能力!
相关推荐
- NestJS 从入门到精通 11:综合实战 - 构建知乎风格的全栈社区平台(下) 2025-06-24
- NestJS 从入门到精通 09:性能调优大师 - 百万并发下的极致优化策略 2025-06-24
- NestJS 从入门到精通 08:安全架构实战 - 构建金融级应用的8重防护门 2025-06-24
- NestJS 从入门到精通 07:配置管理与部署实战 - 多环境与容器化方案 2025-06-24
- NestJS 从入门到精通 06:GraphQL 现代 API 开发 - 解决 RESTful 之痛 2025-06-24
评论0
暂时没有评论