Nodejs+html+mysql实现轻量web应用
教育平台API设计与实现(Node.js + MySQL)
下面我将为您设计一个完整的教育平台API系统,包含用户认证、课程管理、文章管理等功能。
系统架构设计
技术栈
后端: Node.js + Express.js
数据库: MySQL
身份验证: JWT (JSON Web Tokens)
密码加密: bcryptjs
其他: cors, dotenv, nodemon
数据库设计
首先创建数据库表结构:
sql
CREATE DATABASE education_platform; USE education_platform;-- 用户表 CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) UNIQUE NOT NULL,email VARCHAR(100) UNIQUE NOT NULL,password VARCHAR(255) NOT NULL,role ENUM('student', 'teacher', 'admin') DEFAULT 'student',avatar_url VARCHAR(255),created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );-- 课程表 CREATE TABLE courses (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,description TEXT,instructor_id INT NOT NULL,price DECIMAL(10, 2) DEFAULT 0,thumbnail_url VARCHAR(255),category VARCHAR(100),level ENUM('beginner', 'intermediate', 'advanced') DEFAULT 'beginner',duration INT, -- 课程时长(小时)is_published BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (instructor_id) REFERENCES users(id) );-- 文章表 CREATE TABLE articles (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,content LONGTEXT NOT NULL,author_id INT NOT NULL,course_id INT,is_public BOOLEAN DEFAULT TRUE,tags JSON,view_count INT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (author_id) REFERENCES users(id),FOREIGN KEY (course_id) REFERENCES courses(id) );-- 用户课程关联表(报名记录) CREATE TABLE user_courses (id INT AUTO_INCREMENT PRIMARY KEY,user_id INT NOT NULL,course_id INT NOT NULL,enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,progress INT DEFAULT 0, -- 学习进度百分比completed BOOLEAN DEFAULT FALSE,FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (course_id) REFERENCES courses(id),UNIQUE KEY unique_enrollment (user_id, course_id) );-- API表 CREATE TABLE apis (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100) NOT NULL,endpoint VARCHAR(255) NOT NULL,method ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH') DEFAULT 'GET',description TEXT,parameters JSON,response_example JSON,requires_auth BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
API实现代码
1. 项目初始化
创建项目并安装依赖:
bash
mkdir education-platform-api cd education-platform-api npm init -y npm install express mysql2 bcryptjs jsonwebtoken cors dotenv npm install -D nodemon
2. 项目结构
text
education-platform-api/ ├── config/ │ └── database.js ├── controllers/ │ ├── authController.js │ ├── userController.js │ ├── courseController.js │ ├── articleController.js │ └── apiController.js ├── middleware/ │ ├── auth.js │ └── validation.js ├── routes/ │ ├── auth.js │ ├── users.js │ ├── courses.js │ ├── articles.js │ └── apis.js ├── .env ├── app.js └── package.json
3. 环境配置 (.env)
env
PORT=3000 DB_HOST=localhost DB_USER=root DB_PASSWORD=your_password DB_NAME=education_platform JWT_SECRET=your_jwt_secret_key JWT_EXPIRES_IN=7d
4. 数据库连接配置 (config/database.js)
javascript
const mysql = require('mysql2'); require('dotenv').config();const connection = mysql.createConnection({host: process.env.DB_HOST,user: process.env.DB_USER,password: process.env.DB_PASSWORD,database: process.env.DB_NAME });connection.connect((err) => {if (err) {console.error('数据库连接失败: ' + err.stack);return;}console.log('数据库连接成功,连接ID: ' + connection.threadId); });module.exports = connection;
5. 认证中间件 (middleware/auth.js)
javascript
const jwt = require('jsonwebtoken'); require('dotenv').config();const authenticateToken = (req, res, next) => {const authHeader = req.headers['authorization'];const token = authHeader && authHeader.split(' ')[1];if (!token) {return res.status(401).json({ success: false, message: '访问令牌缺失' });}jwt.verify(token, process.env.JWT_SECRET, (err, user) => {if (err) {return res.status(403).json({ success: false, message: '访问令牌无效' });}req.user = user;next();}); };const authorize = (...roles) => {return (req, res, next) => {if (!roles.includes(req.user.role)) {return res.status(403).json({ success: false, message: '没有权限执行此操作' });}next();}; };module.exports = { authenticateToken, authorize };
6. 认证控制器 (controllers/authController.js)
javascript
const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const db = require('../config/database'); require('dotenv').config();// 用户注册 const register = (req, res) => {const { username, email, password, role } = req.body;// 验证必填字段if (!username || !email || !password) {return res.status(400).json({success: false,message: '用户名、邮箱和密码是必填项'});}// 检查用户是否已存在db.query('SELECT id FROM users WHERE email = ? OR username = ?',[email, username],async (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length > 0) {return res.status(409).json({success: false,message: '用户名或邮箱已存在'});}try {// 加密密码const saltRounds = 10;const hashedPassword = await bcrypt.hash(password, saltRounds);// 创建用户db.query('INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)',[username, email, hashedPassword, role || 'student'],(err, results) => {if (err) {return res.status(500).json({success: false,message: '用户创建失败'});}// 生成JWT令牌const token = jwt.sign({ id: results.insertId, username, email, role: role || 'student' },process.env.JWT_SECRET,{ expiresIn: process.env.JWT_EXPIRES_IN });res.status(201).json({success: true,message: '用户注册成功',data: {token,user: {id: results.insertId,username,email,role: role || 'student'}}});});} catch (error) {res.status(500).json({success: false,message: '服务器错误'});}}); };// 用户登录 const login = (req, res) => {const { email, password } = req.body;if (!email || !password) {return res.status(400).json({success: false,message: '邮箱和密码是必填项'});}// 查找用户db.query('SELECT * FROM users WHERE email = ?',[email],async (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(401).json({success: false,message: '邮箱或密码错误'});}const user = results[0];// 验证密码const isPasswordValid = await bcrypt.compare(password, user.password);if (!isPasswordValid) {return res.status(401).json({success: false,message: '邮箱或密码错误'});}// 生成JWT令牌const token = jwt.sign({ id: user.id, username: user.username, email: user.email, role: user.role },process.env.JWT_SECRET,{ expiresIn: process.env.JWT_EXPIRES_IN });res.json({success: true,message: '登录成功',data: {token,user: {id: user.id,username: user.username,email: user.email,role: user.role,avatar_url: user.avatar_url}}});}); };// 获取当前用户信息 const getCurrentUser = (req, res) => {db.query('SELECT id, username, email, role, avatar_url, created_at FROM users WHERE id = ?',[req.user.id],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,data: results[0]});}); };module.exports = { register, login, getCurrentUser };
7. 用户控制器 (controllers/userController.js)
javascript
const db = require('../config/database');// 获取所有用户 const getAllUsers = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;// 获取用户总数db.query('SELECT COUNT(*) as total FROM users', (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页用户数据db.query('SELECT id, username, email, role, avatar_url, created_at FROM users LIMIT ? OFFSET ?',[limit, offset],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {users: results,pagination: {page,limit,total,totalPages}}});});}); };// 获取单个用户 const getUserById = (req, res) => {const userId = req.params.id;db.query('SELECT id, username, email, role, avatar_url, created_at FROM users WHERE id = ?',[userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,data: results[0]});}); };// 更新用户信息 const updateUser = (req, res) => {const userId = req.params.id;const { username, avatar_url } = req.body;// 检查是否有权限更新(只能更新自己的信息或管理员)if (req.user.id != userId && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此用户信息'});}db.query('UPDATE users SET username = ?, avatar_url = ? WHERE id = ?',[username, avatar_url, userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库更新错误'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,message: '用户信息更新成功'});}); };// 删除用户 const deleteUser = (req, res) => {const userId = req.params.id;// 只有管理员可以删除用户if (req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除用户'});}db.query('DELETE FROM users WHERE id = ?',[userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库删除错误'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,message: '用户删除成功'});}); };module.exports = { getAllUsers, getUserById, updateUser, deleteUser };
8. 课程控制器 (controllers/courseController.js)
javascript
const db = require('../config/database');// 获取所有课程 const getAllCourses = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;const { category, level, is_published } = req.query;let query = `SELECT c.*, u.username as instructor_name FROM courses c JOIN users u ON c.instructor_id = u.id`;let countQuery = 'SELECT COUNT(*) as total FROM courses c';let queryParams = [];let countParams = [];let conditions = [];// 添加筛选条件if (category) {conditions.push('c.category = ?');queryParams.push(category);countParams.push(category);}if (level) {conditions.push('c.level = ?');queryParams.push(level);countParams.push(level);}if (is_published !== undefined) {conditions.push('c.is_published = ?');queryParams.push(is_published === 'true');countParams.push(is_published === 'true');}if (conditions.length > 0) {query += ' WHERE ' + conditions.join(' AND ');countQuery += ' WHERE ' + conditions.join(' AND ');}query += ' LIMIT ? OFFSET ?';queryParams.push(limit, offset);// 获取课程总数db.query(countQuery, countParams, (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页课程数据db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {courses: results,pagination: {page,limit,total,totalPages}}});});}); };// 获取单个课程 const getCourseById = (req, res) => {const courseId = req.params.id;db.query(`SELECT c.*, u.username as instructor_name FROM courses c JOIN users u ON c.instructor_id = u.id WHERE c.id = ?`,[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}res.json({success: true,data: results[0]});}); };// 创建课程 const createCourse = (req, res) => {const { title, description, price, thumbnail_url, category, level, duration } = req.body;const instructorId = req.user.id;if (!title || !description) {return res.status(400).json({success: false,message: '课程标题和描述是必填项'});}db.query('INSERT INTO courses (title, description, instructor_id, price, thumbnail_url, category, level, duration) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',[title, description, instructorId, price, thumbnail_url, category, level, duration],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程创建失败'});}res.status(201).json({success: true,message: '课程创建成功',data: {id: results.insertId}});}); };// 更新课程 const updateCourse = (req, res) => {const courseId = req.params.id;const { title, description, price, thumbnail_url, category, level, duration, is_published } = req.body;// 检查是否是课程创建者或管理员db.query('SELECT instructor_id FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}if (results[0].instructor_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此课程'});}db.query('UPDATE courses SET title = ?, description = ?, price = ?, thumbnail_url = ?, category = ?, level = ?, duration = ?, is_published = ? WHERE id = ?',[title, description, price, thumbnail_url, category, level, duration, is_published, courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程更新失败'});}res.json({success: true,message: '课程更新成功'});});}); };// 删除课程 const deleteCourse = (req, res) => {const courseId = req.params.id;// 检查是否是课程创建者或管理员db.query('SELECT instructor_id FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}if (results[0].instructor_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除此课程'});}db.query('DELETE FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程删除失败'});}res.json({success: true,message: '课程删除成功'});});}); };// 报名课程 const enrollCourse = (req, res) => {const courseId = req.params.id;const userId = req.user.id;// 检查课程是否存在db.query('SELECT id FROM courses WHERE id = ? AND is_published = TRUE',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在或未发布'});}// 检查是否已报名db.query('SELECT id FROM user_courses WHERE user_id = ? AND course_id = ?',[userId, courseId],(err, enrollmentResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (enrollmentResults.length > 0) {return res.status(409).json({success: false,message: '您已经报名此课程'});}// 报名课程db.query('INSERT INTO user_courses (user_id, course_id) VALUES (?, ?)',[userId, courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '报名失败'});}res.status(201).json({success: true,message: '报名成功'});});});}); };module.exports = {getAllCourses,getCourseById,createCourse,updateCourse,deleteCourse,enrollCourse };
9. 文章控制器 (controllers/articleController.js)
javascript
const db = require('../config/database');// 获取所有文章 const getAllArticles = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;const { course_id, is_public, author_id } = req.query;let query = `SELECT a.*, u.username as author_name, c.title as course_titleFROM articles a LEFT JOIN users u ON a.author_id = u.idLEFT JOIN courses c ON a.course_id = c.id`;let countQuery = 'SELECT COUNT(*) as total FROM articles a';let queryParams = [];let countParams = [];let conditions = [];// 添加筛选条件if (course_id) {conditions.push('a.course_id = ?');queryParams.push(course_id);countParams.push(course_id);}if (is_public !== undefined) {conditions.push('a.is_public = ?');queryParams.push(is_public === 'true');countParams.push(is_public === 'true');}if (author_id) {conditions.push('a.author_id = ?');queryParams.push(author_id);countParams.push(author_id);}// 非管理员只能查看公开文章或自己的文章if (req.user.role !== 'admin') {conditions.push('(a.is_public = TRUE OR a.author_id = ?)');queryParams.push(req.user.id);countParams.push(req.user.id);}if (conditions.length > 0) {query += ' WHERE ' + conditions.join(' AND ');countQuery += ' WHERE ' + conditions.join(' AND ');}query += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?';queryParams.push(limit, offset);// 获取文章总数db.query(countQuery, countParams, (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页文章数据db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {articles: results,pagination: {page,limit,total,totalPages}}});});}); };// 获取单个文章 const getArticleById = (req, res) => {const articleId = req.params.id;let query = `SELECT a.*, u.username as author_name, c.title as course_titleFROM articles a LEFT JOIN users u ON a.author_id = u.idLEFT JOIN courses c ON a.course_id = c.idWHERE a.id = ?`;let queryParams = [articleId];// 非管理员只能查看公开文章或自己的文章if (req.user.role !== 'admin') {query += ' AND (a.is_public = TRUE OR a.author_id = ?)';queryParams.push(req.user.id);}db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在或没有访问权限'});}// 增加浏览次数if (results[0].is_public || results[0].author_id === req.user.id) {db.query('UPDATE articles SET view_count = view_count + 1 WHERE id = ?',[articleId]);}res.json({success: true,data: results[0]});}); };// 创建文章 const createArticle = (req, res) => {const { title, content, course_id, is_public, tags } = req.body;const authorId = req.user.id;if (!title || !content) {return res.status(400).json({success: false,message: '文章标题和内容是必填项'});}db.query('INSERT INTO articles (title, content, author_id, course_id, is_public, tags) VALUES (?, ?, ?, ?, ?, ?)',[title, content, authorId, course_id, is_public || true, JSON.stringify(tags)],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章创建失败'});}res.status(201).json({success: true,message: '文章创建成功',data: {id: results.insertId}});}); };// 更新文章 const updateArticle = (req, res) => {const articleId = req.params.id;const { title, content, course_id, is_public, tags } = req.body;// 检查是否是文章作者或管理员db.query('SELECT author_id FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在'});}if (results[0].author_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此文章'});}db.query('UPDATE articles SET title = ?, content = ?, course_id = ?, is_public = ?, tags = ? WHERE id = ?',[title, content, course_id, is_public, JSON.stringify(tags), articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章更新失败'});}res.json({success: true,message: '文章更新成功'});});}); };// 删除文章 const deleteArticle = (req, res) => {const articleId = req.params.id;// 检查是否是文章作者或管理员db.query('SELECT author_id FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在'});}if (results[0].author_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除此文章'});}db.query('DELETE FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章删除失败'});}res.json({success: true,message: '文章删除成功'});});}); };module.exports = {getAllArticles,getArticleById,createArticle,updateArticle,deleteArticle };
10. API控制器 (controllers/apiController.js)
javascript
const db = require('../config/database');// 获取所有API const getAllAPIs = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;// 获取API总数db.query('SELECT COUNT(*) as total FROM apis', (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页API数据db.query('SELECT * FROM apis LIMIT ? OFFSET ?',[limit, offset],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {apis: results,pagination: {page,limit,total,totalPages}}});});}); };// 获取单个API const getAPIById = (req, res) => {const apiId = req.params.id;db.query('SELECT * FROM apis WHERE id = ?',[apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,data: results[0]});}); };// 创建API const createAPI = (req, res) => {const { name, endpoint, method, description, parameters, response_example, requires_auth } = req.body;if (!name || !endpoint) {return res.status(400).json({success: false,message: 'API名称和端点是必填项'});}db.query('INSERT INTO apis (name, endpoint, method, description, parameters, response_example, requires_auth) VALUES (?, ?, ?, ?, ?, ?, ?)',[name, endpoint, method, description, JSON.stringify(parameters), JSON.stringify(response_example), requires_auth],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API创建失败'});}res.status(201).json({success: true,message: 'API创建成功',data: {id: results.insertId}});}); };// 更新API const updateAPI = (req, res) => {const apiId = req.params.id;const { name, endpoint, method, description, parameters, response_example, requires_auth } = req.body;db.query('UPDATE apis SET name = ?, endpoint = ?, method = ?, description = ?, parameters = ?, response_example = ?, requires_auth = ? WHERE id = ?',[name, endpoint, method, description, JSON.stringify(parameters), JSON.stringify(response_example), requires_auth, apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API更新失败'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,message: 'API更新成功'});}); };// 删除API const deleteAPI = (req, res) => {const apiId = req.params.id;db.query('DELETE FROM apis WHERE id = ?',[apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API删除失败'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,message: 'API删除成功'});}); };module.exports = {getAllAPIs,getAPIById,createAPI,updateAPI,deleteAPI };
11. 路由配置
创建各个模块的路由文件:
认证路由 (routes/auth.js)
javascript
const express = require('express'); const router = express.Router(); const { register, login, getCurrentUser } = require('../controllers/authController'); const { authenticateToken } = require('../middleware/auth');router.post('/register', register); router.post('/login', login); router.get('/me', authenticateToken, getCurrentUser);module.exports = router;
用户路由 (routes/users.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllUsers, getUserById, updateUser, deleteUser } = require('../controllers/userController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', authenticateToken, authorize('admin'), getAllUsers); router.get('/:id', authenticateToken, getUserById); router.put('/:id', authenticateToken, updateUser); router.delete('/:id', authenticateToken, authorize('admin'), deleteUser);module.exports = router;
课程路由 (routes/courses.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllCourses, getCourseById, createCourse, updateCourse, deleteCourse, enrollCourse } = require('../controllers/courseController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', getAllCourses); router.get('/:id', getCourseById); router.post('/', authenticateToken, authorize('teacher', 'admin'), createCourse); router.put('/:id', authenticateToken, updateCourse); router.delete('/:id', authenticateToken, deleteCourse); router.post('/:id/enroll', authenticateToken, enrollCourse);module.exports = router;
文章路由 (routes/articles.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllArticles, getArticleById, createArticle, updateArticle, deleteArticle } = require('../controllers/articleController'); const { authenticateToken } = require('../middleware/auth');router.get('/', authenticateToken, getAllArticles); router.get('/:id', authenticateToken, getArticleById); router.post('/', authenticateToken, createArticle); router.put('/:id', authenticateToken, updateArticle); router.delete('/:id', authenticateToken, deleteArticle);module.exports = router;
API路由 (routes/apis.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllAPIs, getAPIById, createAPI, updateAPI, deleteAPI } = require('../controllers/apiController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', getAllAPIs); router.get('/:id', getAPIById); router.post('/', authenticateToken, authorize('admin'), createAPI); router.put('/:id', authenticateToken, authorize('admin'), updateAPI); router.delete('/:id', authenticateToken, authorize('admin'), deleteAPI);module.exports = router;
12. 主应用文件 (app.js)
javascript
const express = require('express'); const cors = require('cors'); require('dotenv').config();const authRoutes = require('./routes/auth'); const userRoutes = require('./routes/users'); const courseRoutes = require('./routes/courses'); const articleRoutes = require('./routes/articles'); const apiRoutes = require('./routes/apis');const app = express(); const PORT = process.env.PORT || 3000;// 中间件 app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true }));// 路由 app.use('/api/auth', authRoutes); app.use('/api/users', userRoutes); app.use('/api/courses', courseRoutes); app.use('/api/articles', articleRoutes); app.use('/api/apis', apiRoutes);// 健康检查端点 app.get('/health', (req, res) => {res.json({ success: true, message: '教育平台API运行正常',timestamp: new Date().toISOString()}); });// 404处理 app.use('*', (req, res) => {res.status(404).json({success: false,message: 'API端点不存在'}); });// 错误处理中间件 app.use((err, req, res, next) => {console.error(err.stack);res.status(500).json({success: false,message: '服务器内部错误'}); });// 启动服务器 app.listen(PORT, () => {console.log(`教育平台API服务器运行在端口 ${PORT}`); });module.exports = app;
13. Package.json 脚本配置
json
{"name": "education-platform-api","version": "1.0.0","description": "教育平台API服务","main": "app.js","scripts": {"start": "node app.js","dev": "nodemon app.js","test": "echo \"Error: no test specified\" && exit 1"},"dependencies": {"express": "^4.18.2","mysql2": "^3.6.0","bcryptjs": "^2.4.3","jsonwebtoken": "^9.0.2","cors": "^2.8.5","dotenv": "^16.3.1"},"devDependencies": {"nodemon": "^3.0.1"},"keywords": ["education", "api", "nodejs", "mysql"],"author": "Your Name","license": "MIT" }
API使用说明
1. 认证相关API
POST /api/auth/register
- 用户注册POST /api/auth/login
- 用户登录GET /api/auth/me
- 获取当前用户信息
2. 用户管理API
GET /api/users
- 获取所有用户(仅管理员)GET /api/users/:id
- 获取指定用户信息PUT /api/users/:id
- 更新用户信息DELETE /api/users/:id
- 删除用户(仅管理员)
3. 课程管理API
GET /api/courses
- 获取所有课程GET /api/courses/:id
- 获取指定课程详情POST /api/courses
- 创建课程(教师和管理员)PUT /api/courses/:id
- 更新课程信息DELETE /api/courses/:id
- 删除课程POST /api/courses/:id/enroll
- 报名课程
4. 文章管理API
GET /api/articles
- 获取所有文章GET /api/articles/:id
- 获取指定文章详情POST /api/articles
- 创建文章PUT /api/articles/:id
- 更新文章DELETE /api/articles/:id
- 删除文章
5. API文档管理
GET /api/apis
- 获取所有API文档GET /api/apis/:id
- 获取指定API文档POST /api/apis
- 创建API文档(仅管理员)PUT /api/apis/:id
- 更新API文档(仅管理员)DELETE /api/apis/:id
- 删除API文档(仅管理员)
部署和运行
安装依赖:
bash
npm install
创建MySQL数据库并导入SQL结构
配置环境变量(.env文件)
启动服务器:
bash
# 开发模式 npm run dev# 生产模式 npm start
安全注意事项
在生产环境中,确保使用强密码和安全的JWT密钥
考虑使用HTTPS加密通信
实施速率限制防止API滥用
定期更新依赖包以修复安全漏洞
对用户输入进行严格的验证和清理
这个API系统提供了教育平台的核心功能,您可以根据需要进一步扩展和优化。
使用nodejs链接mysql编写教育网站api,包含登录注册首页和其他页面api接口
我们正在设计一个教育网站的API,包括登录、注册、首页和其他页面的接口。我们将使用Node.js和MySQL。
根据要求,我们需要设计以下API端点:
用户认证
注册
登录
获取当前用户信息(可能需要)
首页
获取首页数据(可能包括轮播图、推荐课程、最新文章等)
课程相关
获取课程列表
获取课程详情
获取课程分类
文章相关
获取文章列表
获取文章详情
笔记相关
获取笔记列表(公开和私密)
创建笔记
更新笔记
删除笔记
自学页面
获取自学课程列表(免费和收费)
案例页面
获取案例列表
API页面
获取API列表
考虑到时间,我们先设计主要接口,并实现其中一部分。
我们将使用Express框架,并使用mysql2库连接MySQL数据库。
步骤:
初始化项目
安装依赖:express, mysql2, bcryptjs, jsonwebtoken, cors, dotenv等
创建数据库连接
设计数据库表(如果尚未创建)
创建路由和控制器
实现中间件(如认证中间件)
由于代码量较大,我们将分步骤实现。
首先,我们设计数据库表(假设数据库名为education_platform):
用户表(users):
id, username, email, password, avatar, role, created_at, updated_at
课程表(courses):
id, title, description, price, category_id, teacher_id, cover_image, created_at, updated_at
课程分类表(categories):
id, name, description
文章表(articles):
id, title, content, author_id, cover_image, is_public, created_at, updated_at
笔记表(notes):
id, title, content, user_id, course_id, is_public, created_at, updated_at
案例表(cases):
id, title, description, image, link, created_at
API表(apis):
id, name, endpoint, method, description, parameters, example
由于时间关系,我们不会完全实现所有接口,但会给出一个框架,并实现部分核心功能。
我们先从用户注册和登录开始。
注意:密码需要加密存储,使用bcryptjs。
我们使用JWT进行身份验证。
接下来,我们开始编写代码。
教育网站API设计与实现 (Node.js + MySQL)
下面我将设计一个完整的教育网站API系统,包含用户认证、课程管理、文章管理等功能。
系统架构
技术栈
后端: Node.js + Express.js
数据库: MySQL
身份验证: JWT (JSON Web Tokens)
密码加密: bcryptjs
其他: cors, dotenv, nodemon
数据库设计
首先创建数据库表结构:
sql
CREATE DATABASE education_platform; USE education_platform;-- 用户表 CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) UNIQUE NOT NULL,email VARCHAR(100) UNIQUE NOT NULL,password VARCHAR(255) NOT NULL,role ENUM('student', 'teacher', 'admin') DEFAULT 'student',avatar_url VARCHAR(255),created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );-- 课程表 CREATE TABLE courses (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,description TEXT,instructor_id INT NOT NULL,price DECIMAL(10, 2) DEFAULT 0,thumbnail_url VARCHAR(255),category VARCHAR(100),level ENUM('beginner', 'intermediate', 'advanced') DEFAULT 'beginner',duration INT, -- 课程时长(小时)is_published BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (instructor_id) REFERENCES users(id) );-- 文章表 CREATE TABLE articles (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,content LONGTEXT NOT NULL,author_id INT NOT NULL,course_id INT,is_public BOOLEAN DEFAULT TRUE,tags JSON,view_count INT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (author_id) REFERENCES users(id),FOREIGN KEY (course_id) REFERENCES courses(id) );-- 用户课程关联表(报名记录) CREATE TABLE user_courses (id INT AUTO_INCREMENT PRIMARY KEY,user_id INT NOT NULL,course_id INT NOT NULL,enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,progress INT DEFAULT 0, -- 学习进度百分比completed BOOLEAN DEFAULT FALSE,FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (course_id) REFERENCES courses(id),UNIQUE KEY unique_enrollment (user_id, course_id) );-- API表 CREATE TABLE apis (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100) NOT NULL,endpoint VARCHAR(255) NOT NULL,method ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH') DEFAULT 'GET',description TEXT,parameters JSON,response_example JSON,requires_auth BOOLEAN DEFAULT FALSE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
API实现代码
1. 项目初始化
创建项目并安装依赖:
bash
mkdir education-platform-api cd education-platform-api npm init -y npm install express mysql2 bcryptjs jsonwebtoken cors dotenv npm install -D nodemon
2. 项目结构
text
education-platform-api/ ├── config/ │ └── database.js ├── controllers/ │ ├── authController.js │ ├── userController.js │ ├── courseController.js │ ├── articleController.js │ └── apiController.js ├── middleware/ │ ├── auth.js │ └── validation.js ├── routes/ │ ├── auth.js │ ├── users.js │ ├── courses.js │ ├── articles.js │ └── apis.js ├── .env ├── app.js └── package.json
3. 环境配置 (.env)
env
PORT=3000 DB_HOST=localhost DB_USER=root DB_PASSWORD=your_password DB_NAME=education_platform JWT_SECRET=your_jwt_secret_key JWT_EXPIRES_IN=7d
4. 数据库连接配置 (config/database.js)
javascript
const mysql = require('mysql2'); require('dotenv').config();const connection = mysql.createConnection({host: process.env.DB_HOST,user: process.env.DB_USER,password: process.env.DB_PASSWORD,database: process.env.DB_NAME });connection.connect((err) => {if (err) {console.error('数据库连接失败: ' + err.stack);return;}console.log('数据库连接成功,连接ID: ' + connection.threadId); });module.exports = connection;
5. 认证中间件 (middleware/auth.js)
javascript
const jwt = require('jsonwebtoken'); require('dotenv').config();const authenticateToken = (req, res, next) => {const authHeader = req.headers['authorization'];const token = authHeader && authHeader.split(' ')[1];if (!token) {return res.status(401).json({ success: false, message: '访问令牌缺失' });}jwt.verify(token, process.env.JWT_SECRET, (err, user) => {if (err) {return res.status(403).json({ success: false, message: '访问令牌无效' });}req.user = user;next();}); };const authorize = (...roles) => {return (req, res, next) => {if (!roles.includes(req.user.role)) {return res.status(403).json({ success: false, message: '没有权限执行此操作' });}next();}; };module.exports = { authenticateToken, authorize };
6. 认证控制器 (controllers/authController.js)
javascript
const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const db = require('../config/database'); require('dotenv').config();// 用户注册 const register = (req, res) => {const { username, email, password, role } = req.body;// 验证必填字段if (!username || !email || !password) {return res.status(400).json({success: false,message: '用户名、邮箱和密码是必填项'});}// 检查用户是否已存在db.query('SELECT id FROM users WHERE email = ? OR username = ?',[email, username],async (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length > 0) {return res.status(409).json({success: false,message: '用户名或邮箱已存在'});}try {// 加密密码const saltRounds = 10;const hashedPassword = await bcrypt.hash(password, saltRounds);// 创建用户db.query('INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)',[username, email, hashedPassword, role || 'student'],(err, results) => {if (err) {return res.status(500).json({success: false,message: '用户创建失败'});}// 生成JWT令牌const token = jwt.sign({ id: results.insertId, username, email, role: role || 'student' },process.env.JWT_SECRET,{ expiresIn: process.env.JWT_EXPIRES_IN });res.status(201).json({success: true,message: '用户注册成功',data: {token,user: {id: results.insertId,username,email,role: role || 'student'}}});});} catch (error) {res.status(500).json({success: false,message: '服务器错误'});}}); };// 用户登录 const login = (req, res) => {const { email, password } = req.body;if (!email || !password) {return res.status(400).json({success: false,message: '邮箱和密码是必填项'});}// 查找用户db.query('SELECT * FROM users WHERE email = ?',[email],async (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(401).json({success: false,message: '邮箱或密码错误'});}const user = results[0];// 验证密码const isPasswordValid = await bcrypt.compare(password, user.password);if (!isPasswordValid) {return res.status(401).json({success: false,message: '邮箱或密码错误'});}// 生成JWT令牌const token = jwt.sign({ id: user.id, username: user.username, email: user.email, role: user.role },process.env.JWT_SECRET,{ expiresIn: process.env.JWT_EXPIRES_IN });res.json({success: true,message: '登录成功',data: {token,user: {id: user.id,username: user.username,email: user.email,role: user.role,avatar_url: user.avatar_url}}});}); };// 获取当前用户信息 const getCurrentUser = (req, res) => {db.query('SELECT id, username, email, role, avatar_url, created_at FROM users WHERE id = ?',[req.user.id],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,data: results[0]});}); };module.exports = { register, login, getCurrentUser };
7. 用户控制器 (controllers/userController.js)
javascript
const db = require('../config/database');// 获取所有用户 const getAllUsers = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;// 获取用户总数db.query('SELECT COUNT(*) as total FROM users', (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页用户数据db.query('SELECT id, username, email, role, avatar_url, created_at FROM users LIMIT ? OFFSET ?',[limit, offset],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {users: results,pagination: {page,limit,total,totalPages}}});});});};// 获取单个用户const getUserById = (req, res) => {const userId = req.params.id;db.query('SELECT id, username, email, role, avatar_url, created_at FROM users WHERE id = ?',[userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,data: results[0]});});};// 更新用户信息const updateUser = (req, res) => {const userId = req.params.id;const { username, avatar_url } = req.body;// 检查是否有权限更新(只能更新自己的信息或管理员)if (req.user.id != userId && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此用户信息'});}db.query('UPDATE users SET username = ?, avatar_url = ? WHERE id = ?',[username, avatar_url, userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库更新错误'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,message: '用户信息更新成功'});});};// 删除用户const deleteUser = (req, res) => {const userId = req.params.id;// 只有管理员可以删除用户if (req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除用户'});}db.query('DELETE FROM users WHERE id = ?',[userId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库删除错误'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: '用户不存在'});}res.json({success: true,message: '用户删除成功'});});};module.exports = { getAllUsers, getUserById, updateUser, deleteUser };
8. 课程控制器 (controllers/courseController.js)
javascript
const db = require('../config/database');// 获取所有课程 const getAllCourses = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;const { category, level, is_published } = req.query;let query = `SELECT c.*, u.username as instructor_name FROM courses c JOIN users u ON c.instructor_id = u.id`;let countQuery = 'SELECT COUNT(*) as total FROM courses c';let queryParams = [];let countParams = [];let conditions = [];// 添加筛选条件if (category) {conditions.push('c.category = ?');queryParams.push(category);countParams.push(category);}if (level) {conditions.push('c.level = ?');queryParams.push(level);countParams.push(level);}if (is_published !== undefined) {conditions.push('c.is_published = ?');queryParams.push(is_published === 'true');countParams.push(is_published === 'true');}if (conditions.length > 0) {query += ' WHERE ' + conditions.join(' AND ');countQuery += ' WHERE ' + conditions.join(' AND ');}query += ' LIMIT ? OFFSET ?';queryParams.push(limit, offset);// 获取课程总数db.query(countQuery, countParams, (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页课程数据db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {courses: results,pagination: {page,limit,total,totalPages}}});});}); };// 获取单个课程 const getCourseById = (req, res) => {const courseId = req.params.id;db.query(`SELECT c.*, u.username as instructor_name FROM courses c JOIN users u ON c.instructor_id = u.id WHERE c.id = ?`,[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}res.json({success: true,data: results[0]});}); };// 创建课程 const createCourse = (req, res) => {const { title, description, price, thumbnail_url, category, level, duration } = req.body;const instructorId = req.user.id;if (!title || !description) {return res.status(400).json({success: false,message: '课程标题和描述是必填项'});}db.query('INSERT INTO courses (title, description, instructor_id, price, thumbnail_url, category, level, duration) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',[title, description, instructorId, price, thumbnail_url, category, level, duration],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程创建失败'});}res.status(201).json({success: true,message: '课程创建成功',data: {id: results.insertId}});}); };// 更新课程 const updateCourse = (req, res) => {const courseId = req.params.id;const { title, description, price, thumbnail_url, category, level, duration, is_published } = req.body;// 检查是否是课程创建者或管理员db.query('SELECT instructor_id FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}if (results[0].instructor_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此课程'});}db.query('UPDATE courses SET title = ?, description = ?, price = ?, thumbnail_url = ?, category = ?, level = ?, duration = ?, is_published = ? WHERE id = ?',[title, description, price, thumbnail_url, category, level, duration, is_published, courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程更新失败'});}res.json({success: true,message: '课程更新成功'});});}); };// 删除课程 const deleteCourse = (req, res) => {const courseId = req.params.id;// 检查是否是课程创建者或管理员db.query('SELECT instructor_id FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在'});}if (results[0].instructor_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除此课程'});}db.query('DELETE FROM courses WHERE id = ?',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '课程删除失败'});}res.json({success: true,message: '课程删除成功'});});}); };// 报名课程 const enrollCourse = (req, res) => {const courseId = req.params.id;const userId = req.user.id;// 检查课程是否存在db.query('SELECT id FROM courses WHERE id = ? AND is_published = TRUE',[courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '课程不存在或未发布'});}// 检查是否已报名db.query('SELECT id FROM user_courses WHERE user_id = ? AND course_id = ?',[userId, courseId],(err, enrollmentResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (enrollmentResults.length > 0) {return res.status(409).json({success: false,message: '您已经报名此课程'});}// 报名课程db.query('INSERT INTO user_courses (user_id, course_id) VALUES (?, ?)',[userId, courseId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '报名失败'});}res.status(201).json({success: true,message: '报名成功'});});});}); };module.exports = {getAllCourses,getCourseById,createCourse,updateCourse,deleteCourse,enrollCourse };
9. 文章控制器 (controllers/articleController.js)
javascript
const db = require('../config/database');// 获取所有文章 const getAllArticles = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;const { course_id, is_public, author_id } = req.query;let query = `SELECT a.*, u.username as author_name, c.title as course_titleFROM articles a LEFT JOIN users u ON a.author_id = u.idLEFT JOIN courses c ON a.course_id = c.id`;let countQuery = 'SELECT COUNT(*) as total FROM articles a';let queryParams = [];let countParams = [];let conditions = [];// 添加筛选条件if (course_id) {conditions.push('a.course_id = ?');queryParams.push(course_id);countParams.push(course_id);}if (is_public !== undefined) {conditions.push('a.is_public = ?');queryParams.push(is_public === 'true');countParams.push(is_public === 'true');}if (author_id) {conditions.push('a.author_id = ?');queryParams.push(author_id);countParams.push(author_id);}// 非管理员只能查看公开文章或自己的文章if (req.user.role !== 'admin') {conditions.push('(a.is_public = TRUE OR a.author_id = ?)');queryParams.push(req.user.id);countParams.push(req.user.id);}if (conditions.length > 0) {query += ' WHERE ' + conditions.join(' AND ');countQuery += ' WHERE ' + conditions.join(' AND ');}query += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?';queryParams.push(limit, offset);// 获取文章总数db.query(countQuery, countParams, (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页文章数据db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {articles: results,pagination: {page,limit,total,totalPages}}});});); };// 获取单个文章 const getArticleById = (req, res) => {const articleId = req.params.id;let query = `SELECT a.*, u.username as author_name, c.title as course_titleFROM articles a LEFT JOIN users u ON a.author_id = u.idLEFT JOIN courses c ON a.course_id = c.idWHERE a.id = ?`;let queryParams = [articleId];// 非管理员只能查看公开文章或自己的文章if (req.user.role !== 'admin') {query += ' AND (a.is_public = TRUE OR a.author_id = ?)';queryParams.push(req.user.id);}db.query(query, queryParams, (err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在或没有访问权限'});}// 增加浏览次数if (results[0].is_public || results[0].author_id === req.user.id) {db.query('UPDATE articles SET view_count = view_count + 1 WHERE id = ?',[articleId]);}res.json({success: true,data: results[0]});}); };// 创建文章 const createArticle = (req, res) => {const { title, content, course_id, is_public, tags } = req.body;const authorId = req.user.id;if (!title || !content) {return res.status(400).json({success: false,message: '文章标题和内容是必填项'});}db.query('INSERT INTO articles (title, content, author_id, course_id, is_public, tags) VALUES (?, ?, ?, ?, ?, ?)',[title, content, authorId, course_id, is_public || true, JSON.stringify(tags)],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章创建失败'});}res.status(201).json({success: true,message: '文章创建成功',data: {id: results.insertId}});}); };// 更新文章 const updateArticle = (req, res) => {const articleId = req.params.id;const { title, content, course_id, is_public, tags } = req.body;// 检查是否是文章作者或管理员db.query('SELECT author_id FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在'});}if (results[0].author_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限更新此文章'});}db.query('UPDATE articles SET title = ?, content = ?, course_id = ?, is_public = ?, tags = ? WHERE id = ?',[title, content, course_id, is_public, JSON.stringify(tags), articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章更新失败'});}res.json({success: true,message: '文章更新成功'});});}); };// 删除文章 const deleteArticle = (req, res) => {const articleId = req.params.id;// 检查是否是文章作者或管理员db.query('SELECT author_id FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: '文章不存在'});}if (results[0].author_id !== req.user.id && req.user.role !== 'admin') {return res.status(403).json({success: false,message: '没有权限删除此文章'});}db.query('DELETE FROM articles WHERE id = ?',[articleId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '文章删除失败'});}res.json({success: true,message: '文章删除成功'});});});};module.exports = {getAllArticles,getArticleById,createArticle,updateArticle,deleteArticle};
10. API控制器 (controllers/apiController.js)
javascript
const db = require('../config/database');// 获取所有API const getAllAPIs = (req, res) => {const page = parseInt(req.query.page) || 1;const limit = parseInt(req.query.limit) || 10;const offset = (page - 1) * limit;// 获取API总数db.query('SELECT COUNT(*) as total FROM apis', (err, countResults) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}const total = countResults[0].total;const totalPages = Math.ceil(total / limit);// 获取分页API数据db.query('SELECT * FROM apis LIMIT ? OFFSET ?',[limit, offset],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}res.json({success: true,data: {apis: results,pagination: {page,limit,total,totalPages}}});}); }); };// 获取单个API const getAPIById = (req, res) => {const apiId = req.params.id;db.query('SELECT * FROM apis WHERE id = ?',[apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: '数据库查询错误'});}if (results.length === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,data: results[0]});}); };// 创建API const createAPI = (req, res) => {const { name, endpoint, method, description, parameters, response_example, requires_auth } = req.body;if (!name || !endpoint) {return res.status(400).json({success: false,message: 'API名称和端点是必填项'});}db.query('INSERT INTO apis (name, endpoint, method, description, parameters, response_example, requires_auth) VALUES (?, ?, ?, ?, ?, ?, ?)',[name, endpoint, method, description, JSON.stringify(parameters), JSON.stringify(response_example), requires_auth],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API创建失败'});}res.status(201).json({success: true,message: 'API创建成功',data: {id: results.insertId}});}); };// 更新API const updateAPI = (req, res) => {const apiId = req.params.id;const { name, endpoint, method, description, parameters, response_example, requires_auth } = req.body;db.query('UPDATE apis SET name = ?, endpoint = ?, method = ?, description = ?, parameters = ?, response_example = ?, requires_auth = ? WHERE id = ?',[name, endpoint, method, description, JSON.stringify(parameters), JSON.stringify(response_example), requires_auth, apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API更新失败'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,message: 'API更新成功'});}); };// 删除API const deleteAPI = (req, res) => {const apiId = req.params.id;db.query('DELETE FROM apis WHERE id = ?',[apiId],(err, results) => {if (err) {return res.status(500).json({success: false,message: 'API删除失败'});}if (results.affectedRows === 0) {return res.status(404).json({success: false,message: 'API不存在'});}res.json({success: true,message: 'API删除成功'});}); };module.exports = {getAllAPIs,getAPIById,createAPI,updateAPI,deleteAPI };
11. 路由配置
创建各个模块的路由文件:
认证路由 (routes/auth.js)
javascript
const express = require('express'); const router = express.Router(); const { register, login, getCurrentUser } = require('../controllers/authController'); const { authenticateToken } = require('../middleware/auth');router.post('/register', register); router.post('/login', login); router.get('/me', authenticateToken, getCurrentUser);module.exports = router;
用户路由 (routes/users.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllUsers, getUserById, updateUser, deleteUser } = require('../controllers/userController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', authenticateToken, authorize('admin'), getAllUsers); router.get('/:id', authenticateToken, getUserById); router.put('/:id', authenticateToken, updateUser); router.delete('/:id', authenticateToken, authorize('admin'), deleteUser);module.exports = router;
课程路由 (routes/courses.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllCourses, getCourseById, createCourse, updateCourse, deleteCourse, enrollCourse } = require('../controllers/courseController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', getAllCourses); router.get('/:id', getCourseById); router.post('/', authenticateToken, authorize('teacher', 'admin'), createCourse); router.put('/:id', authenticateToken, updateCourse); router.delete('/:id', authenticateToken, deleteCourse); router.post('/:id/enroll', authenticateToken, enrollCourse);module.exports = router;
文章路由 (routes/articles.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllArticles, getArticleById, createArticle, updateArticle, deleteArticle } = require('../controllers/articleController'); const { authenticateToken } = require('../middleware/auth');router.get('/', authenticateToken, getAllArticles); router.get('/:id', authenticateToken, getArticleById); router.post('/', authenticateToken, createArticle); router.put('/:id', authenticateToken, updateArticle); router.delete('/:id', authenticateToken, deleteArticle);module.exports = router;
API路由 (routes/apis.js)
javascript
const express = require('express'); const router = express.Router(); const { getAllAPIs, getAPIById, createAPI, updateAPI, deleteAPI } = require('../controllers/apiController'); const { authenticateToken, authorize } = require('../middleware/auth');router.get('/', getAllAPIs); router.get('/:id', getAPIById); router.post('/', authenticateToken, authorize('admin'), createAPI); router.put('/:id', authenticateToken, authorize('admin'), updateAPI); router.delete('/:id', authenticateToken, authorize('admin'), deleteAPI);module.exports = router;
12. 主应用文件 (app.js)
javascript
const express = require('express'); const cors = require('cors'); require('dotenv').config();const authRoutes = require('./routes/auth'); const userRoutes = require('./routes/users'); const courseRoutes = require('./routes/courses'); const articleRoutes = require('./routes/articles'); const apiRoutes = require('./routes/apis');const app = express(); const PORT = process.env.PORT || 3000;// 中间件 app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true }));// 路由 app.use('/api/auth', authRoutes); app.use('/api/users', userRoutes); app.use('/api/courses', courseRoutes); app.use('/api/articles', articleRoutes); app.use('/api/apis', apiRoutes);// 健康检查端点 app.get('/health', (req, res) => {res.json({ success: true, message: '教育平台API运行正常',timestamp: new Date().toISOString()}); });// 404处理 app.use('*', (req, res) => {res.status(404).json({success: false,message: 'API端点不存在'}); });// 错误处理中间件 app.use((err, req, res, next) => {console.error(err.stack);res.status(500).json({success: false,message: '服务器内部错误'}); });// 启动服务器 app.listen(PORT, () => {console.log(`教育平台API服务器运行在端口 ${PORT}`); });module.exports = app;
13. Package.json 脚本配置
json
{"name": "education-platform-api","version": "1.0.0","description": "教育平台API服务","main": "app.js","scripts": {"start": "node app.js","dev": "nodemon app.js","test": "echo \"Error: no test specified\" && exit 1"},"dependencies": {"express": "^4.18.2","mysql2": "^3.6.0","bcryptjs": "^2.4.3","jsonwebtoken": "^9.0.2","cors": "^2.8.5","dotenv": "^16.3.1"},"devDependencies": {"nodemon": "^3.0.1"},"keywords": ["education", "api", "nodejs", "mysql"],"author": "Your Name","license": "MIT" }
API使用说明
1. 认证相关API
POST /api/auth/register
- 用户注册POST /api/auth/login
- 用户登录GET /api/auth/me
- 获取当前用户信息
2. 用户管理API
GET /api/users
- 获取所有用户(仅管理员)GET /api/users/:id
- 获取指定用户信息PUT /api/users/:id
- 更新用户信息DELETE /api/users/:id
- 删除用户(仅管理员)
3. 课程管理API
GET /api/courses
- 获取所有课程GET /api/courses/:id
- 获取指定课程详情POST /api/courses
- 创建课程(教师和管理员)PUT /api/courses/:id
- 更新课程信息DELETE /api/courses/:id
- 删除课程POST /api/courses/:id/enroll
- 报名课程
4. 文章管理API
GET /api/articles
- 获取所有文章GET /api/articles/:id
- 获取指定文章详情POST /api/articles
- 创建文章PUT /api/articles/:id
- 更新文章DELETE /api/articles/:id
- 删除文章
5. API文档管理
GET /api/apis
- 获取所有API文档GET /api/apis/:id
- 获取指定API文档POST /api/apis
- 创建API文档(仅管理员)PUT /api/apis/:id
- 更新API文档(仅管理员)DELETE /api/apis/:id
- 删除API文档(仅管理员)
部署和运行
安装依赖:
bash
npm install
创建MySQL数据库并导入SQL结构
配置环境变量(.env文件)
启动服务器:
bash
# 开发模式 npm run dev# 生产模式 npm start
安全注意事项
在生产环境中,确保使用强密码和安全的JWT密钥
考虑使用HTTPS加密通信
实施速率限制防止API滥用
定期更新依赖包以修复安全漏洞
对用户输入进行严格的验证和清理
这个API系统提供了教育平台的核心功能,您可以根据需要进一步扩展和优化。