EE308FZ - 5th Assignment - PoopCare Sprint Essays - Backend Group Sprint Log2

832302122李炳言 2025-12-21 22:28:07
Course of the AssignmentEE308FZ Software Engineering
Assignment RequirementsAssignment 5 - Alpha Sprint (Backend Group Sprint Log)
Objectives of This Assignment记录后端组在冲刺阶段的 API 接口调试、Bug 修复、登录安全优化与数据库功能完善进展
Other References阿里巴巴Java开发手册终极版v1.3.0、微信小程序开发文档、Swagger 官方文档、bcrypt 加密文档、Express.js开发指南、MySQL参考手册、JWT认证规范
Project/Group NameGroup 2 —— PoopCare——Backend Group
Backend Group Members李炳言、苏子妍、王洛森

目录

  1. 当期冲刺概况
  2. 项目燃尽图
  3. 运行结果和优化说明
  4. 功能优化详情
    优化点 1:API 文档路由配置(公开访问适配)
    优化点 2:前后端路由匹配修复(复数 / 单数端点统一)
    优化点 3:getHealthRecords 函数参数匹配修复
    优化点 4:登录功能安全增强(密码加密与验证)
    优化点 5:MySQL 连接池实现与连接测试
    优化点 6:数据库清空工具开发
  5. 未来规划

一、当期冲刺概况

  • 冲刺时间段:12月20日-12月21日
  • 当期核心任务:

1.API 接口调试与 Bug 修复(文档路由保护异常、路由端点不匹配等)
2.登录功能安全优化(密码加密存储、验证机制完善)
3.数据库连接池实现与连接测试
4.数据库清空工具开发(支持交互式数据清理)
5.前端 API 请求配置适配(端口与 URL 硬编码)

  • 当期任务完成情况:全部完成

二、项目燃尽图

本期冲刺计划工时30h(优化点1预计3h,优化点2预计2h,优化点3预计4h,优化点4预计8h,优化点5预计5h,优化点6预计8h),实际消耗27h,燃尽图数据如下:

任务阶段计划剩余工时(h)实际剩余工时(h)负责成员阶段说明
冲刺开始前3030所有任务待开展
优化点1完成后2728李炳言计划3h,实际耗时2h
优化点2完成后2526苏子妍计划2h,实际耗时2h
优化点3完成后2122李炳言计划4h,实际耗时4h
优化点4完成后1314苏子妍计划8h,实际耗时8h
优化点5完成后89李炳言计划5h,实际耗时5h
优化点6完成后03王洛森计划8h,实际耗时6h(整体低于总计划)

img

三、运行结果和优化说明

3.1 工具说明

1. Swagger相关工具

  • swagger-jsdoc:从代码注释生成OpenAPI规范文档,支持接口参数、响应格式定义。
  • swagger-ui-express:提供可视化API文档页面,支持在线接口调试与参数校验。

2. 加密工具

  • bcrypt:自适应哈希加密算法,支持盐值随机生成,用于用户密码安全存储,防暴力破解与彩虹表攻击。

3. 数据库工具

  • mysql2/promise:支持Promise API的MySQL驱动,提供连接池功能,优化数据库连接复用与性能。
  • readline:Node.js内置模块,用于实现交互式命令行交互,支撑数据库清空工具的用户操作。

4. 其他工具

  • Express路由中间件:用于API路由配置与权限控制,实现公开路由与受保护路由分离。

3.2 功能优化点总结

优化点编号优化描述耗时核心原因优化方案核心思路
优化点1API文档路由被认证中间件保护,无法公开访问2h路由配置未排除认证中间件,不符合文档公开需求创建Swagger配置,添加独立公开路由,绕过认证保护
优化点2前端复数端点(/health-records)与后端单数路由(/health-record)不匹配2h前端字段名(time/typeIndex等)与后端模型字段名(record_time/shape等)不一致,导致数据验证失败修改前端请求URL,适配后端单数路由格式
优化点3getHealthRecords函数参数与路由传递参数不匹配,导致SQL执行错误4h路由传递多余record_type参数,SQL占位符与参数数量不一致移除多余参数,完善函数参数处理与日志调试
优化点4登录功能缺乏密码加密存储与安全保护机制8h明文密码存在泄露风险,无验证码频率限制用bcrypt加密密码,实现密码验证与60秒验证码限流
优化点5数据库连接管理不完善,无连接池与连接测试功能5h单次连接模式性能开销大,无法快速验证连接状态实现MySQL连接池,配置核心参数,提供连接测试方法
优化点6缺乏便捷数据库清空工具,手动清理需处理外键约束6h手动操作繁琐易出错,无批量清理能力开发交互式工具,支持全量/指定表清空,自动处理外键

###3.3 后端部分代码
我们上传相关修改代码在github的合作库上

img

链接:https://github.com/Jupiter-rids/PoopCare-backend

四、功能优化详情

4.1 优化点1:API文档路由公开配置(解除认证保护)

4.1.1优化描述

核心问题:API文档路由(/api/docs)被项目全局认证中间件保护,非登录状态下无法访问,不符合API文档公开查阅与接口调试的预期需求。
影响范围: - 开发人员接口调试效率 - 前后端对接时的接口查阅便利性

4.1.2问题定位过程

  • 定位工具与方法:路由配置文件分析、中间件执行流程排查
  • 具体问题定位:
  1. app.js中认证中间件对所有/api前缀路由生效,未单独排除文档路由
  2. 缺少Swagger配置文件与独立的文档路由定义

4.1.3 修复方案

  • 修复目标:实现API文档路由公开访问,支持可视化查阅与接口调试
  • 核心修复思路:
  1. 安装swagger-jsdoc和swagger-ui-express依赖
  2. 创建Swagger配置文件,定义API文档规范与基础信息
  3. 在路由文件中添加独立文档路由(/api/docs),绕过认证中间件 4. 测试路由可用性,确保返回200 OK状态码

4.1.4修复对比前后代码对比

修复前:无Swagger配置与公开路由,文档路由被认证保护 修复后(核心代码):

  1. Swagger配置文件(backend/config/swagger.js):
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'PoopCare API文档',
      version: '1.0.0',
      description: 'PoopCare项目后端API接口调试文档'
    },
    servers: [
      {
        url: 'http://localhost:3000/api'
      }
    ]
  },
  apis: ['./routes/*.js', './controllers/*.js'] // 指定API注释所在文件
};
const specs = swaggerJsdoc(options);
module.exports = specs;
  1. 路由配置(backend/routes/index.js):
const router = express.Router();
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('../config/swagger');

// 公开API文档路由,无需认证
router.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

module.exports = router;

4.1.5效果对比

对比项修复前(Sequelize ORM)修复后(mysql2原生SQL)
访问权限需登录认证(401未授权)公开访问(无需登录)
访问地址无独立文档路由http://localhost:3000/api/docs
响应状态401 Unauthorized200 OK
核心功能无可视化文档支持接口列表、参数说明、在线调试

img

4.2 优化点2:前后端路由端点统一(复数/单数格式适配)

4.2.1优化描述

核心问题:前端添加健康记录时使用复数形式API端点(/health-records),但后端路由配置为单数形式(/health-record),导致请求地址不匹配,数据无法正常提交。
影响范围:

  • 健康记录提交功能可用性 - 前后端数据交互一致性

4.2.2问题定位过程

  • 定位工具与方法:前后端代码对比、接口请求日志分析
  • 具体问题定位:
  1. 前端addRecord.vue中请求URL为/health-records(复数)
  2. 后端routes/health-record.js中路由定义为/health-record(单数)
  3. 地址不匹配导致请求返回404错误,数据提交失败

4.2.3修复方案

  • 修复目标:统一前后端路由地址格式,确保数据正常提交
  • 核心修复思路:
    修改前端API请求URL,从复数形式改为单数形式,与后端路由配置保持一致

4.2.4修复对比前后代码对比

修复前代码(前端addRecord.vue):

const submitRecord = async () => {
  try {
    const response = await axios.post(`${$apiBaseUrl}/health-records`, recordData);
    // 后续逻辑...
  } catch (error) {
    console.error('提交失败:', error);
  }
};

修复后代码:

const submitRecord = async () => {
  try {
    const response = await axios.post('http://localhost:3000/api/health-record', recordData);
    // 后续逻辑...
  } catch (error) {
    console.error('提交失败:', error);
  }
};

4.2.5效果对比

对比项修复前修复后
前端请求URL/health-records(复数)/health-record(单数)
后端路由匹配不匹配(404错误)匹配(200成功)
数据提交结果提交失败提交成功
前后端一致性不一致完全一致

4.3 优化点3:getHealthRecords函数参数匹配修复

4.3.1优化描述

核心问题:路由文件调用getHealthRecords函数时传递了多余的record_type参数,而函数未处理该参数,且SQL查询中占位符与实际传递参数数量不匹配,导致执行错误(Error: Incorrect arguments to mysqld_stmt_execute)。
影响范围:

  • 健康记录列表获取功能
  • 后端接口稳定性

    4.3.2问题定位过程

  • 定位工具与方法:代码逻辑分析、日志调试、SQL执行报错排查
  • 具体问题定位:
  1. 路由文件中调用getHealthRecords(userId, { record_type: 'poop' }),传递多余参数
  2. getHealthRecords函数仅定义queryParams参数(start_date、end_date等),未处理record_type
  3. SQL查询中占位符数量与实际参数不匹配,导致执行失败

4.3.3修复方案

  • 修复目标:移除多余参数传递,统一函数参数与SQL占位符数量,确保查询正常执行
  • 核心修复思路:
  1. 修改路由文件,调用函数时仅传递userId和req.query,不传递多余参数
  2. 完善函数参数处理逻辑,添加日志输出便于调试
  3. 确保SQL查询占位符与参数数量严格一致

4.3.4 修复对比(前后代码对比)

修复前(路由文件 backend/routes/health-record.js)

// 错误:传递多余 record_type 参数 
router.get('/', authMiddleware, async (req, res) => { 
  const result = await healthRecordController.getHealthRecords(req.user.id, { record_type: 'poop' }); 
  if (result.success) { 
    res.json(result); 
  } else { 
    res.status(400).json(result); 
  } 
}); 

修复前(函数实现 backend/controllers/healthRecordController.js)

exports.getHealthRecords = async (userId, queryParams) => { 
  try { 
    const { start_date, end_date, page = 1, limit = 20 } = queryParams; 
    const sql = ` 
      SELECT * FROM poop_records 
      WHERE user_id = ? 
      ${start_date ? 'AND time >= ?' : ''} 
      ${end_date ? 'AND time <= ?' : ''} 
      ORDER BY time DESC 
      LIMIT ?, ? 
    `; 
    const params = [userId]; 
    if (start_date) params.push(start_date); 
    if (end_date) params.push(end_date); 
    params.push((page - 1) * limit, limit); 
    const [rows] = await pool.execute(sql, params); 
    return { success: true, data: rows }; 
  } catch (error) { 
    console.error('获取健康记录失败:', error); 
    return { success: false, message: '获取健康记录列表失败' }; 
  } 
}; 

修复后(路由文件 backend/routes/health-record.js)

// 修复:仅传递必要参数 
router.get('/', authMiddleware, async (req, res) => { 
  const result = await healthRecordController.getHealthRecords(req.user.id, req.query); 
  if (result.success) { 
    res.json(result); 
  } else { 
    res.status(400).json(result); 
  } 
}); 

修复后(函数实现 backend/controllers/healthRecordController.js)

exports.getHealthRecords = async (userId, queryParams) => { 
  try { 
    // 添加日志调试,明确参数接收情况 
    console.log('getHealthRecords 参数:', { userId, queryParams }); 
    const { 
      start_date, end_date, page = 1, limit = 20, 
      sort_by = 'time', sort_order = 'desc' 
    } = queryParams; 

    // 处理排序字段白名单,防止 SQL 注入 
    const validSortFields = ['time', 'created_at', 'type_index', 'mood_index']; 
    const sortField = validSortFields.includes(sort_by) ? sort_by : 'time'; 
    const sortDir = sort_order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; 

    // 构建 SQL 查询,确保占位符与参数数量一致 
    const sql = ` 
      SELECT * FROM poop_records 
      WHERE user_id = ? 
      ${start_date ? 'AND time >= ?' : ''} 
      ${end_date ? 'AND time <= ?' : ''} 
      ORDER BY ${sortField} ${sortDir} 
      LIMIT ?, ? 
    `; 
    const params = [userId]; 
    if (start_date) params.push(new Date(start_date)); 
    if (end_date) params.push(new Date(end_date)); 
    params.push((parseInt(page) - 1) * parseInt(limit), parseInt(limit)); 

    console.log('SQL 查询参数:', params); 
    const [rows] = await pool.execute(sql, params); 
    return { success: true, data: rows }; 
  } catch (error) { 
    console.error('获取健康记录失败:', error); 
    return { success: false, message: '获取健康记录列表失败', error: error.message }; 
  } 
}; 

4.3.5 效果对比

对比项修复前修复后
参数传递传递多余 record_type 参数仅传递必要的 query 参数
日志调试无日志,难以排查问题输出参数与 SQL 信息,便于调试
SQL 执行占位符与参数不匹配(400 错误)匹配正常,执行成功(200)
功能支持仅支持固定查询条件支持日期筛选、分页、排序,功能更完善

4.4 优化点 4:登录功能安全增强(密码加密与账户保护)

4.4.1 优化描述

核心问题:原登录功能存在安全隐患,具体包括:密码可能以明文形式存储、缺乏严格的密码验证流程、验证码发送无频率限制,易遭受恶意攻击。
影响范围:

  • 用户账户安全
  • 系统数据安全性
  • 用户信任度

4.4.2 问题定位过程

定位工具与方法:用户模型代码分析、登录流程梳理
具体问题定位:

  • User 模型中未对密码进行加密处理,存在明文存储风险
  • 登录功能仅验证用户存在性,未校验密码有效性
  • 验证码发送无时间间隔限制,可能遭受暴力请求攻击

4.4.3 修复方案

修复目标:实现密码安全存储、严格密码验证与账户保护机制
核心修复思路:

  • 使用 bcrypt 算法对用户密码进行加密存储,配置 10 轮盐值强度
  • 实现密码验证实例方法,登录时通过 bcrypt.compare 比对密码
  • 增加验证码发送频率限制,默认 60 秒内仅允许发送一次
  • 优化错误提示,明确区分“用户不存在”与“密码错误”

    4.4.4 修复对比(前后代码对比)

修复前(用户模型backend/models/User.js)

// 可能的明文密码存储
async create(userData) {
  try {
    const sql = `
      INSERT INTO users (id, phone, nickname, password, created_at)
      VALUES (?, ?, ?, ?, ?)
    `;
    const params = [uuidv4(), userData.phone, userData.nickname, userData.password, new Date()];
    await pool.execute(sql, params);
    return { success: true };
  } catch (error) {
    console.error('创建用户失败:', error);
    return { success: false };
  }
}

修复前(认证控制器backend/controllers/authController.js)

// 简单登录逻辑,无密码验证
async function login(phone, password) {
  try {
    const [user] = await pool.execute('SELECT * FROM users WHERE phone = ?', [phone]);
    if (!user.length) {
      return { success: false, message: '登录失败' };
    }
    // 直接返回登录成功,未验证密码
    return { success: true, data: { user: user[0], token: generateToken(user[0].id) } };
  } catch (error) {
    console.error('登录失败:', error);
    return { success: false, message: '登录失败' };
  }
}

修复后(用户模型backend/models/User.js)

const bcrypt = require('bcrypt');
const config = require('../config/config');

// 密码加密存储
async create(userData) {
  try {
    const id = uuidv4();
    const now = new Date();
    
    // 加密密码,盐值强度从配置文件读取
    let hashedPassword = userData.password;
    if (hashedPassword) {
      hashedPassword = await bcrypt.hash(hashedPassword, config.bcrypt.saltRounds || 10);
    }
    
    const sql = `
      INSERT INTO users (
        id, phone, nickname, password, email, avatar, gender, age, status, created_at, updated_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;
    
    const params = [
      id,
      userData.phone,
      userData.nickname,
      hashedPassword,
      userData.email || null,
      userData.avatar || null,
      userData.gender || 'unknown',
      userData.age || null,
      'active',
      now,
      now
    ];
    
    await pool.execute(sql, params);
    return { success: true, data: { id } };
  } catch (error) {
    console.error('创建用户失败:', error);
    return { success: false, message: '创建用户失败', error: error.message };
  }
}

// 密码验证实例方法
async validPassword(password) {
  return await bcrypt.compare(password, this.password);
}

// 静态方法:根据手机号查找用户
static async findOne({ where }) {
  const [rows] = await pool.execute('SELECT * FROM users WHERE phone = ?', [where.phone]);
  if (rows.length) {
    const user = rows[0];
    // 绑定验证方法
    user.validPassword = this.prototype.validPassword;
    return user;
  }
  return null;
}

修复后(认证控制器backend/controllers/authController.js)

// 安全登录逻辑
async function login(phone, password) {
  try {
    // 查找用户
    const user = await User.findOne({ where: { phone } });
    if (!user) {
      return { success: false, message: '用户不存在', code: 401 };
    }
    
    // 验证密码
    const isPasswordValid = await user.validPassword(password);
    if (!isPasswordValid) {
      return { success: false, message: '密码错误', code: 401 };
    }
    
    // 更新最后登录时间
    await pool.execute(
      'UPDATE users SET last_login_at = ? WHERE id = ?',
      [new Date(), user.id]
    );
    
    // 生成JWT令牌
    const token = generateToken(user.id);
    
    return { success: true, message: '登录成功', data: {
      user: {
        id: user.id,
        phone: user.phone,
        nickname: user.nickname,
        status: user.status
      },
      token
    } };
  } catch (error) {
    console.error('登录失败:', error);
    return { success: false, message: '登录失败,请稍后重试', code: 500, error: error.message };
  }
}

// 验证码发送频率限制
async function canSendNewVerifyCode(phone, type = 'register') {
  try {
    const [rows] = await pool.execute(`
      SELECT * FROM verify_codes 
      WHERE phone = ? AND type = ? AND created_at > ?
      ORDER BY created_at DESC
    `, [phone, type, new Date(Date.now() - (config.verifyCode?.resendInterval || 60000))]);
    
    return rows.length === 0;
  } catch (error) {
    console.error('检查验证码发送频率错误:', error);
    return false;
  }
}

4.4.5 效果对比

对比项修复前修复后
密码存储可能明文存储,风险高bcrypt加密存储,防泄露
密码验证无验证流程基于bcrypt.compare的安全验证
账户保护无频率限制,易遭恶意攻击验证码60秒内仅能发送一次
错误提示模糊提示“登录失败”明确提示“用户不存在”/“密码错误”
安全性高(自适应哈希+频率限制+错误防护)

4.5 优化点5:MySQL连接池实现与连接测试

4.5.1 优化描述

核心问题:原数据库连接采用单次连接模式,频繁创建与销毁连接导致性能开销大,且缺乏快速验证连接状态的方法,开发调试时无法快速判断数据库可用性。
影响范围:

  • 数据库操作性能
  • 开发调试效率
  • 系统稳定性

4.5.2 问题定位过程

  • 定位工具与方法:数据库操作代码分析、性能测试
  • 具体问题定位:
    1. 无连接池管理,每次数据库操作都创建新连接
    2. 连接参数配置不完整,缺少字符集、校对规则等关键配置
    3. 无连接测试方法,无法快速验证数据库是否正常连接

4.5.3 修复方案

  • 修复目标:实现MySQL连接池管理,优化连接性能,提供连接测试功能
  • 核心修复思路:
    1. 使用mysql2/promise创建连接池,配置最大连接数、字符集等核心参数
    2. 提供testConnection方法,快速验证数据库连接状态
    3. 启用命名占位符,提升SQL语句可读性与维护性

4.5.4 修复对比(前后代码对比)

修复前:无连接池配置,单次连接模式(示例)

// 可能的单次连接方式
const mysql = require('mysql2');

async function query(sql, params) {
  const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '123456',
    database: 'poopcare'
  });
  
  return new Promise((resolve, reject) => {
    connection.query(sql, params, (err, results) => {
      connection.end();
      if (err) reject(err);
      else resolve(results);
    });
  });
}

修复后(backend/config/database.js)

const mysql = require('mysql2/promise');
const config = require('./config');

// 创建MySQL连接池
const pool = mysql.createPool({
  host: config.database.host,         // 主机地址(从配置文件读取)
  port: config.database.port,         // 端口号
  user: config.database.username,     // 用户名
  password: config.database.password, // 密码
  database: config.database.database, // 数据库名
  charset: 'utf8mb4',                 // 支持emoji的字符集
  collation: 'utf8mb4_unicode_ci',    // 校对规则
  waitForConnections: true,           // 无可用连接时等待
  connectionLimit: config.database.pool?.max || 5, // 最大连接数
  queueLimit: 0,                      // 无连接请求队列限制
  namedPlaceholders: true             // 启用命名占位符(:param)
});

// 数据库连接测试方法
const testConnection = async () => {
  try {
    const connection = await pool.getConnection();
    console.log('数据库连接测试成功!');
    connection.release(); // 释放连接回连接池
    return true;
  } catch (error) {
    console.error('数据库连接测试失败:', error);
    return false;
  }
};

// 导出连接池与测试方法
module.exports = { pool, testConnection };

4.5.5 效果对比

对比项修复前修复后
连接管理单次连接,频繁创建销毁连接池管理,复用连接
性能开销高(连接创建销毁耗时)低(连接复用,减少开销)
连接参数基础配置,可能缺失关键参数完整配置(字符集、校对规则等)
测试功能无连接测试方法提供testConnection,快速验证连接
SQL可读性仅支持?占位符支持命名占位符,可读性更高

img

4.6 优化点6:数据库清空工具开发(交互式数据清理)

4.6.1 优化描述

核心问题:项目维护过程中需要清理测试数据或冗余数据,但手动操作需逐表处理外键约束,步骤繁琐且易出错,缺乏批量、便捷的清空工具。
影响范围:

  • 开发维护效率
  • 数据清理安全性
  • 数据一致性

4.6.2 问题定位过程

  • 定位工具与方法:数据库表结构分析、外键约束排查
  • 具体问题定位:
    1. 表之间存在外键关联(如poop_records关联users),直接TRUNCATE失败
    2. 手动清空需临时禁用外键约束,操作步骤多
    3. 无批量清空能力,重复操作效率低

4.6.3 修复方案

  • 修复目标:开发交互式数据库清空工具,支持安全、高效的批量数据清理
  • 核心修复思路:
    1. 定义需清空的核心表列表(按外键依赖顺序排列)
    2. 实现表数据清空逻辑,自动禁用/启用外键约束
    3. 提供交互式命令行界面,支持"清空所有表"或"清空指定表"
    4. 支持非交互式模式,通过命令行参数快速执行清理

4.6.4 修复对比(前后代码对比)

修复前:无专用工具,手动清空(示例SQL):

-- 手动清空需执行多个命令,且需处理外键
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM poop_records;
DELETE FROM drink_records;
DELETE FROM users;
SET FOREIGN_KEY_CHECKS = 1;

修复后(backend/clear-database.js):

const { pool } = require('./config/database');
const readline = require('readline');
const { argv } = require('process');

// 需清空数据的核心表列表(按依赖顺序排列)
const TABLES_TO_CLEAR = [
  'poop_records',
  'drink_records',
  'feedbacks',
  'verify_codes',
  'users'
];

// 清空单表数据
async function clearTableData(tableName) {
  try {
    // 临时禁用外键约束,避免关联错误
    await pool.execute(`SET FOREIGN_KEY_CHECKS = 0`);
    
    // 优先使用TRUNCATE(效率更高),失败则使用DELETE
    try {
      await pool.execute(`TRUNCATE TABLE ${tableName}`);
      console.log(`✓ 成功清空表 ${tableName} 的数据(TRUNCATE)`);
    } catch (truncateError) {
      await pool.execute(`DELETE FROM ${tableName}`);
      console.log(`✓ 成功清空表 ${tableName} 的数据(DELETE)`);
    }
    
    // 重新启用外键约束
    await pool.execute(`SET FOREIGN_KEY_CHECKS = 1`);
    return true;
  } catch (error) {
    // 确保异常时仍启用外键约束
    await pool.execute(`SET FOREIGN_KEY_CHECKS = 1`);
    console.error(`✗ 清空表 ${tableName} 数据失败:`, error.message);
    return false;
  }
}

// 交互式界面
function createInteractiveInterface() {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  
  console.log('\n⚠️ 警告:此操作将清空指定表的所有数据,且无法恢复!');
  console.log(`支持清空的表:${TABLES_TO_CLEAR.join(', ')}\n`);
  
  rl.question('请选择操作:\n1. 清空所有表\n2. 清空指定表\n输入选项(1/2):', async (choice) => {
    if (choice === '1') {
      console.log('\n开始清空所有表数据...');
      for (const table of TABLES_TO_CLEAR) {
        await clearTableData(table);
      }
    } else if (choice === '2') {
      rl.question(`请输入要清空的表名(多个用逗号分隔,可选:${TABLES_TO_CLEAR.join(', ')}):`, async (tableNames) => {
        const tables = tableNames.split(',').map(t => t.trim()).filter(t => TABLES_TO_CLEAR.includes(t));
        
        if (tables.length === 0) {
          console.log('✗ 未选择有效表名');
          rl.close();
          return;
        }
        
        console.log(`\n开始清空表 ${tables.join(', ')} 数据...`);
        for (const table of tables) {
          await clearTableData(table);
        }
        rl.close();
      });
    } else {
      console.log('✗ 无效选项');
      rl.close();
      return;
    }
    rl.close();
    process.exit(0);
  });
}

// 执行入口
(async () => {
  // 测试数据库连接
  const isConnected = await pool.getConnection().then(conn => {
    conn.release();
    return true;
  }).catch(() => false);
  
  if (!isConnected) {
    console.error('✗ 数据库连接失败,无法执行清空操作');
    process.exit(1);
  }
  
  // 非交互式模式(通过命令行参数)
  if (argv.length > 2) {
    const cmd = argv[2];
    
    if (cmd === 'all') {
      console.log('开始清空所有表数据...');
      for (const table of TABLES_TO_CLEAR) {
        await clearTableData(table);
      }
    } else if (cmd === 'table' && argv.length > 3) {
      const tableNames = argv[3].split(',');
      const validTables = tableNames.filter(t => TABLES_TO_CLEAR.includes(t));
      
      for (const table of validTables) {
        await clearTableData(table);
      }
    } else {
      console.log('无效命令参数');
      console.log('用法:');
      console.log(' 清空所有表:node clear-database.js all');
      console.log(' 清空指定表:node clear-database.js table 表名1,表名2');
    }
    process.exit(0);
  }
  
  // 交互式模式
  createInteractiveInterface();
})();

4.6.5 效果对比

对比项修复前修复后
操作方式手动执行SQL命令命令行工具(交互式/非交互式)
外键处理需手动禁用/启用自动处理外键约束
操作效率逐表执行,效率低批量处理,支持全量/指定表清空
安全性易误操作,无提示警告提示,仅支持指定表清空
复用性无复用性,重复操作一次开发,多次使用,支持脚本调用

五、测试验证结果

我们通过命令行测试、接口调试工具等方式验证了所有优化功能的正确性,测试结果如下:

=== 开始功能测试 ===
1. API文档路由测试...
测试命令:Invoke-WebRequest -Uri http://localhost:3000/api/docs -Method GET
响应结果:StatusCode: 200, StatusDescription: OK
测试结论:API文档路由公开访问正常,可视化页面可正常查看与调试

2. 前后端路由匹配测试...
前端请求URL:http://localhost:3000/api/health-record
提交数据:{ "typeIndex": 2, "moodIndex": 1, "time": "2025-12-20T10:00:00Z" }
响应结果:{ "success": true, "record": { "id": 3, "user_id": 3, "type_index": 2, ... } }
测试结论:路由匹配正常,健康记录提交成功

3. 健康记录列表获取测试...
请求URL:http://localhost:3000/api/health-record?page=1&limit=10
响应结果:{ "success": true, "data": [ { "id": 3, "time": "2025-12-20T10:00:00Z", ... } ] }
测试结论:getHealthRecords函数参数匹配正常,查询成功

4. 登录功能安全测试...
注册数据:{ "phone": "13800138001", "password": "Test123456", "nickname": "test_user2" }
登录请求(正确密码):{ "success": true, "message": "登录成功", "data": { "user": {...}, "token": "..." } }
登录请求(错误密码):{ "success": false, "message": "密码错误", "code": 401 }
验证码限流测试:60秒内重复发送,返回"无法重复发送"提示
测试结论:密码加密存储与验证功能正常,账户保护机制生效

5. 数据库连接测试...
执行命令:node config/database.js
输出结果:数据库连接测试成功!
测试结论:连接池配置正常,数据库连接可用

6. 数据库清空工具测试...
执行命令:node clear-database.js
交互操作:选择"清空所有表"
输出结果:
 ✓ 成功清空表 poop_records 的数据(TRUNCATE)
 ✓ 成功清空表 drink_records 的数据(TRUNCATE)
 ✓ 成功清空表 feedbacks 的数据(TRUNCATE)
 ✓ 成功清空表 verify_codes 的数据(TRUNCATE)
 ✓ 成功清空表 users 的数据(TRUNCATE)
测试结论:工具可正常清空数据,外键约束处理正常

=== 测试完成 ===

并且我们也通过前端HBuilderX录入数据:

img

在成功连接的数据库中,我们也可以看到刚刚手动录入的两次数据,证明切实可用

img

测试结果显示:

  1. 所有优化功能均正常工作,无功能异常或报错
  2. API文档可公开访问,接口调试便捷;前后端数据交互一致
  3. 登录功能安全性显著增强,密码存储与验证符合安全规范
  4. 数据库连接池与清空工具运行稳定,提升开发与维护效率
  5. 所有核心功能(记录提交、查询、登录、数据清理)均满足预期需求

六、未来规划

1. 功能扩展

  • 完善API文档:补充所有接口的响应示例与错误码说明,支持接口分组展示
  • 新增数据备份功能:开发数据库定时备份工具,支持手动备份与历史备份恢复
  • 扩展登录方式:支持短信验证码登录、第三方登录,提升用户体验

2. 性能优化

  • 数据库索引优化:为user_id、time等常用查询字段添加索引,提升大数据量查询速度
  • 接口缓存策略:使用Redis缓存高频访问接口数据(如健康统计、用户信息),减少数据库压力
  • 异步处理优化:将日志记录、数据统计等非实时操作改为异步执行,提升接口响应速度

3. 安全性增强

  • 完善JWT令牌机制:实现令牌刷新、过期提醒功能,增加令牌黑名单机制
  • 接口权限细化:基于用户角色(普通用户/管理员)分配接口访问权限,防止越权操作
  • 输入验证强化:对所有API输入参数进行严格校验,防止SQL注入等恶意攻击

4. 可维护性提升

  • 代码规范化:完善代码注释与文档,统一代码风格
  • 自动化测试:编写单元测试与接口测试脚本,集成CI/CD流程,实现自动化测试与部署
  • 错误监控:集成错误监控工具,实时捕获系统异常并告警

5. 兼容性扩展

  • 多环境配置:支持开发、测试、生产环境的配置分离,通过环境变量切换配置
  • 数据库兼容:适配不同版本MySQL,支持数据库迁移功能,便于版本升级
  • 跨域优化:完配置,支持更多前端域名访问,适配多端部署场景
...全文
73 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
01、数据简介 规模以上工业企业,是指年主营业务收入达到一定规模的工业法人单位。这一标准由国家统计局制定,旨在通过统一口径筛选出对工业经济具有显著贡献的“核心企业”,为政策制定、经济监测和学术研究提供精准数据支撑。 数据名称:地级市-规模以上工业企业相关数据 数据年份:2000-2024年 02、相关数据 原始数据:年份 省份 城市 省份代码 城市代码 规模以上工业企业单位数(个) 规模以上工业增加值增速(%) 规模以上工业企业单位数_内资企业(个) 规模以上工业企业单位数_港澳台商投资企业(个) 规模以上工业企业单位数_外商投资企业(个) 规模以上工业亏损企业单位数(个) 插值:年份 省份 城市 省份代码 城市代码 规模以上工业企业单位数(个) 规模以上工业企业单位数(个)_线性插值 规模以上工业企业单位数(个)_回归填补 规模以上工业增加值增速(%) 规模以上工业增加值增速(%)_线性插值 规模以上工业增加值增速(%)_回归填补 规模以上工业企业单位数_内资企业(个) 规模以上工业企业单位数_内资企业(个)_线性插值 规模以上工业企业单位数_内资企业(个)_回归填补 规模以上工业企业单位数_港澳台商投资企业(个) 规模以上工业企业单位数_港澳台商投资企业(个)_线性插值 规模以上工业企业单位数_港澳台商投资企业(个)_回归填补 规模以上工业企业单位数_外商投资企业(个) 规模以上工业企业单位数_外商投资企业(个)_线性插值 规模以上工业企业单位数_外商投资企业(个)_回归填补 规模以上工业亏损企业单位数(个) 规模以上工业亏损企业单位数(个)_线性插值 规模以上工业亏损企业单位数(个)_回归填补

164

社区成员

发帖
与我相关
我的任务
社区描述
2501_MU_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_LQF
  • 助教_林日臻
  • 朱仕君
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧