博客项目全栈开发 API接口 第五章:其他功能

前言

这章是后续所有系统功能更新,类似缓存,安全之类的

简单登录日志

创建模型models/LoginLog.js

const mongoose = require('mongoose');

const loginLogSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  status: {
    type: String,
    enum: ['success', 'failed'],
    required: true
  },
  ip: {
    type: String,
    required: true
  },
  timestamp: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true
});

const LoginLog = mongoose.model('LoginLog', loginLogSchema);
module.exports = LoginLog;

修改控制器controller/authController.js

const User = require('../models/User');
const jwt = require('jsonwebtoken');
const LoginLog = require('../models/LoginLog');

// 生成 JWT Token
const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: '30d'
  });
};
// 记录登录日志
const logLoginAttempt = async (user, status, req) => {
  try {
    await LoginLog.create({
      user: user._id,
      status,
      ip: req.ip
    });
  } catch (error) {
    console.error('Failed to create login log:', error);
  }
};

// 用户登录
const login = async (req, res) => {
  try {
    const { email, password } = req.body;

    // 查找用户
    const user = await User.findOne({ email });

    if (!user) {
      await logLoginAttempt(user, 'failed', req);
      return res.status(401).json({ message: '邮箱或密码错误' });
    }

    // 验证密码
    const isMatch = await user.matchPassword(password);
    if (!isMatch) {
      await logLoginAttempt(user, 'failed', req);
      return res.status(401).json({ message: '邮箱或密码错误' });
    }

    // 记录成功登录
    await logLoginAttempt(user, 'success', req);

    // 生成 token
    const token = generateToken(user._id);

    res.json({
      _id: user._id,
      username: user.username,
      email: user.email,
      token
    });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

// 获取用户登录历史
const getLoginHistory = async (req, res) => {
  try {
    const loginLogs = await LoginLog.find({ user: req.user._id })
      .sort({ timestamp: -1 })
      .limit(10)
      .populate('user', 'username email')
      .lean();
      
    res.json(loginLogs);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

module.exports = {
  register,
  login,
  getMe,
  getLoginHistory
};

添加路由

// 导入必要的模块
const express = require('express');
const router = express.Router();
// 导入认证控制器函数
const { register, login, getMe, getLoginHistory } = require('../controllers/authController');
// 导入认证中间件
const { protect } = require('../middleware/auth');
// 导入请求验证中间件
const validateRequest = require('../middleware/validateRequest');
// 导入验证模式
const { registerSchema, loginSchema } = require('../middleware/validationSchemas');

// 注册路由 - 使用验证中间件验证请求数据,然后调用注册控制器
router.post('/register', validateRequest(registerSchema), register);
// 登录路由 - 使用验证中间件验证请求数据,然后调用登录控制器
router.post('/login', validateRequest(loginSchema), login);
// 获取当前用户信息路由 - 需要认证中间件保护,然后调用获取用户信息控制器
router.get('/me', protect, getMe);
// 获取用户登录历史路由 - 需要认证中间件保护,然后调用获取用户登录历史控制器
router.get('/login-history', protect, getLoginHistory);
module.exports = router;

编写API文档

使用Swagger/OpenAPI编写API文档
安装依赖

npm install swagger-jsdoc swagger-ui-express

创建swagger.js

const swaggerJsdoc = require('swagger-jsdoc');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Blog API',
      version: '1.0.0',
      description: '博客系统API文档'
    },
    servers: [
      {
        url: 'http://localhost:3000',
        description: '开发服务器'
      }
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT'
        }
      }
    }
  },
  apis: ['./routes/*.js']
};

module.exports = swaggerJsdoc(options);

app.js中添加Swagger UI

const swaggerUi = require('swagger-ui-express');
const swaggerSpecs = require('./config/swagger');

// ... 其他中间件

// Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));

在路由文件中添加Swagger注释

用户注册/登录auth.js

/**
 * @swagger
 * tags:
 *   name: Auth
 *   description: 用户认证相关接口
 */

/**
 * @swagger
 * /api/auth/register:
 *   post:
 *     tags: [Auth]
 *     summary: 用户注册
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - username
 *               - email
 *               - password
 *             properties:
 *               username:
 *                 type: string
 *                 example: johndoe
 *               email:
 *                 type: string
 *                 format: email
 *                 example: john@example.com
 *               password:
 *                 type: string
 *                 format: password
 *                 example: password123
 *     responses:
 *       201:
 *         description: 注册成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 _id:
 *                   type: string
 *                 username:
 *                   type: string
 *                 email:
 *                   type: string
 *                 token:
 *                   type: string
 *       400:
 *         description: 请求参数错误
 */
router.post('/register', register);

/**
 * @swagger
 * /api/auth/login:
 *   post:
 *     tags: [Auth]
 *     summary: 用户登录
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *                 example: john@example.com
 *               password:
 *                 type: string
 *                 format: password
 *                 example: password123
 *     responses:
 *       200:
 *         description: 登录成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 _id:
 *                   type: string
 *                 username:
 *                   type: string
 *                 email:
 *                   type: string
 *                 token:
 *                   type: string
 *       401:
 *         description: 邮箱或密码错误
 */
router.post('/login', login);

/**
 * @swagger
 * /api/auth/me:
 *   get:
 *     tags: [Auth]
 *     summary: 获取当前用户信息
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: 成功获取用户信息
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 _id:
 *                   type: string
 *                 username:
 *                   type: string
 *                 email:
 *                   type: string
 *       401:
 *         description: 未授权
 */
router.get('/me', protect, getMe);

文章接口post.js

/**
 * @swagger
 * tags:
 *   name: Posts
 *   description: 文章相关接口
 */

/**
 * @swagger
 * /api/posts:
 *   get:
 *     tags: [Posts]
 *     summary: 获取文章列表
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *         description: 页码
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           default: 10
 *         description: 每页数量
 *     responses:
 *       200:
 *         description: 成功获取文章列表
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 posts:
 *                   type: array
 *                   items:
 *                     type: object
 *                     properties:
 *                       _id:
 *                         type: string
 *                       title:
 *                         type: string
 *                       content:
 *                         type: string
 *                       author:
 *                         type: string
 *                       category:
 *                         type: string
 *                 pagination:
 *                   type: object
 *                   properties:
 *                     current:
 *                       type: integer
 *                     total:
 *                       type: integer
 *                     totalRecords:
 *                       type: integer
 *   post:
 *     tags: [Posts]
 *     summary: 创建文章
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - title
 *               - content
 *               - category
 *             properties:
 *               title:
 *                 type: string
 *                 example: 文章标题
 *               content:
 *                 type: string
 *                 example: 文章内容
 *               category:
 *                 type: string
 *                 example: 技术
 *               tags:
 *                 type: array
 *                 items:
 *                   type: string
 *                 example: ["JavaScript", "Node.js"]
 *     responses:
 *       201:
 *         description: 文章创建成功
 *       401:
 *         description: 未授权
 */
router.get('/', getPosts);
router.post('/', protect, createPost);

/**
 * @swagger
 * /api/posts/{id}:
 *   get:
 *     tags: [Posts]
 *     summary: 获取文章详情
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *         description: 文章ID
 *     responses:
 *       200:
 *         description: 成功获取文章详情
 *       404:
 *         description: 文章不存在
 *   put:
 *     tags: [Posts]
 *     summary: 更新文章
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *         description: 文章ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               title:
 *                 type: string
 *               content:
 *                 type: string
 *               category:
 *                 type: string
 *               tags:
 *                 type: array
 *                 items:
 *                   type: string
 *     responses:
 *       200:
 *         description: 文章更新成功
 *       401:
 *         description: 未授权
 *       404:
 *         description: 文章不存在
 *   delete:
 *     tags: [Posts]
 *     summary: 删除文章
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *         description: 文章ID
 *     responses:
 *       200:
 *         description: 文章删除成功
 *       401:
 *         description: 未授权
 *       404:
 *         description: 文章不存在
 */
router.get('/:id', getPost);
router.put('/:id', protect, updatePost);
router.delete('/:id', protect, deletePost);

使用方法:

  1. 启动服务器
  2. 访问 http://localhost:3000/api-docs 查看文档

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值