commit 2ba425016586b5ccd4befd369b6f7391c400bb5d Author: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Tue Jul 7 23:39:37 2026 +0800 feat(p1): complete P1 foundation stage - monorepo: pnpm workspace + go.work + pyproject.toml + commitlint/husky - infra: docker-compose (minimal + full profiles) + init-sql + prometheus - arch-scan: multi-language scanner skeleton (TS/Go/Python/Proto) - shared-proto: buf v2 + classes.proto (ClassService CRUD contract) - api-gateway: Go/Gin + JWT HS256 auth + reverse proxy + request ID - classes: NestJS golden template (error system + observability + middleware + CRUD + tests) - teacher-portal: Next.js + paper-feel UI design system - CI/CD: 4 workflows (go/ts/py/proto) - docs: migration guide + project_rules + coding-standards + git-workflow + ui-design-system + 004 + 9 module READMEs + known-issues + spec/plan migration + roadmap diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..598303b --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,22 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + ['feat', 'fix', 'chore', 'docs', 'style', 'refactor', 'test', 'perf', 'ci', 'build', 'revert'], + ], + 'scope-enum': [ + 2, + 'always', + [ + 'api-gateway', 'push-gateway', + 'iam', 'core-edu', 'classes', 'content', 'data-ana', 'msg', 'ai', + 'teacher-bff', 'student-bff', 'parent-bff', + 'teacher-portal', 'student-portal', 'parent-portal', 'admin-portal', + 'shared-proto', 'shared-ts', 'shared-go', 'shared-py', 'shared-tokens', + 'arch-scan', 'infra', 'docs', 'deps', 'release', + ], + ], + }, +}; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4c51883 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{go,mod,sum}] +indent_style = tab + +[*.{py,toml}] +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5a33af4 --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +# MySQL +MYSQL_ROOT_PASSWORD=changeme +MYSQL_DATABASE=next_edu_cloud +MYSQL_USER=edu +MYSQL_PASSWORD=changeme + +# Redis +REDIS_PASSWORD= + +# JWT (P1 用 HS256 测试密钥,P2 改 RS256) +JWT_SECRET=p1-dev-secret-change-in-production +JWT_ISSUER=next-edu-cloud +JWT_AUDIENCE=next-edu-cloud + +# 服务端口 +API_GATEWAY_PORT=8080 +CLASSES_SERVICE_PORT=3001 +TEACHER_PORTAL_PORT=3000 + +# 数据库连接 +DATABASE_URL=mysql://edu:changeme@localhost:3306/next_edu_cloud +REDIS_URL=redis://localhost:6379 + +# 可观测性 +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 +LOG_LEVEL=info + +# Neo4j(P4 启用) +NEO4J_PASSWORD=changeme + +# Kafka(P3 启用) +KAFKA_BROKERS=localhost:9092 diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml new file mode 100644 index 0000000..253c6a3 --- /dev/null +++ b/.github/workflows/ci-go.yml @@ -0,0 +1,38 @@ +name: CI Go + +on: + push: + branches: [main] + paths: + - 'services/api-gateway/**' + - 'services/push-gateway/**' + - 'packages/shared-go/**' + - 'go.work' + pull_request: + branches: [main] + paths: + - 'services/api-gateway/**' + - 'services/push-gateway/**' + - 'packages/shared-go/**' + - 'go.work' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + working-directory: services/api-gateway + - name: Build + working-directory: services/api-gateway + run: | + go mod download + go build ./... + - name: Test + working-directory: services/api-gateway + run: go test ./... -v -coverprofile=coverage.out diff --git a/.github/workflows/ci-proto.yml b/.github/workflows/ci-proto.yml new file mode 100644 index 0000000..7fcbf57 --- /dev/null +++ b/.github/workflows/ci-proto.yml @@ -0,0 +1,25 @@ +name: CI Proto + +on: + push: + branches: [main] + paths: + - 'packages/shared-proto/**' + pull_request: + branches: [main] + paths: + - 'packages/shared-proto/**' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bufbuild/buf-setup-action@v1 + - name: buf lint + working-directory: packages/shared-proto + run: buf lint + - name: buf breaking + if: github.event_name == 'pull_request' + working-directory: packages/shared-proto + run: buf breaking --against https://github.com/${{ github.repository }}.git#branch=main,subdir=packages/shared-proto diff --git a/.github/workflows/ci-py.yml b/.github/workflows/ci-py.yml new file mode 100644 index 0000000..5d93f59 --- /dev/null +++ b/.github/workflows/ci-py.yml @@ -0,0 +1,33 @@ +name: CI Python + +on: + push: + branches: [main] + paths: + - 'services/data-ana/**' + - 'services/ai/**' + - 'packages/shared-py/**' + - 'pyproject.toml' + pull_request: + branches: [main] + paths: + - 'services/data-ana/**' + - 'services/ai/**' + - 'packages/shared-py/**' + - 'pyproject.toml' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: astral-sh/setup-uv@v3 + - name: Install + run: uv sync + - name: Lint (ruff) + run: uv run ruff check . + - name: Test + run: uv run pytest diff --git a/.github/workflows/ci-ts.yml b/.github/workflows/ci-ts.yml new file mode 100644 index 0000000..ad0cf9a --- /dev/null +++ b/.github/workflows/ci-ts.yml @@ -0,0 +1,43 @@ +name: CI TypeScript + +on: + push: + branches: [main] + paths: + - 'services/classes/**' + - 'apps/**' + - 'packages/**' + - 'scripts/**' + - 'package.json' + - 'pnpm-workspace.yaml' + - 'tsconfig.base.json' + pull_request: + branches: [main] + paths: + - 'services/classes/**' + - 'apps/**' + - 'packages/**' + - 'scripts/**' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + - name: Install + run: pnpm install --frozen-lockfile + - name: Lint + run: pnpm -r run lint + - name: Typecheck + run: pnpm -r run typecheck + - name: Test + run: pnpm -r run test + - name: Build + run: pnpm -r run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..692a98e --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +build/ +.next/ +out/ +*.tsbuildinfo +target/ +bin/ +obj/ + +# Go +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out + +# Python +__pycache__/ +*.py[cod] +*$py.class +.venv/ +venv/ +*.egg-info/ + +# Environment +.env +.env.local +.env.*.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Database +*.db +*.sqlite +arch.db + +# Test coverage +coverage/ +.nyc_output/ + +# Docker +docker-compose.override.yml + +# Temp +tmp/ +temp/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..a212843 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..ca2c025 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx --no-install lint-staged diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..82bb164 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- P1 地基阶段:仓库骨架、Docker 基础设施、API Gateway、classes 黄金模板服务 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..952fe9a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing + +## 开发环境要求 + +- Node.js 20+ +- pnpm 9+ +- Go 1.22+ +- Python 3.12+ +- Docker Desktop +- buf 1.40+ (`go install github.com/bufbuild/buf/cmd/buf@latest`) + +## 开发流程 + +1. 从 main 创建 feature 分支:`git checkout -b feature/xxx` +2. 开发并确保 CI 通过:`pnpm lint && pnpm test && pnpm build` +3. 提交 PR 到 main +4. CI 全绿后合并 + +## 提交规范 + +使用 Conventional Commits: + +``` +(): + +types: feat, fix, chore, docs, style, refactor, test, perf, ci +scope: 服务名或包名(如 api-gateway, classes, shared-proto) +``` + +示例: +- `feat(api-gateway): add JWT auth middleware` +- `fix(classes): resolve N+1 query in list classes` +- `docs(004): update service dependency graph` + +## 文档同步 + +修改代码后必须: +1. 运行 `pnpm run arch:scan` 更新 arch.db +2. 若架构意图变化,更新 `docs/architecture/004_architecture_impact_map.md` +3. 若服务接口变化,更新对应 `docs/modules//README.md` +4. 若发现新经验,更新 `docs/troubleshooting/known-issues.md` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c86f046 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Edu Cloud + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 0000000..605551c --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,492 @@ +# 迁移指南:CICD → Edu + +> 版本:1.0 +> 日期:2026-07-07 +> 状态:基线发布 +> 维护者:架构组 +> 关联文档: +> - [项目规则](./project_rules.md) +> - [编码规范](./docs/standards/coding-standards.md) +> - [Git 工作流](./docs/standards/git-workflow.md) + +--- + +## 目录 + +1. [迁移背景](#一迁移背景) +2. [原始项目说明](#二原始项目说明cicd) +3. [新项目说明](#三新项目说明edu) +4. [迁移策略矩阵](#四迁移策略矩阵) +5. [文档体系映射表](#五文档体系映射表) +6. [六阶段路线图概览](#六六阶段路线图概览) +7. [原始项目可复用资产清单](#七原始项目可复用资产清单) +8. [迁移验收标准](#八迁移验收标准) + +--- + +## 一、迁移背景 + +### 1.1 迁移动机 + +原始项目 CICD 是基于 Next.js 16 的单应用架构,承载 K12 智慧教务系统 35 个业务模块。随着业务规模扩张与团队增长,单应用架构在以下方面暴露瓶颈: + +| 维度 | 单应用瓶颈 | 微服务目标 | +|------|-----------|-----------| +| 团队协作 | 35 模块挤在同一仓库,合并冲突频繁 | 按领域拆分,6 领域团队独立迭代 | +| 部署节奏 | 全量构建发布,单模块变更牵动全站 | 服务粒度独立部署,故障爆炸半径缩小 | +| 技术选型 | TypeScript 单语言,AI/分析场景受限 | TS(业务)+ Go(网关)+ Python(AI/分析)各取所长 | +| 数据规模 | 单 MySQL,跨模块联表与读放大 | 读写分离 + CQRS,ClickHouse 承载分析负载 | +| 可演进性 | 模块间隐式耦合,重构成本高 | DDD 限界上下文显式契约,演化可控 | + +### 1.2 迁移原则 + +1. **契约先行**:服务间通信先定 protobuf 契约,再写实现,禁止"先实现再补契约" +2. **资产复用**:设计令牌、权限模型、缓存策略、UI 组件模式等优质资产尽量平移,不重复造轮子 +3. ** strangler fig**:不一次性切流,按阶段灰度迁移,旧系统与新系统在过渡期并存 +4. **数据自治**:每个微服务独占自身数据库,跨服务查询走 BFF 聚合或读模型 +5. **可观测优先**:迁移同步建立 tracing/metrics/logging 三件套,禁止"先上线后补监控" + +--- + +## 二、原始项目说明(CICD) + +### 2.1 基本概况 + +| 属性 | 值 | +|------|-----| +| 仓库路径 | `e:\Desktop\CICD` | +| 技术栈 | Next.js 16 + React 19 + Tailwind v4 + Drizzle ORM | +| 语言 | TypeScript(全栈) | +| 架构 | 单应用 + 严格模块化(app → modules → shared 三层) | +| 业务模块数 | 35 个 | +| 数据库 | MySQL(单库) | +| 认证 | NextAuth.js | +| 部署 | Gitea Actions + Docker standalone | + +### 2.2 模块清单(按领域归类) + +| 领域 | 模块 | +|------|------| +| 身份与权限 | auth、users、onboarding、settings、permissions | +| 教学组织 | classes、teachers、students、parents、subjects | +| 教学核心 | courses、lessons、schedule、attendance、leave-requests | +| 考试评价 | exams、questions、grading、scores、analytics | +| 作业内容 | homework、textbooks、resources | +| 沟通通知 | messaging、notifications、announcements | +| 智能辅助 | ai(备课/出题/分析)、search | +| 系统支撑 | audit-logs、reports、dashboard、layout | + +### 2.3 已沉淀的工程资产 + +- **arch.db 架构元数据库**:通过 `npm run arch:scan` 扫描得到模块、函数、调用关系、依赖关系、技术标签的结构化数据 +- **设计令牌体系**:primitive / semantic-light / semantic-dark / 模块命名空间 / Tailwind 暴露 五层分层 +- **权限模型**:`requirePermission()` 服务端校验 + `usePermission().hasPermission()` 客户端校验,权限点常量集中维护 +- **缓存策略**:`cacheFn`(React `cache()` + 自定义缓存层),权限数据不跨请求缓存 +- **ActionState 模式**:Server Action 统一返回 `{ success, message, errors, data }` 结构 +- **5 层状态模型**:URL(nuqs)→ Server(TanStack Query)→ Client Business(Zustand)→ Global UI(Zustand)→ Form(react-hook-form) +- **A11y 工具集**:`useA11yId`、`mergeA11yProps`、`describeInput`、`loadingAria`、aria-live Hook、focus-trap 组件 +- **CI/CD 流水线**:CI + 安全扫描 + 灾备演练三套 Gitea Actions 工作流 +- **审计日志三件套**:`login-logger`、`change-logger`、`audit-logger` + +--- + +## 三、新项目说明(Edu) + +### 3.1 基本概况 + +| 属性 | 值 | +|------|-----| +| 仓库路径 | `e:\Desktop\Edu` | +| 远程仓库 | https://git.eazygame.cn/xiner/Edu.git | +| 架构范式 | DDD + EDA + CQRS 微服务 | +| 语言 | TypeScript(NestJS 10)+ Go(Gin 网关)+ Python(FastAPI 分析/AI) | +| 前端 | React + Next.js + Module Federation(4 微前端) | +| monorepo 策略 | pnpm workspace + go.work + pyproject.toml (uv) | +| 事件总线 | Kafka + Debezium CDC | +| 契约 | protobuf + buf | +| 存储 | MySQL / Redis / ClickHouse / Neo4j / Elasticsearch | + +### 3.2 整体架构 + +```mermaid +flowchart TB + subgraph Client[客户端] + WEB[Web 浏览器] + APP[移动端] + end + + subgraph Gateway[网关层 Go Gin] + GW[API Gateway
路由/鉴权/限流/熔断] + end + + subgraph BFF[聚合层 BFF] + BFF1[Admin BFF] + BFF2[Teacher BFF] + BFF3[Student/Parent BFF] + end + + subgraph Services[业务微服务 NestJS] + S1[identity 身份] + S2[org 教学组织] + S3[teaching 教学核心] + S4[content 内容分析] + S5[comm 沟通] + S6[insight 智能洞察] + end + + subgraph Infra[基础设施服务] + I1[auth 认证授权] + I2[notification 通知] + end + + subgraph EventBus[事件总线] + K[Kafka] + DBZ[Debezium CDC] + end + + subgraph Storage[存储] + MY[(MySQL)] + RD[(Redis)] + CH[(ClickHouse)] + NEO[(Neo4j)] + ES[(Elasticsearch)] + end + + Client --> GW + GW --> BFF + BFF --> Services + Services --> Infra + Services <--> K + DBZ --> K + Services --> Storage + Infra --> Storage +``` + +### 3.3 微前端与领域服务映射 + +| 微前端 | 路由前缀 | 对接 BFF | 主要消费的服务 | +|--------|---------|---------|---------------| +| Admin Shell | `/admin` | Admin BFF | identity、org、insight | +| Teacher Shell | `/teacher` | Teacher BFF | teaching、content、comm | +| Student Shell | `/student` | Student/Parent BFF | teaching、content | +| Parent Shell | `/parent` | Student/Parent BFF | teaching、comm | + +| 微服务 | 原始模块映射 | 主存储 | 对外契约 | +|--------|------------|--------|---------| +| identity | auth、users、onboarding、settings、permissions | MySQL + Redis | identity.proto | +| org | classes、teachers、students、parents、subjects | MySQL | org.proto | +| teaching | courses、lessons、schedule、attendance、leave-requests、exams、homework | MySQL | teaching.proto | +| content | textbooks、resources、questions、grading | MySQL + Elasticsearch | content.proto | +| comm | messaging、notifications、announcements | MySQL + Redis | comm.proto | +| insight | scores、analytics、ai、search、reports、dashboard | ClickHouse + Neo4j | insight.proto | +| auth(基础设施) | NextAuth 逻辑下沉 | MySQL + Redis | auth.proto | +| notification(基础设施) | notifications channel 抽离 | MySQL + Redis | notification.proto | + +--- + +## 四、迁移策略矩阵 + +按"直接迁移 / 调整迁移 / 重写 / 新建"四档分类。 + +### 4.1 规范类资产 + +| 资产 | 策略 | 说明 | +|------|------|------| +| 项目规则(project_rules) | 调整 | 架构图优先保留,分层规则从三层改为微服务分层,新增 DDD/契约/事件驱动规则 | +| 编码规范(coding-standards) | 调整 | TS 部分保留并补充 NestJS 装饰器/DI 规则,新增 Go、Python、protobuf 章节 | +| Git 工作流 | 调整 | trunk-based 替代分支策略,scope 改为服务/包名,新增多语言 monorepo 提交规则 | +| 提交规范(Conventional Commits) | 直接迁移 | 类型与格式完全沿用 | +| 设计令牌规范 | 调整 | 分层模型保留,分布位置从 `src/app/styles/tokens/` 改为微前端共享包 | +| 安全规范 | 直接迁移 | Cookie 策略、env 校验、XSS 防护、权限校验规则全部沿用 | +| 问题记录规则 | 直接迁移 | known-issues.md 索引式速查手册模式沿用 | +| A11y 规范 | 直接迁移 | WCAG 2.2 AA 目标与工具集沿用 | + +### 4.2 代码类资产 + +| 资产 | 策略 | 说明 | +|------|------|------| +| Zod schema 定义 | 直接迁移 | 各模块 schema.ts 平移至对应微服务,复用验证规则 | +| 权限点常量(Permissions) | 直接迁移 | 集中迁入 identity 服务共享包 | +| Drizzle schema(表结构) | 调整 | 按领域拆分到各微服务独占库,关系型字段保持不变 | +| ActionState 类型 | 直接迁移 | 升级为 protobuf message,结构不变 | +| Server Actions | 重写 | 改写为 NestJS Controller + Service + Application Service 三层 | +| data-access 层 | 重写 | 改写为 NestJS Repository + Domain Entity | +| UI 组件(shared/components) | 直接迁移 | 平移至微前端共享包,保持 PascalCase 命名 | +| Hook(useAuth、usePermission) | 调整 | 改为通过 BFF/gRPC client 获取,接口签名保持不变 | +| 缓存层(cacheFn) | 重写 | 改为 NestJS Cache 模块 + Redis,去除 React cache() | +| arch:scan 工具 | 调整 | 扫描器扩展为多语言(TS+Go+Python),数据库结构保持 | +| 审计日志三件套 | 直接迁移 | 平移至 notification 服务,日志结构保持 | +| CI/CD 流水线 | 重写 | Gitea Actions 改为多服务并行流水线,新增契约校验阶段 | + +### 4.3 文档类资产 + +| 资产 | 策略 | 说明 | +|------|------|------| +| 架构影响地图(004) | 重写 | 从单应用模块图改为微服务限界上下文图 | +| K12 功能清单(006) | 直接迁移 | 功能清单与架构无关,直接平移 | +| 差距审计报告(007) | 调整 | 重新审计各微服务的功能完成度 | +| 模块角色映射(008) | 直接迁移 | 角色权限矩阵不变 | +| 路线图(roadmap/) | 重写 | 6 阶段微服务路线图 | +| known-issues.md | 直接迁移 | 经验日志平移,新增"微服务"分区 | +| 各模块 README | 重写 | 改为各微服务 README,按 DDD 上下文描述 | + +--- + +## 五、文档体系映射表 + +| CICD 文档 | Edu 对应文档 | 关系 | +|-----------|------------|------| +| `.trae/rules/project_rules.md` | `project_rules.md` | 调整(微服务版) | +| `docs/standards/coding-standards.md` | `docs/standards/coding-standards.md` | 调整(多语言版) | +| —(散落在 project_rules) | `docs/standards/git-workflow.md` | 新建 | +| `docs/architecture/004_architecture_impact_map.md` | `docs/architecture/001_architecture_overview.md` | 重写 | +| `docs/architecture/006_k12_feature_checklist.md` | `docs/architecture/feature_checklist.md` | 直接迁移 | +| `docs/architecture/007_gap_audit_report.md` | `docs/architecture/gap_audit.md` | 调整 | +| `docs/architecture/008_module_role_mapping.md` | `docs/architecture/role_mapping.md` | 直接迁移 | +| `docs/architecture/roadmap/README.md` | `docs/architecture/roadmap/README.md` | 重写 | +| `docs/architecture/roadmap/tech-debt.md` | `docs/architecture/roadmap/tech-debt.md` | 重写 | +| `docs/architecture/roadmap/decoupling.md` | `docs/architecture/roadmap/migration_phases.md` | 重写(迁移阶段化) | +| `docs/troubleshooting/known-issues.md` | `docs/troubleshooting/known-issues.md` | 直接迁移 + 新分区 | +| `src/modules/[module]/README.md` | `services/[service]/README.md` | 重写 | +| `docs/standards/coding-standards.md` §A11y | `docs/standards/accessibility.md` | 拆分独立 | + +--- + +## 六、六阶段路线图概览 + +### 6.1 阶段总览 + +```mermaid +gantt + title Edu 微服务迁移六阶段路线图 + dateFormat YYYY-MM-DD + axisFormat %m月 + + section P1 地基 + 仓库结构/契约/CI/可观测 :p1, 2026-07-01, 30d + + section P2 身份 + identity/auth/notification :p2, after p1, 30d + + section P3 核心教学 + org/teaching/content :p3, after p2, 45d + + section P4 内容分析 + insight+CQRS读模型/ES :p4, after p3, 30d + + section P5 沟通AI + comm/AI增强 :p5, after p4, 30d + + section P6 硬化 + 安全/灾备/性能/混沌 :p6, after p5, 30d +``` + +### 6.2 各阶段目标 + +| 阶段 | 名称 | 目标 | 关键交付物 | 验收信号 | +|------|------|------|-----------|---------| +| P1 | 地基 | 仓库骨架、契约工具链、CI、可观测平台 | monorepo 结构、buf 配置、Kafka 集群、OpenTelemetry | 契约生成 + 一次端到端 trace | +| P2 | 身份 | identity + auth + notification 三服务打通 | JWT 颁发、RBAC、邮件/短信/站内通知 | 用户注册→登录→收到通知 | +| P3 | 核心教学 | org + teaching + content | 班级/课表/作业/题库/考试 | 教师创建作业→学生提交→批改闭环 | +| P4 | 内容分析 | insight + CQRS 读模型 + ES 全文检索 | ClickHouse 报表、ES 搜索、Neo4j 知识图谱 | 多维分析报表 + 全文搜索可用 | +| P5 | 沟通AI | comm + AI 辅助 | 站内信/通知中心、AI 备课/出题/答疑 | 教师用 AI 出题并发布到班级 | +| P6 | 硬化 | 安全加固、灾备、性能、混沌工程 | WAF、定期备份、压测报告、混沌演练 | RPO≤15min、RTO≤30min、P99≤500ms | + +### 6.3 阶段交付门槛 + +每个阶段进入下一阶段前必须满足: + +1. **契约稳定**:本阶段涉及服务的 protobuf 契约冻结,breaking change 走 deprecation 流程 +2. **可观测完整**:tracing 覆盖率 ≥ 95%,关键业务指标(QPS/延迟/错误率)接入 Grafana +3. **测试覆盖**:单元测试 ≥ 80%,关键流程 E2E 通过 +4. **文档同步**:服务 README + 004 架构图 + known-issues 已更新 +5. **arch.db 同步**:`pnpm run arch:scan` 已扫描最新代码状态 + +--- + +## 七、原始项目可复用资产清单 + +### 7.1 设计令牌思路 + +**复用方式**:将 CICD 的 `src/app/styles/tokens/` 五层分层模型平移至微前端共享包。 + +| CICD 位置 | Edu 位置 | 调整 | +|-----------|---------|------| +| `src/app/styles/tokens/primitive.css` | `packages/ui-tokens/primitive.css` | 直接平移 | +| `src/app/styles/tokens/semantic-light.css` | `packages/ui-tokens/semantic-light.css` | 直接平移 | +| `src/app/styles/tokens/semantic-dark.css` | `packages/ui-tokens/semantic-dark.css` | 直接平移 | +| `src/app/styles/tokens/lesson-preparation.css` | `packages/ui-tokens/lesson-preparation.css` | 直接平移 | +| `src/app/styles/tokens/tailwind-theme.css` | `packages/ui-tokens/tailwind-theme.css` | 直接平移 | + +**强制规则保持不变**: +- 禁止硬编码颜色(`#hex`) +- 禁止硬编码字体(`'Inter'`/`'Fraunces'`/`'JetBrains Mono'`) +- 禁止硬编码字号(`font-size: Npx`) +- 禁止 Tailwind 任意值(`w-[Npx]`) +- ESLint `no-restricted-syntax` + `design-tokens/no-hardcoded-fonts` 规则沿用 + +### 7.2 UI 组件模式 + +**复用方式**:将 CICD 的 `src/shared/components/ui/` 平移至 `packages/ui-components/`,作为 Module Federation 共享依赖。 + +| 组件类别 | CICD 路径 | Edu 路径 | 复用要点 | +|---------|----------|---------|---------| +| 基础组件(Button/Input/Dialog) | `shared/components/ui/` | `packages/ui-components/` | 全部平移,保持 PascalCase 命名 | +| A11y 组件 | `shared/components/a11y/` | `packages/ui-components/a11y/` | skip-link、visually-hidden、focus-trap、aria-status 全部平移 | +| 图表组件 | 各模块内 | `packages/ui-components/charts/` | 收集 recharts 封装,统一暴露 | +| 表单组件 | react-hook-form 封装 | `packages/ui-components/form/` | 与 zod resolver 一同平移 | + +**复用规则**: +- 组件必须为纯函数,使用 `function` 声明 +- 不使用 `React.FC`,直接用函数声明 + 显式标注 props 类型 +- 默认服务端组件(微前端 host),需要交互时才添加 `"use client"` +- 组件行数 ≤ 500 行(复杂表单/大型表格可放宽至 800 行) + +### 7.3 权限模型 + +**复用方式**:将 CICD 的 `requirePermission()` + `usePermission().hasPermission()` 模式平移至 identity 服务 + auth 基础设施服务。 + +| CICD 资产 | Edu 位置 | 调整 | +|-----------|---------|------| +| `shared/lib/auth-guard.ts`(requirePermission) | `services/auth/src/guards/permission.guard.ts`(NestJS Guard) | 改为 NestJS Guard 装饰器 | +| `shared/types/permissions.ts`(权限点常量) | `packages/contracts/src/permissions.ts` | 集中到 contracts 包,多服务共享 | +| `usePermission` Hook | `packages/ui-components/hooks/use-permission.ts` | 通过 BFF 拉取权限,Hook 接口不变 | +| 角色权限矩阵(008) | `docs/architecture/role_mapping.md` | 直接平移 | + +**强制规则保持不变**: +- 每个 Controller/Action 必须调用 `requirePermission()` 等价物 +- 前端组件禁止使用 `role === "xxx"` 硬编码,统一使用 `usePermission().hasPermission()` + +### 7.4 缓存策略 + +**复用方式**:CICD 的 `cacheFn`(React `cache()` + 自定义缓存层)改为 NestJS Cache 模块 + Redis,**权限数据不跨请求缓存**的规则沿用。 + +| CICD 模式 | Edu 模式 | 备注 | +|-----------|---------|------| +| `cacheFn`(React cache) | NestJS `@UseInterceptors(CacheInterceptor)` + Redis | 单请求内缓存改为 NestJS REQUEST scope | +| `unstable_cache` | 禁用 | CICD 已禁用,Edu 沿用禁用决策 | +| 权限数据缓存 | 仅在 auth 服务内部缓存,TTL ≤ 60s | 跨服务不缓存权限 | +| 静态资源缓存 | CDN + 短缓存 | 配置不变 | + +### 7.5 Server Action 模式 → Application Service 模式 + +**复用方式**:CICD 的 Server Action 编排模式(权限 + Zod 验证 + 调用 data-access + revalidate)平移为 NestJS Application Service 编排模式。 + +| CICD Server Action 步骤 | NestJS Application Service 对应 | +|------------------------|-------------------------------| +| `requirePermission(perm)` | `@RequirePermission(perm)` 装饰器 + Guard | +| Zod `safeParse` | `ValidationPipe` + DTO class-validator | +| 调用 `data-access` | 调用 Domain Service / Repository | +| `revalidatePath` | 发布领域事件触发读模型更新 | +| 返回 `ActionState` | 返回 protobuf message(结构同 ActionState) | + +### 7.6 状态管理 5 层模型 + +**复用方式**:CICD 的 5 层状态模型平移至微前端 host 应用。 + +| 层级 | CICD 方案 | Edu 方案 | 备注 | +|------|----------|---------|------| +| L1 URL | nuqs | nuqs | 直接平移 | +| L2 Server | TanStack Query | TanStack Query | 直接平移 | +| L3 Client Business | Zustand slice | Zustand slice | 直接平移 | +| L4 Global UI | Zustand ui-store + ModalRoot | Zustand ui-store + ModalRoot | 直接平移 | +| L5 Form | react-hook-form + zodResolver | react-hook-form + zodResolver | 直接平移 | + +### 7.7 A11y 工具集 + +**复用方式**:全部平移至 `packages/ui-components/a11y/`。 + +- `useA11yId`:自动生成唯一 ARIA ID +- `mergeA11yProps`:合并 ARIA props +- `describeInput`:关联 input 与描述 +- `loadingAria`:加载状态 ARIA 属性 +- `useAriaLive`:aria-live 区域管理 Hook +- `skip-link`、`visually-hidden`、`focus-trap`、`aria-status` 组件 + +**目标**:WCAG 2.2 AA 合规,`eslint-plugin-jsx-a11y` 设为 `error`。 + +### 7.8 审计日志三件套 + +**复用方式**:平移至 notification 服务,日志结构保持。 + +| CICD 资产 | Edu 位置 | 备注 | +|-----------|---------|------| +| `shared/lib/login-logger.ts` | `services/notification/src/loggers/login-logger.ts` | 登录尝试日志 | +| `shared/lib/change-logger.ts` | `services/notification/src/loggers/change-logger.ts` | 数据变更日志(监听领域事件) | +| `shared/lib/audit-logger.ts` | `services/notification/src/loggers/audit-logger.ts` | 关键业务操作日志 | + +### 7.9 CI/CD 流水线模式 + +**复用方式**:三套工作流模式沿用(CI + 安全扫描 + 灾备演练),但执行方式改为多服务并行。 + +| CICD 工作流 | Edu 工作流 | 调整 | +|------------|-----------|------| +| `ci.yml` | `.gitea/workflows/ci.yml` | 单体改为矩阵并行(每个服务一个 job) | +| `security.yml` | `.gitea/workflows/security.yml` | 新增 Trivy 扫描 Docker 镜像 | +| `dr-drill.yml` | `.gitea/workflows/dr-drill.yml` | 灾备演练改为多服务恢复顺序演练 | + +**CI 必须包含**(沿用 CICD 规则): +1. 安装依赖(多语言:pnpm install / go mod download / uv sync) +2. Lint 检查(ESLint + golangci-lint + ruff) +3. 类型检查(tsc --noEmit + go vet + mypy) +4. 契约校验(buf lint + buf breaking) +5. 单元测试 + 覆盖率 +6. 构建(多服务并行) +7. 安全审计(npm audit + Snyk + Trivy) +8. E2E 测试(仅主分支) + +--- + +## 八、迁移验收标准 + +### 8.1 阶段验收清单 + +每个阶段完成时必须勾选所有项: + +- [ ] 本阶段服务的 protobuf 契约已冻结并通过 `buf breaking` 校验 +- [ ] 服务 README 已编写,包含限界上下文图与依赖关系 +- [ ] `docs/architecture/001_architecture_overview.md` 已同步更新 +- [ ] `docs/troubleshooting/known-issues.md` 已追加本阶段经验日志 +- [ ] `pnpm run arch:scan` 已扫描最新代码状态(含 TS+Go+Python) +- [ ] 单元测试覆盖率 ≥ 80% +- [ ] tracing 覆盖率 ≥ 95%,关键指标已接入 Grafana +- [ ] `pnpm run lint` 与 `pnpm run typecheck` 零错误 +- [ ] 安全扫描无高危漏洞 + +### 8.2 整体迁移完成标准 + +- [ ] 6 阶段全部交付且验收通过 +- [ ] CICD 35 个业务模块全部在对应微服务中找到归宿 +- [ ] 旧系统流量完全切流至新系统(strangler fig 完成) +- [ ] RPO ≤ 15min,RTO ≤ 30min +- [ ] P99 延迟 ≤ 500ms +- [ ] 全链路追踪可用,任意请求可一键定位调用链 +- [ ] 文档体系完整:架构图、契约、README、known-issues 全部同步 + +### 8.3 回滚预案 + +每个阶段均需准备回滚方案: + +1. **P1 地基**:基础设施回滚(Kafka/可观测平台降级) +2. **P2 身份**:双写期间保留 CICD NextAuth 作为 fallback +3. **P3 核心教学**:双写 + 读流量按比例切流,异常即回切 +4. **P4 内容分析**:CQRS 读模型失败时降级为直查 MySQL +5. **P5 沟通AI**:AI 服务降级为兜底响应,不影响主流程 +6. **P6 硬化**:混沌演练发现问题后回滚至上一稳定版本 + +--- + +## 附录:迁移过程中的关键决策点 + +| 决策点 | 选择 | 理由 | +|--------|------|------| +| 服务拆分粒度 | 6 业务 + 2 基础设施 | 平衡团队规模与拆分收益,避免过细导致 RPC 开销 | +| 通信协议 | gRPC(内部)+ REST(BFF 对外) | 内部高性能,外部兼容性 | +| 事件总线 | Kafka + Debezium CDC | CDC 减少业务代码侵入,Outbox 模式保证一致性 | +| 契约工具 | protobuf + buf | 多语言代码生成,breaking change 检测 | +| 前端架构 | Module Federation | 微前端独立部署,运行时共享依赖 | +| 状态管理 | 沿用 5 层模型 | 团队熟悉度高,迁移成本低 | +| 数据库拆分 | 每服务独占库 | 杜绝跨服务联表,强制契约化通信 | +| 缓存策略 | NestJS Cache + Redis | 替代 React cache(),规则(权限不跨请求缓存)沿用 | +| 迁移方式 | strangler fig | 风险可控,渐进式切换 | +| arch.db | 多语言扫描扩展 | 复用 CICD 元数据库思路,扩展 Go/Python 扫描器 | diff --git a/README.md b/README.md new file mode 100644 index 0000000..76c477c --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Next Edu Cloud + +基于 DDD + EDA + CQRS 的企业级微服务架构教育云平台。 + +## 技术栈 + +- **网关层**: Go (Gin) +- **业务服务**: TypeScript (NestJS) +- **分析/AI**: Python (FastAPI) +- **前端**: React (Next.js + Module Federation) +- **存储**: MySQL / Redis / ClickHouse / Neo4j / Elasticsearch +- **事件总线**: Kafka + Debezium CDC +- **契约**: protobuf + buf + +## 快速开始 + +```bash +# 1. 安装依赖 +pnpm install + +# 2. 启动最小基础设施(MySQL + Redis) +docker compose -f infra/docker-compose.minimal.yml up -d + +# 3. 复制环境变量 +cp .env.example .env + +# 4. 运行 arch.db 扫描 +pnpm run arch:scan + +# 5. 启动服务 +pnpm dev +``` + +## 文档 + +- [架构设计](docs/architecture/004_architecture_impact_map.md) +- [理想蓝图](docs/architecture/0010_architecture.md) +- [编码规范](docs/standards/coding-standards.md) +- [UI 设计系统](docs/standards/ui-design-system.md) +- [Git 工作流](docs/standards/git-workflow.md) +- [已知问题](docs/troubleshooting/known-issues.md) +- [项目规则](project_rules.md) +- [迁移指南](MIGRATION_GUIDE.md) + +## 开发阶段 + +- **P1 地基** (M1-M3): 仓库骨架 + 基础设施 + classes 黄金模板 +- **P2 身份** (M4-M6): IAM 服务 + Teacher BFF + 微前端骨架 +- **P3 核心教学** (M7-M10): CoreEdu + Outbox + 考试/作业/成绩 +- **P4 内容分析** (M11-M13): Content + DataAna + CDC +- **P5 沟通AI** (M14-M16): Msg + Push + AI + ES +- **P6 硬化** (M17-M18): Service Mesh + 生产硬化 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..841631e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## 报告漏洞 + +发现安全漏洞请勿公开提交 issue,请发送邮件至 security@example.com。 + +## 安全架构 + +- JWT RS256 非对称签名(IAM 私钥签发,Gateway/服务公钥校验) +- DataScope 6 级数据范围过滤 +- 参数化 SQL 查询(禁止字符串拼接) +- 环境变量校验(@t3-oss/env-nextjs / viper / pydantic-settings) +- 密钥管理:开发用 .env + docker secrets,生产用 Vault(P6) diff --git a/apps/teacher-portal/next-env.d.ts b/apps/teacher-portal/next-env.d.ts new file mode 100644 index 0000000..6080add --- /dev/null +++ b/apps/teacher-portal/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/apps/teacher-portal/next.config.js b/apps/teacher-portal/next.config.js new file mode 100644 index 0000000..027d357 --- /dev/null +++ b/apps/teacher-portal/next.config.js @@ -0,0 +1,14 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + async rewrites() { + return [ + { + source: '/api/v1/:path*', + destination: `${process.env.API_GATEWAY_URL || 'http://localhost:8080'}/api/v1/:path*`, + }, + ]; + }, +}; + +module.exports = nextConfig; diff --git a/apps/teacher-portal/package.json b/apps/teacher-portal/package.json new file mode 100644 index 0000000..7a2da05 --- /dev/null +++ b/apps/teacher-portal/package.json @@ -0,0 +1,26 @@ +{ + "name": "@edu/teacher-portal", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3000", + "build": "next build", + "start": "next start -p 3000", + "lint": "next lint", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.6.0" + } +} diff --git a/apps/teacher-portal/postcss.config.js b/apps/teacher-portal/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/apps/teacher-portal/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/teacher-portal/src/app/globals.css b/apps/teacher-portal/src/app/globals.css new file mode 100644 index 0000000..52d88b5 --- /dev/null +++ b/apps/teacher-portal/src/app/globals.css @@ -0,0 +1,43 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --bg-paper: hsl(40, 20%, 98%); + --color-ink: hsl(25, 3%, 15%); + --color-ink-muted: hsl(30, 5%, 45%); + --color-accent: hsl(220, 60%, 35%); + --color-rule: hsl(30, 10%, 90%); + --font-serif: 'Fraunces', Georgia, serif; + --font-sans: 'Inter', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', monospace; +} + +html, body { + background: var(--bg-paper); + color: var(--color-ink); + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-serif); + font-weight: 600; + letter-spacing: -0.01em; +} + +/* 纸感分隔线 */ +.rule { + border-top: 1px solid var(--color-rule); +} + +.rule-thin { + border-top: 2px solid var(--color-rule); +} + +/* 左侧竖线标记(节点展开样式)*/ +.mark-left { + border-left: 2px solid var(--color-rule); + padding-left: 12px; +} diff --git a/apps/teacher-portal/src/app/layout.tsx b/apps/teacher-portal/src/app/layout.tsx new file mode 100644 index 0000000..65e5214 --- /dev/null +++ b/apps/teacher-portal/src/app/layout.tsx @@ -0,0 +1,24 @@ +import './globals.css'; +import type { Metadata } from 'next'; +import { Inter, Fraunces, JetBrains_Mono } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-inter' }); +const fraunces = Fraunces({ subsets: ['latin'], variable: '--font-fraunces' }); +const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' }); + +export const metadata: Metadata = { + title: 'Edu Teacher Portal', + description: 'K12 智慧教务平台 - 教师端', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/apps/teacher-portal/src/app/page.tsx b/apps/teacher-portal/src/app/page.tsx new file mode 100644 index 0000000..a1abe7b --- /dev/null +++ b/apps/teacher-portal/src/app/page.tsx @@ -0,0 +1,219 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +interface ClassItem { + id: string; + name: string; + gradeId: string; + description?: string; + createdAt: number; + updatedAt: number; +} + +interface ApiResponse { + success: boolean; + data?: T; + error?: { code: string; message: string }; +} + +export default function HomePage() { + const [classes, setClasses] = useState([]); + const [loading, setLoading] = useState(false); + const [name, setName] = useState(''); + const [gradeId, setGradeId] = useState('550e8400-e29b-41d4-a716-446655440000'); + const [description, setDescription] = useState(''); + const [error, setError] = useState(null); + + const fetchClasses = useCallback(async () => { + setLoading(true); + setError(null); + try { + const res = await fetch('/api/v1/classes'); + const json: ApiResponse = await res.json(); + if (json.success && json.data) { + setClasses(json.data); + } else { + setError(json.error?.message || 'Failed to load'); + } + } catch (e) { + setError(e instanceof Error ? e.message : 'Network error'); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchClasses(); + }, [fetchClasses]); + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim()) return; + try { + const res = await fetch('/api/v1/classes', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer dev-token' }, + body: JSON.stringify({ name, gradeId, description }), + }); + const json = await res.json(); + if (!json.success) { + setError(json.error?.message || 'Create failed'); + return; + } + setName(''); + setDescription(''); + await fetchClasses(); + } catch (e) { + setError(e instanceof Error ? e.message : 'Network error'); + } + }; + + const handleDelete = async (id: string) => { + try { + const res = await fetch(`/api/v1/classes/${id}`, { + method: 'DELETE', + headers: { 'Authorization': 'Bearer dev-token' }, + }); + const json = await res.json(); + if (!json.success) { + setError(json.error?.message || 'Delete failed'); + return; + } + await fetchClasses(); + } catch (e) { + setError(e instanceof Error ? e.message : 'Network error'); + } + }; + + return ( +
+
+
+

+ 班级管理 +

+

+ P1 黄金模板验证 - classes 域 CRUD +

+
+
+ +
+ {/* 左侧:创建表单 */} +