Files
CICD/ARCHITECTURE.md
SpecialX e7c902e8e1
Some checks failed
CI / build-and-test (push) Failing after 1m31s
CI / deploy (push) Has been skipped
Module Update
2025-12-30 14:42:30 +08:00

12 KiB
Raw Permalink Blame History

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: 相比 ClerkAuth.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):

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) 也会自动去重。
// 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 transitionanimation 实现。
  • Hardware Acceleration: 确保动画属性触发 GPU 加速 (transform, opacity)。
  • Micro-interactions: 关注 :hover, :active, :focus-visible 状态。

高性能通用组件示例 (Interactive Card)

这是一个符合规范的卡片组件,使用了 Tailwind 的 grouptransform 属性实现丝滑的微交互,且没有 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