333 lines
12 KiB
Markdown
333 lines
12 KiB
Markdown
# Next_Edu Architecture RFC
|
||
|
||
**Status**: PROPOSED
|
||
**Date**: 2025-12-22
|
||
**Author**: Principal Software Architect
|
||
**Version**: 1.0.0
|
||
|
||
---
|
||
|
||
## 1. 核心原则 (Core Principles)
|
||
|
||
本架构设计遵循以下核心原则,旨在构建一个高性能、可扩展、易维护的企业级在线教育平台。
|
||
|
||
1. **Vertical Slice Architecture (垂直切片)**: 拒绝传统的按技术分层(Layered Architecture),采用按业务功能分层。代码应根据“它属于哪个功能”而不是“它是什么文件”来组织。
|
||
2. **Type Safety First (类型安全优先)**: 全链路 TypeScript (Strict Mode)。从数据库 Schema 到 API 再到 UI 组件,必须保持类型一致性。
|
||
3. **Server-First (服务端优先)**: 充分利用 Next.js 15 App Router 的 RSC (React Server Components) 能力,减少客户端 Bundle 体积。
|
||
4. **Performance by Default (默认高性能)**: 严禁引入重型动画库,动效优先使用 CSS Native 实现。Web Vitals 指标作为 CI 阻断标准。
|
||
5. **Strict Engineering (严格工程化)**: CI/CD 流程标准化,代码风格统一,自动化测试覆盖。
|
||
|
||
---
|
||
|
||
## 2. 技术栈全景图 (Technology Panorama)
|
||
|
||
### 核心框架
|
||
* **Framework**: Next.js 15 (App Router)
|
||
* **Language**: TypeScript 5.x (Strict Mode enabled)
|
||
* *Correction*: 鉴于 Next.js App Router 的特性,`.tsx` 仅用于包含 JSX 的组件文件。所有的业务逻辑、Hooks、API 路由、Lib 工具函数必须使用 `.ts` 后缀,以明确区分“渲染层”与“逻辑层”。
|
||
* **Runtime**: Node.js 20.x (LTS)
|
||
|
||
### 数据层
|
||
* **Database**: MySQL 8.0+
|
||
* **ORM**: Drizzle ORM (轻量、无运行时开销、类型安全)
|
||
* **Driver**: `mysql2` (配合连接池) 或 `serverless-mysql` (针对特定 Serverless 环境)
|
||
* **Validation**: Zod (Schema 定义与运行时验证)
|
||
|
||
### UI/UX 层
|
||
* **Styling**: Tailwind CSS v3.4+
|
||
* **Components**: Shadcn/UI (基于 Radix UI 的 Headless 组件拷贝)
|
||
* **Icons**: Lucide React
|
||
* **Animations**: CSS Transitions / Tailwind `animate-*` / `tailwindcss-animate`
|
||
* *Complex Interactions*: Framer Motion (仅限按需加载 `LazyMotion`)
|
||
|
||
### 身份验证与授权
|
||
* **Auth**: Auth.js v5 (NextAuth)
|
||
* *Decision Driver*: 相比 Clerk,Auth.js 提供了完全的**数据所有权**和**无 Vendor Lock-in**。
|
||
* *Enterprise Needs*: 允许自定义 Session 结构(如注入 `Role` 字段)并直接对接现有 MySQL 数据库,满足复杂的企业级权限管理需求。
|
||
|
||
### 状态管理
|
||
* **Server State**: TanStack Query v5 (仅用于复杂客户端轮询/无限加载)
|
||
* **URL State (Primary)**: Nuqs (Type-safe search params state manager)
|
||
* *Principle*: 绝大多数状态(筛选、分页、Tab)应存在 URL 中,以支持分享和书签。
|
||
* **Global Client State (Secondary)**: Zustand
|
||
* *Usage*: 仅限极少数全局交互状态(如播放器悬浮窗、全局 Modal)。
|
||
* *Anti-pattern*: **严禁使用 Redux**。避免不必要的样板代码和 Bundle 体积。
|
||
|
||
### 基础设施 & DevOps
|
||
* **CI/CD**: GitHub Actions (Strictly v3)
|
||
* **Linting**: ESLint (Next.js config), Prettier
|
||
* **Package Manager**: pnpm (推荐) 或 npm
|
||
|
||
---
|
||
|
||
## 3. 项目目录结构规范 (Project Structure)
|
||
|
||
采用 **Feature-based / Vertical Slice** 架构。所有业务逻辑应封装在 `src/modules` 中。
|
||
|
||
文档存放位置:
|
||
* 架构设计文档: `docs/architecture/`
|
||
* API 规范文档: `docs/api/`
|
||
|
||
### 目录树 (Directory Tree)
|
||
|
||
```
|
||
Next_Edu/
|
||
├── .github/
|
||
│ └── workflows/
|
||
│ └── ci.yml # GitHub Actions (v3 strict)
|
||
├── docs/
|
||
│ └── architecture/ # 架构决策记录 (ADR)
|
||
├── drizzle/ # 数据库迁移文件 (Generated)
|
||
├── public/ # 静态资源
|
||
├── src/
|
||
│ ├── app/ # [路由层] 极薄,仅负责路由分发和布局
|
||
│ │ ├── (auth)/ # 路由组
|
||
│ │ ├── (dashboard)/
|
||
│ │ ├── api/ # Webhooks / External APIs
|
||
│ │ ├── layout.tsx
|
||
│ │ └── page.tsx
|
||
│ │
|
||
│ ├── modules/ # [核心业务层] 垂直切片
|
||
│ │ ├── courses/ # 课程模块
|
||
│ │ │ ├── components/ # 模块私有组件 (CourseCard, Player)
|
||
│ │ │ ├── actions.ts # Server Actions (业务逻辑入口)
|
||
│ │ │ ├── service.ts # 领域服务 (可选,复杂逻辑拆分)
|
||
│ │ │ ├── data-access.ts # 数据库查询 (DTOs)
|
||
│ │ │ └── types.ts # 模块私有类型
|
||
│ │ │
|
||
│ │ ├── users/ # 用户模块
|
||
│ │ ├── payments/ # 支付模块
|
||
│ │ └── community/ # 社区模块
|
||
│ │
|
||
│ ├── shared/ # [共享层] 仅存放真正通用的代码
|
||
│ │ ├── components/ # 通用 UI (Button, Dialog - Shadcn)
|
||
│ │ ├── lib/ # 通用工具 (utils, date formatting)
|
||
│ │ ├── db/ # Drizzle Client & Schema
|
||
│ │ │ ├── index.ts # DB 连接实例
|
||
│ │ │ └── schema.ts # 全局 Schema 定义 (或按模块拆分导出)
|
||
│ │ └── hooks/ # 通用 Hooks
|
||
│ │
|
||
│ ├── env.mjs # 环境变量类型检查
|
||
│ └── middleware.ts # 边缘中间件 (Auth check)
|
||
├── drizzle.config.ts # Drizzle 配置文件
|
||
├── next.config.mjs
|
||
└── package.json
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 数据库层设计 (Database Strategy)
|
||
|
||
### 连接配置 (Connection Pooling)
|
||
在 Next.js 的 Serverless/Edge 环境中,直接连接 MySQL 可能导致连接数耗尽。我们采取以下策略:
|
||
|
||
1. **开发环境**: 使用 Global Singleton 模式防止 Hot Reload 导致连接泄露。
|
||
2. **生产环境**:
|
||
* 推荐使用支持 HTTP 连接或内置连接池的 Serverless MySQL 方案 (如 PlanetScale)。
|
||
* 若使用标准 MySQL,必须配置连接池 (`connectionLimit`) 并合理设置空闲超时。
|
||
|
||
**代码示例 (`src/shared/db/index.ts`)**:
|
||
|
||
```typescript
|
||
import { drizzle } from "drizzle-orm/mysql2";
|
||
import mysql from "mysql2/promise";
|
||
import * as schema from "./schema";
|
||
|
||
// Global cache to prevent connection exhaustion in development
|
||
const globalForDb = globalThis as unknown as {
|
||
conn: mysql.Pool | undefined;
|
||
};
|
||
|
||
const poolConnection = globalForDb.conn ?? mysql.createPool({
|
||
uri: process.env.DATABASE_URL,
|
||
waitForConnections: true,
|
||
connectionLimit: 10, // 根据数据库规格调整
|
||
queueLimit: 0
|
||
});
|
||
|
||
if (process.env.NODE_ENV !== "production") globalForDb.conn = poolConnection;
|
||
|
||
export const db = drizzle(poolConnection, { schema, mode: "default" });
|
||
```
|
||
|
||
### Migration 策略
|
||
* 使用 `drizzle-kit` 进行迁移管理。
|
||
* 严禁在生产环境运行时自动执行 Migration。
|
||
* **流程**:
|
||
1. 修改 Schema (`schema.ts`).
|
||
2. 运行 `pnpm drizzle-kit generate` 生成 SQL 文件。
|
||
3. Review SQL 文件。
|
||
4. 在 CI/CD 部署前置步骤或手动运行 `pnpm drizzle-kit migrate`。
|
||
|
||
### Server Components 中的数据查询
|
||
* **Colocation**: 查询逻辑应尽量靠近使用它的组件,或者封装在 `data-access.ts` 中。
|
||
* **Request Memoization**: 即使在一个请求中多次调用相同的查询函数,Next.js 的 `cache` (或 React `cache`) 也会自动去重。
|
||
|
||
```typescript
|
||
// src/modules/courses/data-access.ts
|
||
import { cache } from 'react';
|
||
import { db } from '@/shared/db';
|
||
import { eq } from 'drizzle-orm';
|
||
import { courses } from '@/shared/db/schema';
|
||
|
||
// 使用 React cache 确保单次请求内的去重
|
||
export const getCourseById = cache(async (id: string) => {
|
||
return await db.query.courses.findFirst({
|
||
where: eq(courses.id, id),
|
||
});
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 5. UI/UX 动效规范 (Animation Guidelines)
|
||
|
||
### 核心策略
|
||
* **CSS Native First**: 90% 的交互通过 CSS `transition` 和 `animation` 实现。
|
||
* **Hardware Acceleration**: 确保动画属性触发 GPU 加速 (`transform`, `opacity`)。
|
||
* **Micro-interactions**: 关注 `:hover`, `:active`, `:focus-visible` 状态。
|
||
|
||
### 高性能通用组件示例 (Interactive Card)
|
||
|
||
这是一个符合规范的卡片组件,使用了 Tailwind 的 `group` 和 `transform` 属性实现丝滑的微交互,且没有 JS 运行时开销。
|
||
|
||
```tsx
|
||
// src/shared/components/ui/interactive-card.tsx
|
||
import { cn } from "@/shared/lib/utils";
|
||
|
||
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||
children: React.ReactNode;
|
||
}
|
||
|
||
export function InteractiveCard({ className, children, ...props }: CardProps) {
|
||
return (
|
||
<div
|
||
className={cn(
|
||
"group relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-sm",
|
||
// 核心动效:
|
||
// 1. duration-300 ease-out: 丝滑的时间函数
|
||
// 2. hover:shadow-md: 悬浮提升感
|
||
// 3. hover:-translate-y-1: 物理反馈
|
||
"transition-all duration-300 ease-out hover:-translate-y-1 hover:shadow-md",
|
||
// 消除 Safari 上的闪烁
|
||
"transform-gpu backface-hidden",
|
||
className
|
||
)}
|
||
{...props}
|
||
>
|
||
{/* 光泽效果 (Shimmer Effect) - 仅 CSS */}
|
||
<div
|
||
className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/5 to-transparent transition-transform duration-700 group-hover:translate-x-full"
|
||
aria-hidden="true"
|
||
/>
|
||
|
||
<div className="relative p-6">
|
||
{children}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. CI/CD 配置文件模板 (GitHub Actions)
|
||
|
||
**警告**: 必须严格遵守 `v3` 版本限制。严禁使用 `v4`。
|
||
|
||
文件路径: `.github/workflows/ci.yml`
|
||
|
||
```yaml
|
||
name: CI/CD Pipeline
|
||
|
||
on:
|
||
push:
|
||
branches: [ "main", "develop" ]
|
||
pull_request:
|
||
branches: [ "main", "develop" ]
|
||
|
||
env:
|
||
NODE_VERSION: '20.x'
|
||
|
||
jobs:
|
||
quality-check:
|
||
name: Quality & Type Check
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
# 强制使用 v3
|
||
- name: Checkout Code
|
||
uses: actions/checkout@v3
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm' # 或 'pnpm'
|
||
|
||
- name: Install Dependencies
|
||
run: npm ci
|
||
|
||
- name: Linting (ESLint)
|
||
run: npm run lint
|
||
|
||
- name: Type Checking (TSC)
|
||
# 确保没有 TS 错误
|
||
run: npx tsc --noEmit
|
||
|
||
test:
|
||
name: Unit Tests
|
||
needs: quality-check
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout Code
|
||
uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Install Dependencies
|
||
run: npm ci
|
||
|
||
- name: Run Tests
|
||
run: npm run test
|
||
|
||
build-check:
|
||
name: Production Build Check
|
||
needs: test
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout Code
|
||
uses: actions/checkout@v3
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'npm'
|
||
|
||
- name: Install Dependencies
|
||
run: npm ci
|
||
|
||
- name: Cache Next.js build
|
||
uses: actions/cache@v3
|
||
with:
|
||
path: |
|
||
~/.npm
|
||
${{ github.workspace }}/.next/cache
|
||
# Generate a new cache whenever packages or source files change.
|
||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||
|
||
- name: Build Application
|
||
run: npm run build
|
||
env:
|
||
# 构建时跳过 ESLint/TS 检查 (因为已经在 quality-check job 做过了,加速构建)
|
||
NEXT_TELEMETRY_DISABLED: 1
|
||
```
|