You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Sequelize与NodeJS的用户课程数据存储最优方案咨询

用户课程学习数据的高效存储方案

核心结论

别用嵌套数组或者在User/Course表直接塞入关联数据,**新增一张中间表(比如UserCourseProgress)**来存储用户与课程的关联关系及学习数据,这是关系型数据库的标准设计方案,操作更简洁、扩展性更强。

现有方案的问题

  • User表嵌套数组:MySQL本身不支持Sequelize的ARRAY类型(这是PostgreSQL专属特性),而且嵌套数组的查询、更新、维护都极度繁琐——比如修改某门课的学习进度,得先取出整个数组修改再存回去,容易出错,也没法做复杂统计(比如用户完成课程数)。
  • Course表塞用户数据:Course表的职责是存储课程本身的信息(名称、课时列表等),塞入用户数据会导致大量冗余(多个用户选同一门课就得重复存用户信息),违反单一职责原则,后续维护会越来越乱。

推荐的数据库模型设计

1. User表(仅存用户基础信息)

移除课程相关字段,精简核心数据:

const { Sequelize, DataTypes } = require("sequelize");
const { v4: uuidv4 } = require("uuid");

const sequelize = new Sequelize(
  'Tafkeer',
  'root',
  'sb32986710*/Tafkeer',
  {
    host: '127.0.0.1',
    dialect: 'mysql'
  }
);

const User = sequelize.define("tafkeers_users_table", {
  user_id: {
    type: Sequelize.UUID,
    allowNull: false,
    primaryKey: true,
    defaultValue: () => uuidv4()
  },
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  secondName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false
  },
  curr_status: {
    type: DataTypes.STRING,
    allowNull: false
  },
  token: {
    type: DataTypes.STRING,
    allowNull: false
  },
  release_date: {
    type: DataTypes.DATEONLY,
  }
});

module.exports = User;

2. Course表(仅存课程基础信息)

修正原模型的变量命名错误(原代码误将Course定义为User),移除用户相关字段,存储课程本身属性:

const { Sequelize, DataTypes } = require("sequelize");
const { v4: uuidv4 } = require("uuid");

const sequelize = new Sequelize(
  'Tafkeer',
  'root',
  'sb32986710*/Tafkeer',
  {
    host: '127.0.0.1',
    dialect: 'mysql'
  }
);

const Course = sequelize.define("tafkeers_courses_table", {
  course_id: {
    type: Sequelize.UUID,
    allowNull: false,
    primaryKey: true,
    defaultValue: () => uuidv4()
  },
  course_name: {
    type: DataTypes.STRING,
    allowNull: false
  },
  curr_status: {
    type: DataTypes.STRING,
    allowNull: false
  },
  release_date: {
    type: DataTypes.DATEONLY,
  },
  lessons: {
    // MySQL不支持ARRAY,改用JSON存储课时列表,格式示例:[{"lesson_id":"xxx","title":"xxx"}, ...]
    type: DataTypes.JSON,
    allowNull: false
  },
});

module.exports = Course;

3. UserCourseProgress中间表(存储用户-课程关联及学习数据)

这是核心表,专门存储每个用户的每门课的学习进度、完成课时、暂停时间等数据:

const { Sequelize, DataTypes } = require("sequelize");
const { v4: uuidv4 } = require("uuid");
const User = require('./User.model');
const Course = require('./Course.model');

const sequelize = new Sequelize(
  'Tafkeer',
  'root',
  'sb32986710*/Tafkeer',
  {
    host: '127.0.0.1',
    dialect: 'mysql'
  }
);

const UserCourseProgress = sequelize.define("tafkeers_user_course_progress", {
  progress_id: {
    type: Sequelize.UUID,
    allowNull: false,
    primaryKey: true,
    defaultValue: () => uuidv4()
  },
  user_id: {
    type: Sequelize.UUID,
    allowNull: false,
    references: {
      model: User,
      key: 'user_id'
    }
  },
  course_id: {
    type: Sequelize.UUID,
    allowNull: false,
    references: {
      model: Course,
      key: 'course_id'
    }
  },
  completed_lesson_ids: {
    // 用JSON存储已完成的课时ID列表
    type: DataTypes.JSON,
    defaultValue: []
  },
  current_progress: {
    // 存储当前学习的课时ID或进度百分比
    type: DataTypes.STRING,
    allowNull: false
  },
  pause_time: {
    type: DataTypes.DATE,
    allowNull: true
  },
  enroll_date: {
    type: DataTypes.DATE,
    defaultValue: Sequelize.NOW
  }
});

// 建立多对多关联关系
User.belongsToMany(Course, { through: UserCourseProgress, foreignKey: 'user_id' });
Course.belongsToMany(User, { through: UserCourseProgress, foreignKey: 'course_id' });

module.exports = UserCourseProgress;

常用操作示例

用户报名课程

const enrollCourse = async (userId, courseId) => {
  await UserCourseProgress.create({
    user_id: userId,
    course_id: courseId,
    current_progress: 'lesson_1', // 默认从第一课时开始
    completed_lesson_ids: []
  });
};

更新用户学习进度

const updateProgress = async (userId, courseId, completedLessonId, pauseTime) => {
  await UserCourseProgress.update(
    {
      completed_lesson_ids: Sequelize.fn('JSON_ARRAY_APPEND', Sequelize.col('completed_lesson_ids'), '$', completedLessonId),
      current_progress: completedLessonId,
      pause_time: pauseTime
    },
    { where: { user_id: userId, course_id: courseId } }
  );
};

查询用户已报名的所有课程及进度

const getUserCourses = async (userId) => {
  return await User.findByPk(userId, {
    include: [{
      model: Course,
      through: { attributes: ['current_progress', 'completed_lesson_ids', 'pause_time', 'enroll_date'] }
    }]
  });
};

方案优势

  • 职责清晰:每个表只负责核心数据,符合数据库设计的单一职责原则。
  • 操作简洁:增删改查通过Sequelize的关联查询和模型方法实现,无需手动处理嵌套数组。
  • 扩展性强:后续新增学习时长、课程评分等字段,直接在中间表添加即可,不影响User和Course表。
  • 数据安全:通过外键约束保证数据一致性,避免无效的用户-课程关联。

内容的提问来源于stack exchange,提问作者White Hat Hacker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 22:35:24