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

333 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`)**:
```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
```