kindergarten_java/docs/design/14-技术选型与项目初始化.md
2026-02-28 16:41:39 +08:00

28 KiB
Raw Permalink Blame History

技术选型与项目初始化

创建时间2025-02-04 基于之前的设计文档,确定技术栈并搭建项目框架


一、技术选型确认

1.1 前端技术栈

技术 选择 版本 说明
框架 Vue 3 ^3.4.0 渐进式框架,生态完善
构建工具 Vite ^5.0.0 快速的开发服务器
UI组件库 Ant Design Vue ^4.0.0 企业级UI组件
状态管理 Pinia ^2.1.0 Vue官方推荐
路由 Vue Router ^4.2.0 官方路由管理
HTTP客户端 Axios ^1.6.0 Promise-based HTTP
工具库 Lodash-es ^4.17.0 实用工具函数
日期处理 Day.js ^1.11.0 轻量级日期库

1.2 后端技术栈

技术 选择 版本 说明
框架 NestJS ^10.0.0 Node.js企业级框架
语言 TypeScript ^5.0.0 类型安全
ORM Prisma ^5.0.0 现代化ORM
数据库 PostgreSQL ^15.0.0 开源关系数据库
缓存 Redis ^7.0.0 内存数据库
认证 JWT + Passport - Token认证
文件存储 阿里云OSS / MinIO - 对象存储
验证 class-validator - 数据验证

1.3 开发工具

工具 用途
VS Code 代码编辑器
Git 版本控制
Docker 容器化部署
Postman API测试

二、项目目录结构

2.1 整体结构

reading-platform/
├── frontend/                    # 前端项目(三端统一)
│   ├── src/
│   │   ├── views/
│   │   │   ├── admin/           # 超管端页面
│   │   │   ├── school/          # 学校端页面
│   │   │   ├── teacher/         # 教师端页面
│   │   │   └── auth/            # 认证页面
│   │   ├── components/          # 公共组件
│   │   ├── stores/              # Pinia状态管理
│   │   ├── api/                 # API请求
│   │   ├── router/              # 路由配置
│   │   ├── utils/               # 工具函数
│   │   └── types/               # TypeScript类型
│   ├── package.json
│   └── vite.config.ts
│
├── backend/                     # 后端项目
│   ├── src/
│   │   ├── modules/
│   │   │   ├── auth/            # 认证模块
│   │   │   ├── admin/           # 超管端模块
│   │   │   ├── school/          # 学校端模块
│   │   │   ├── teacher/         # 教师端模块
│   │   │   ├── course/          # 课程包模块
│   │   │   ├── tenant/          # 租户模块
│   │   │   ├── common/          # 公共模块
│   │   │   └── upload/          # 文件上传模块
│   │   ├── common/
│   │   │   ├── decorators/      # 装饰器
│   │   │   ├── guards/          # 守卫
│   │   │   ├── filters/         # 过滤器
│   │   │   └── interceptors/    # 拦截器
│   │   ├── config/              # 配置
│   │   └── database/            # 数据库相关
│   ├── prisma/
│   │   └── schema.prisma        # Prisma模型
│   ├── package.json
│   └── nest-cli.json
│
├── docs/                        # 文档
│   └── ...                      # 已有的设计文档
│
├── docker-compose.yml           # Docker编排
└── README.md

三、数据库模型Prisma Schema

3.1 核心模型定义

// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// ==================== 租户相关 ====================

model Tenant {
  id            BigInt   @id @default(autoincrement())
  name          String
  address       String?
  contactPerson String?  @map("contact_person")
  contactPhone  String?  @map("contact_phone")
  logoUrl       String?  @map("logo_url")

  packageType   PackageType @map("package_type")
  teacherQuota  Int       @default(20) @map("teacher_quota")
  studentQuota  Int       @default(200) @map("student_quota")
  storageQuota  BigInt    @default(5368709120) @map("storage_quota") // 5GB

  startDate     DateTime @map("start_date") @db.Date
  expireDate    DateTime @map("expire_date") @db.Date

  teacherCount  Int       @default(0) @map("teacher_count")
  studentCount  Int       @default(0) @map("student_count")
  storageUsed   BigInt    @default(0) @map("storage_used")

  status        TenantStatus @default(ACTIVE)

  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")

  teachers      Teacher[]
  students      Student[]
  classes       Class[]
  lessons       Lesson[]
  tenantCourses TenantCourse[]

  @@map("tenants")
}

enum PackageType {
  BASIC
  STANDARD
  ADVANCED
}

enum TenantStatus {
  ACTIVE
  SUSPENDED
  EXPIRED
}

// ==================== 教师相关 ====================

model Teacher {
  id           BigInt   @id @default(autoincrement())
  tenantId     BigInt   @map("tenant_id")
  name         String
  phone        String
  email        String?

  loginAccount String   @unique @map("login_account")
  passwordHash String   @map("password_hash")
  classIds     Json?    @map("class_ids") // 存储负责的班级ID列表

  status       TeacherStatus @default(ACTIVE)

  lessonCount  Int      @default(0) @map("lesson_count")
  feedbackCount Int     @default(0) @map("feedback_count")

  createdAt    DateTime @default(now()) @map("created_at")
  updatedAt    DateTime @updatedAt @map("updated_at")
  lastLoginAt  DateTime? @map("last_login_at")

  tenant       Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  lessons      Lesson[]
  feedbacks    LessonFeedback[]

  @@map("teachers")
}

enum TeacherStatus {
  ACTIVE
  SUSPENDED
}

// ==================== 班级相关 ====================

model Class {
  id           BigInt   @id @default(autoincrement())
  tenantId     BigInt   @map("tenant_id")
  name         String
  grade        Grade

  teacherId    BigInt?  @map("teacher_id")

  studentCount Int      @default(0) @map("student_count")
  lessonCount  Int      @default(0) @map("lesson_count")

  createdAt    DateTime @default(now()) @map("created_at")
  updatedAt    DateTime @updatedAt @map("updated_at")

  tenant       Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  teacher      Teacher? @relation(fields: [teacherId], references: [id])
  students     Student[]
  lessons      Lesson[]

  @@map("classes")
}

enum Grade {
  SMALL    // 小班
  MIDDLE   // 中班
  BIG      // 大班
}

// ==================== 学生相关 ====================

model Student {
  id           BigInt    @id @default(autoincrement())
  tenantId     BigInt    @map("tenant_id")
  classId      BigInt    @map("class_id")
  name         String
  gender       Gender?
  birthDate    DateTime? @map("birth_date") @db.Date
  parentPhone  String?   @map("parent_phone")
  parentName   String?   @map("parent_name")

  readingCount Int       @default(0) @map("reading_count")
  lessonCount  Int       @default(0) @map("lesson_count")

  createdAt    DateTime  @default(now()) @map("created_at")
  updatedAt    DateTime  @updatedAt @map("updated_at")

  tenant       Tenant    @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  class        Class     @relation(fields: [classId], references: [id], onDelete: Cascade)
  records      StudentRecord[]

  @@map("students")
}

enum Gender {
  MALE
  FEMALE
}

// ==================== 课程包相关 ====================

model Course {
  id               BigInt      @id @default(autoincrement())
  name             String
  description      String?     @db.Text

  pictureBookId    BigInt?     @map("picture_book_id")
  pictureBookName  String?     @map("picture_book_name")

  gradeTags        Json        @map("grade_tags") // ["small", "middle"]
  domainTags       Json        @map("domain_tags") // [{"level1":"语言", "level2":"阅读与书写准备"}]

  duration         Int         @default(25) // 分钟

  status           CourseStatus @default(DRAFT)
  version          String      @default("1.0")

  usageCount       Int         @default(0) @map("usage_count")
  teacherCount     Int         @default(0) @map("teacher_count")
  avgRating        Decimal     @default(0) @map("avg_rating") @db.Decimal(3, 2)

  createdBy        BigInt?     @map("created_by")

  createdAt        DateTime    @default(now()) @map("created_at")
  updatedAt        DateTime    @updatedAt @map("updated_at")
  publishedAt      DateTime?   @map("published_at")

  resources        CourseResource[]
  scripts          CourseScript[]
  activities       CourseActivity[]
  lessons          Lesson[]
  tenantCourses    TenantCourse[]

  @@map("courses")
}

enum CourseStatus {
  DRAFT
  REVIEWING
  PUBLISHED
  ARCHIVED
}

// ==================== 课程资源 ====================

model CourseResource {
  id            BigInt         @id @default(autoincrement())
  courseId      BigInt         @map("course_id")
  resourceType  ResourceType   @map("resource_type")
  resourceName  String         @map("resource_name")
  fileUrl       String         @map("file_url")
  fileSize      BigInt?        @map("file_size")
  mimeType      String?        @map("mime_type")
  metadata      Json?          // 其他元数据(时长、分辨率等)

  sortOrder     Int            @default(0) @map("sort_order")

  createdAt     DateTime       @default(now()) @map("created_at")

  course        Course         @relation(fields: [courseId], references: [id], onDelete: Cascade)

  @@map("course_resources")
}

enum ResourceType {
  EBOOK
  AUDIO
  VIDEO
  PPT
  ANIMATION
  INTERACTIVE
}

// ==================== 课程脚本 ====================

model CourseScript {
  id                  BigInt    @id @default(autoincrement())
  courseId            BigInt    @map("course_id")
  stepIndex           Int       @map("step_index")
  stepName            String    @map("step_name")
  stepType            StepType  @map("step_type")

  duration            Int       // 分钟
  objective           String?   @db.Text
  teacherScript       String?   @map("teacher_script") @db.Text
  interactionPoints   Json?     @map("interaction_points")

  resourceIds         Json?     @map("resource_ids") // 关联的资源ID列表

  sortOrder           Int       @default(0) @map("sort_order")

  createdAt           DateTime  @default(now()) @map("created_at")
  updatedAt           DateTime  @updatedAt @map("updated_at")

  course              Course    @relation(fields: [courseId], references: [id], onDelete: Cascade)
  pages               CourseScriptPage[]

  @@unique([courseId, stepIndex])
  @@map("course_scripts")
}

enum StepType {
  INTRO      // 导入
  GUIDE      // 引导观察
  READING    // 师幼共读
  ACTIVITY   // 延伸活动
  ENDING     // 活动结束
}

// ==================== 逐页配置 ====================

model CourseScriptPage {
  id                    BigInt   @id @default(autoincrement())
  scriptId              BigInt   @map("script_id")
  pageNumber            Int      @map("page_number")
  questions             Json?    // 提问列表
  interactionComponent  Json?    @map("interaction_component")
  teacherNotes          String?  @map("teacher_notes") @db.Text

  createdAt             DateTime @default(now()) @map("created_at")
  updatedAt             DateTime @updatedAt @map("updated_at")

  script                CourseScript @relation(fields: [scriptId], references: [id], onDelete: Cascade)

  @@unique([scriptId, pageNumber])
  @@map("course_script_pages")
}

// ==================== 延伸活动 ====================

model CourseActivity {
  id                BigInt          @id @default(autoincrement())
  courseId          BigInt          @map("course_id")
  name              String

  domain            String?         // 所属领域
  domainTagId       BigInt?         @map("domain_tag_id")
  activityType      ActivityType    @map("activity_type")
  duration          Int?            // 分钟

  onlineMaterials   Json?           @map("online_materials")
  offlineMaterials  String?         @map("offline_materials") @db.Text
  activityGuide     String?         @map("activity_guide") @db.Text
  objectives        String?         @db.Text

  sortOrder         Int             @default(0) @map("sort_order")

  createdAt         DateTime        @default(now()) @map("created_at")

  course            Course          @relation(fields: [courseId], references: [id], onDelete: Cascade)

  @@map("course_activities")
}

enum ActivityType {
  HANDS_ON       // 动手操作
  COMMUNICATION  // 交流讨论
  GAME           // 游戏互动
  OTHER
}

// ==================== 授课记录 ====================

model Lesson {
  id               BigInt        @id @default(autoincrement())
  tenantId         BigInt        @map("tenant_id")
  teacherId        BigInt        @map("teacher_id")
  classId          BigInt        @map("class_id")
  courseId         BigInt        @map("course_id")

  plannedDatetime  DateTime?     @map("planned_datetime")
  startDatetime    DateTime?     @map("start_datetime")
  endDatetime      DateTime?     @map("end_datetime")
  actualDuration   Int?          @map("actual_duration") // 分钟

  status           LessonStatus  @default(PLANNED)

  overallRating    OverallRating? @map("overall_rating")
  participationRating ParticipationRating? @map("participation_rating")
  completionNote   CompletionNote? @map("completion_note")

  createdAt        DateTime      @default(now()) @map("created_at")
  updatedAt        DateTime      @updatedAt @map("updated_at")

  tenant           Tenant        @relation(fields: [tenantId], references: [id])
  teacher          Teacher       @relation(fields: [teacherId], references: [id])
  class            Class         @relation(fields: [classId], references: [id])
  course           Course        @relation(fields: [courseId], references: [id])
  feedbacks        LessonFeedback[]
  records          StudentRecord[]

  @@map("lessons")
}

enum LessonStatus {
  PLANNED
  IN_PROGRESS
  COMPLETED
  CANCELLED
}

enum OverallRating {
  EXCELLENT
  GOOD
  AVERAGE
  NEEDS_IMPROVEMENT
}

enum ParticipationRating {
  VERY_HIGH
  HIGH
  MEDIUM
  LOW
}

enum CompletionNote {
  ALL_COMPLETED
  ADJUSTED
  PARTIAL
}

// ==================== 课程反馈 ====================

model LessonFeedback {
  id               BigInt   @id @default(autoincrement())
  lessonId         BigInt   @map("lesson_id")
  teacherId        BigInt   @map("teacher_id")

  designQuality    Int?     @map("design_quality") // 1-5
  participation    Int?     // 1-5
  goalAchievement  Int?     @map("goal_achievement") // 1-5

  stepFeedbacks    Json?    @map("step_feedbacks")

  pros             String?  @db.Text
  suggestions      String?  @db.Text

  activitiesDone   Json?    @map("activities_done")

  createdAt        DateTime @default(now()) @map("created_at")
  updatedAt        DateTime @updatedAt @map("updated_at")

  lesson           Lesson   @relation(fields: [lessonId], references: [id], onDelete: Cascade)
  teacher          Teacher  @relation(fields: [teacherId], references: [id])

  @@unique([lessonId, teacherId])
  @@map("lesson_feedbacks")
}

// ==================== 学生测评记录 ====================

model StudentRecord {
  id                  BigInt   @id @default(autoincrement())
  lessonId            BigInt   @map("lesson_id")
  studentId           BigInt   @map("student_id")

  focus               Int?     // 1-5
  participation       Int?
  interest            Int?
  understanding       Int?

  domainAchievements  Json?    @map("domain_achievements")

  notes               String?  @db.Text

  createdAt           DateTime @default(now()) @map("created_at")
  updatedAt           DateTime @updatedAt @map("updated_at")

  lesson              Lesson   @relation(fields: [lessonId], references: [id], onDelete: Cascade)
  student             Student  @relation(fields: [studentId], references: [id])

  @@unique([lessonId, studentId])
  @@map("student_records")
}

// ==================== 标签体系 ====================

model Tag {
  id          BigInt   @id @default(autoincrement())
  level       Int      // 1-4
  code        String   @unique
  name        String
  parentId    BigInt?  @map("parent_id")

  description String?  @db.Text
  metadata    Json?

  sortOrder   Int      @default(0) @map("sort_order")

  createdAt   DateTime @default(now()) @map("created_at")

  @@index([parentId])
  @@index([level])
  @@map("tags")
}

// ==================== 租户课程授权 ====================

model TenantCourse {
  id           BigInt   @id @default(autoincrement())
  tenantId     BigInt   @map("tenant_id")
  courseId     BigInt   @map("course_id")

  authorized   Boolean  @default(true)
  authorizedAt DateTime? @map("authorized_at")

  createdAt    DateTime @default(now()) @map("created_at")

  tenant       Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  course       Course   @relation(fields: [courseId], references: [id], onDelete: Cascade)

  @@unique([tenantId, courseId])
  @@map("tenant_courses")
}

四、后端核心模块

4.1 项目初始化

# 创建后端项目
mkdir reading-platform-backend
cd reading-platform-backend
npm init -y

# 安装依赖
npm install @nestjs/common@^10.0.0 @nestjs/core@^10.0.0 @nestjs/platform-express@^10.0.0
npm install @nestjs/config@^3.0.0 @nestjs/jwt@^10.0.0 @nestjs/passport@^10.0.0
npm install @prisma/client@^5.0.0 passport@^0.6.0 passport-jwt@^4.0.0
npm install class-validator@^0.14.0 class-transformer@^0.5.1
npm install bcrypt@^5.1.0
npm install @nestjs/throttler@^5.0.0  # 限流
npm install -D @nestjs/cli@^10.0.0
npm install -D prisma@^5.0.0
npm install -D typescript@^5.0.0 @types/node@^20.0.0 @types/passport-jwt@^4.0.0 @types/bcrypt@^5.0.0

# 安装额外依赖
npm install @nestjs/swagger@^7.0.0  # API文档
npm install rxjs@^7.8.0
npm install reflect-metadata@^0.1.0

4.2 NestJS主模块

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 全局验证管道
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );

  // CORS
  app.enableCors({
    origin: process.env.FRONTEND_URL || 'http://localhost:5173',
    credentials: true,
  });

  // API前缀
  app.setGlobalPrefix('api/v1');

  const port = process.env.PORT || 3000;
  await app.listen(port);

  console.log(`🚀 Application is running on: http://localhost:${port}`);
  console.log(`📚 API documentation: http://localhost:${port}/api/docs`);
}

bootstrap();
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
import { PrismaModule } from './database/prisma.module';
import { AuthModule } from './modules/auth/auth.module';
import { AdminModule } from './modules/admin/admin.module';
import { SchoolModule } from './modules/school/school.module';
import { TeacherModule } from './modules/teacher/teacher.module';
import { CourseModule } from './modules/course/course.module';
import { TenantModule } from './modules/tenant/tenant.module';
import { CommonModule } from './modules/common/common.module';
import { UploadModule } from './modules/upload/upload.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
    }),
    ThrottlerModule.forRoot([{
      ttl: 60000, // 60秒
      limit: 100, // 最多100个请求
    }]),
    PrismaModule,
    AuthModule,
    AdminModule,
    SchoolModule,
    TeacherModule,
    CourseModule,
    TenantModule,
    CommonModule,
    UploadModule,
  ],
})
export class AppModule {}

五、前端项目初始化

5.1 项目创建

# 使用Vite创建Vue3项目
npm create vite@latest reading-platform-frontend -- --template vue-ts
cd reading-platform-frontend

# 安装依赖
npm install
npm install vue-router@^4.2.0 pinia@^2.1.0
npm install ant-design-vue@^4.0.0
npm install axios@^1.6.0 dayjs@^1.11.0 lodash-es@^4.17.0
npm install @ant-design/icons-vue@^7.0.0

# 开发依赖
npm install -D @types/lodash-es@^4.17.0 @types/node@^20.0.0
npm install -D unplugin-vue-components@^0.26.0  # 组件自动导入
npm install -D unplugin-auto-import@^0.17.0     # API自动导入

5.2 Vite配置

// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'pinia',
        {
          'ant-design-vue': [
            'message',
            'notification',
            'Modal',
          ],
        },
      ],
      dts: 'src/auto-imports.d.ts',
    }),
    Components({
      resolvers: [
        AntDesignVueResolver({
          importStyle: false,
        }),
      ],
      dts: 'src/components.d.ts',
    }),
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
});

5.3 路由配置

// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';

const routes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/auth/LoginView.vue'),
    meta: { requiresAuth: false },
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/admin/LayoutView.vue'),
    meta: { requiresAuth: true, role: 'admin' },
    children: [
      {
        path: '',
        redirect: '/admin/dashboard',
      },
      {
        path: 'dashboard',
        name: 'AdminDashboard',
        component: () => import('@/views/admin/DashboardView.vue'),
      },
      {
        path: 'courses',
        name: 'AdminCourses',
        component: () => import('@/views/admin/courses/CourseListView.vue'),
      },
      // ... 其他超管端路由
    ],
  },
  {
    path: '/school',
    name: 'School',
    component: () => import('@/views/school/LayoutView.vue'),
    meta: { requiresAuth: true, role: 'school' },
    children: [
      {
        path: '',
        redirect: '/school/dashboard',
      },
      {
        path: 'dashboard',
        name: 'SchoolDashboard',
        component: () => import('@/views/school/DashboardView.vue'),
      },
      // ... 其他学校端路由
    ],
  },
  {
    path: '/teacher',
    name: 'Teacher',
    component: () => import('@/views/teacher/LayoutView.vue'),
    meta: { requiresAuth: true, role: 'teacher' },
    children: [
      {
        path: '',
        redirect: '/teacher/dashboard',
      },
      {
        path: 'dashboard',
        name: 'TeacherDashboard',
        component: () => import('@/views/teacher/DashboardView.vue'),
      },
      {
        path: 'courses',
        name: 'TeacherCourses',
        component: () => import('@/views/teacher/courses/CourseListView.vue'),
      },
      {
        path: 'courses/:id',
        name: 'TeacherCourseDetail',
        component: () => import('@/views/teacher/courses/CourseDetailView.vue'),
      },
      {
        path: 'courses/:id/prepare',
        name: 'TeacherCoursePrepare',
        component: () => import('@/views/teacher/courses/PrepareModeView.vue'),
      },
      {
        path: 'lessons/:id',
        name: 'TeacherLesson',
        component: () => import('@/views/teacher/lessons/LessonView.vue'),
      },
      // ... 其他教师端路由
    ],
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFoundView.vue'),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

// 路由守卫
router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token');

  if (to.meta.requiresAuth && !token) {
    next('/login');
  } else if (to.path === '/login' && token) {
    // 根据用户角色跳转
    const role = localStorage.getItem('role');
    next(`/${role}/dashboard`);
  } else {
    next();
  }
});

export default router;

六、Docker配置

6.1 docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: reading-platform-db
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: reading_platform
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    container_name: reading-platform-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: reading-platform-backend
    environment:
      NODE_ENV: development
      DATABASE_URL: postgresql://admin:password@postgres:5432/reading_platform
      REDIS_URL: redis://redis:6379
      JWT_SECRET: your-secret-key
    ports:
      - "3000:3000"
    depends_on:
      - postgres
      - redis
    volumes:
      - ./backend:/app
      - /app/node_modules
    command: npm run start:dev

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: reading-platform-frontend
    ports:
      - "5173:5173"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    command: npm run dev

volumes:
  postgres_data:
  redis_data:

七、环境变量配置

7.1 后端环境变量

# backend/.env.development
NODE_ENV=development
PORT=3000

# 数据库
DATABASE_URL="postgresql://admin:password@localhost:5432/reading_platform"

# Redis
REDIS_URL="redis://localhost:6379"

# JWT
JWT_SECRET="your-super-secret-jwt-key-change-in-production"
JWT_EXPIRES_IN="7d"

# 文件上传
UPLOAD_DIR="./uploads"
MAX_FILE_SIZE=104857600  # 100MB

# OSS (阿里云)
OSS_REGION="oss-cn-hangzhou"
OSS_ACCESS_KEY_ID="your-access-key"
OSS_ACCESS_KEY_SECRET="your-secret"
OSS_BUCKET="reading-platform"

# 前端URL
FRONTEND_URL="http://localhost:5173"

7.2 前端环境变量

# frontend/.env.development
VITE_API_BASE_URL=http://localhost:3000/api/v1
VITE_APP_TITLE=幼儿阅读教学服务平台

八、下一步开发计划

更新时间: 2026-02-13 项目状态: 开发中 - 超管端课程包核心功能开发中

Phase 1: 基础框架第1-2周 已完成

  • 技术选型确认
  • 项目初始化
  • 数据库设计与迁移
  • 认证系统实现
  • 权限管理实现
  • 标签体系初始化

Phase 2: 超管端核心第3-6周🔄 进行中

  • 课程包制作工作台
  • 课程包管理(草稿/发布/审核)
  • 课程包审核流程
  • 租户管理(基础功能)
  • 资源库管理(第二阶段开发)
  • 数据统计看板

Phase 3: 教师端核心第7-10周🔄 进行中

  • 课程中心
  • 备课模式
  • 上课模式
  • 班级管理
  • 反馈记录

Phase 4: 学校端第11-12周🔄 框架完成

  • 教师管理
  • 学生管理
  • 课程授权查看
  • 数据报表

Phase 5: 反馈与数据第13-14周 待开发

  • 教师反馈收集
  • 测评记录
  • 数据统计优化
  • 性能优化

九、资源库开发策略2026-02-13 确认)

根据MVP优先级资源库功能采用三阶段开发策略

阶段 内容 状态
第一阶段 完成课程包核心功能,关联绘本使用文本输入 进行中
第二阶段 开发资源库完整管理功能 待开发
第三阶段 重构资源选择逻辑,整合课程包与资源库 待开发

详见 开发进展记录.md 资源库开发策略部分。


现在让我开始创建具体的项目文件...