12 KiB
12 KiB
Next_Edu Architecture RFC
Status: PROPOSED
Date: 2025-12-22
Author: Principal Software Architect
Version: 1.0.0
1. 核心原则 (Core Principles)
本架构设计遵循以下核心原则,旨在构建一个高性能、可扩展、易维护的企业级在线教育平台。
- Vertical Slice Architecture (垂直切片): 拒绝传统的按技术分层(Layered Architecture),采用按业务功能分层。代码应根据“它属于哪个功能”而不是“它是什么文件”来组织。
- Type Safety First (类型安全优先): 全链路 TypeScript (Strict Mode)。从数据库 Schema 到 API 再到 UI 组件,必须保持类型一致性。
- Server-First (服务端优先): 充分利用 Next.js 15 App Router 的 RSC (React Server Components) 能力,减少客户端 Bundle 体积。
- Performance by Default (默认高性能): 严禁引入重型动画库,动效优先使用 CSS Native 实现。Web Vitals 指标作为 CI 阻断标准。
- 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后缀,以明确区分“渲染层”与“逻辑层”。
- Correction: 鉴于 Next.js App Router 的特性,
- 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)
- Complex Interactions: Framer Motion (仅限按需加载
身份验证与授权
- 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 可能导致连接数耗尽。我们采取以下策略:
- 开发环境: 使用 Global Singleton 模式防止 Hot Reload 导致连接泄露。
- 生产环境:
- 推荐使用支持 HTTP 连接或内置连接池的 Serverless MySQL 方案 (如 PlanetScale)。
- 若使用标准 MySQL,必须配置连接池 (
connectionLimit) 并合理设置空闲超时。
代码示例 (src/shared/db/index.ts):
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。
- 流程:
- 修改 Schema (
schema.ts). - 运行
pnpm drizzle-kit generate生成 SQL 文件。 - Review SQL 文件。
- 在 CI/CD 部署前置步骤或手动运行
pnpm drizzle-kit migrate。
- 修改 Schema (
Server Components 中的数据查询
- Colocation: 查询逻辑应尽量靠近使用它的组件,或者封装在
data-access.ts中。 - Request Memoization: 即使在一个请求中多次调用相同的查询函数,Next.js 的
cache(或 Reactcache) 也会自动去重。
// 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 运行时开销。
// 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
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