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

Express/Ejs/Node/Mongodb 多路由简化及症状、身体部位统计实现

问题根因说明

现有路由的核心问题是使用了回调式的MongoDB查询,res.render 属于同步执行代码,会在三个异步查询的回调触发前就执行,最终渲染的页面无法拿到真实的用户业务数据。

分步实现方案

1. 业务统计函数开发

针对SymptomModel数据集的统计需求,封装独立纯函数,方便复用、测试:

/**
 * 计算症状数据集的统计指标
 * @param {Array} symptoms - 近30天的用户症状记录数组
 * @returns {Object} 统计结果
 */
function calculateSymptomStats(symptoms) {
  // 空数据边界处理,避免除零、空值报错
  if (!symptoms.length) {
    return {
      avgPainLevel: 0,
      mostCommonBodyPart: '暂无记录'
    }
  }

  // 计算平均疼痛等级:求和后除以总记录数,保留1位小数
  const totalPain = symptoms.reduce((sum, item) => sum + (item.painLevel || 0), 0)
  const avgPainLevel = (totalPain / symptoms.length).toFixed(1)

  // 统计最常记录的身体部位
  const bodyPartCount = {}
  symptoms.forEach(item => {
    const part = item.bodyPart || '未填写'
    bodyPartCount[part] = (bodyPartCount[part] || 0) + 1
  })
  // 按出现次数降序排序,取频次最高的部位
  const sortedParts = Object.entries(bodyPartCount).sort((a, b) => b[1] - a[1])
  const mostCommonBodyPart = sortedParts[0][0]

  return { avgPainLevel, mostCommonBodyPart }
}

2. 优化后的/dashboard路由实现

  • 将路由回调改为async函数,用await等待异步查询完成
  • 三个模型的查询无依赖关系,用Promise.all并行执行,减少整体等待时间
  • 所有异步操作包裹在try/catch中做错误兜底,避免服务崩溃
  • 所有数据获取、统计完成后,再触发页面渲染
// 保留原有引入的moment、三个Model、鉴权中间件等依赖
app.get('/dashboard', isLoggedIn, async (req, res) => {
  try {
    // 计算近30天的时间阈值,直接传Date对象避免字符串格式、时区匹配问题
    const thirtyDaysAgo = moment().subtract(30, 'days').toDate()
    const userQuery = {
      postedBy: req.user._id,
      created: { $gte: thirtyDaysAgo }
    }

    // 并行执行三个模型的查询,等待全部返回结果
    const [symptoms, treatments, doctors] = await Promise.all([
      SymptomModel.find(userQuery).lean(), // lean()直接返回普通JS对象,减少Mongoose实例的性能开销
      TreatmentModel.find(userQuery).lean(),
      DoctorModel.find(userQuery).lean()
    ])

    // 调用统计函数处理症状数据
    const symptomStats = calculateSymptomStats(symptoms)

    // 所有数据准备完成后再渲染页面,将全部结果传给Ejs模板
    res.render('dashboard.ejs', {
      user: req.user.firstName,
      rangeStart: thirtyDaysAgo,
      symptoms,
      treatments,
      doctors,
      avgPainLevel: symptomStats.avgPainLevel,
      mostCommonBodyPart: symptomStats.mostCommonBodyPart
    })

  } catch (err) {
    // 统一错误处理,记录日志后返回错误页面
    console.error('Dashboard route error:', err)
    res.status(500).render('error.ejs', { message: '页面数据加载失败' })
  }
})

补充优化点:如果症状数据量较大,不需要把全量数据拉到内存里计算统计值,可以直接用MongoDB的聚合管道在数据库端完成平均疼痛、高频部位的计算,减少网络传输和服务端计算开销。

相关学习知识点参考
  • Node.js事件循环基础:理解回调式异步代码与同步代码的执行顺序差异,掌握异步操作的触发时机
  • Mongoose查询机制:了解Mongoose的find()方法不传回调时返回的是可await的Query对象,掌握lean()等性能优化用法
  • 异步流程控制:梳理从回调地狱到Promise、再到async/await的演进逻辑,掌握Promise.all处理并行无依赖异步任务、串行await处理有依赖任务的适用场景
  • Express路由错误处理:掌握async路由下的错误捕获逻辑,避免未处理的Promise拒绝导致服务崩溃
  • MongoDB聚合管道:掌握数据库端聚合计算的写法,学会在数据库层完成统计类需求,降低服务端运算压力

内容的提问来源于stack exchange,提问作者sinNombre

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 09:24:03