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

如何修改API端点以正确识别参会者最早共同空闲时段?

查找多人最早共同空闲时段的API问题修复

问题背景

开发一个API端点,接收人员ID列表和指定日期作为查询参数,从CSV读取可用时段数据,筛选出选定人员的时段,最终返回他们的最早共同空闲时段。当前实现存在错误:例如查询人员9和39在2023-07-18的空闲时段时,API错误返回"11:00",但实际最早共同空闲时段应为"13:00"——因为11:00时人员9仍处于9:00-13:00的占用状态。

现有代码的核心问题

  1. 日期匹配逻辑错误

    • 对于D(每日)类型的时段,未检查记录的date是否与查询日期完全匹配,导致所有D类型记录都被纳入占用时段,无论日期是否符合。
    • W(每周)类型的判断仅通过天数差取模7,未验证两个日期是否为同一星期几,且未确保查询日期在起始日期之后的重复周期内。
    • M(每月)类型的weekCount函数未定义,逻辑完全不可靠。
  2. 未合并用户的重叠/连续占用时段
    代码仅将所有用户的占用时段简单排序,未对单个用户的重叠时段进行合并(例如用户9在2023-07-18有8:00-8:30和9:00-13:00,用户39有9:00-11:00,这些时段合并后应为8:00-13:00的连续占用区间),导致空闲间隔判断完全错误。

  3. 空闲时段判断逻辑错误

    • 未从当天工作时间起始点开始检查空闲,直接跳过了早间可能的空闲时段。
    • 错误地将所有用户的占用时段混排后找间隔,未确保这些间隔是所有用户共同的空闲时段——某个间隔可能只是部分用户空闲,并非全部。
    • 存在浏览器端代码window.location.reload(),在Node.js环境中执行会直接报错。

修正后的完整代码

const fs = require('fs');
const csv = require('csv-parser');

// 辅助函数:将时间字符串(如"9:00")转换为分钟数
const timeToMinutes = (timeStr) => {
  const [hours, minutes] = timeStr.split(':').map(Number);
  return hours * 60 + (minutes || 0);
};

// 辅助函数:将分钟数转换为时间字符串(如540 -> "9:00")
const minutesToTime = (minutes) => {
  const hours = Math.floor(minutes / 60);
  const mins = minutes % 60;
  return `${hours}:${mins.toString().padStart(2, '0')}`;
};

// 辅助函数:合并单个用户的重叠/连续占用时段
const mergeOccupiedSlots = (slots) => {
  if (slots.length === 0) return [];
  
  // 按时段开始时间排序
  const sortedSlots = slots.sort((a, b) => a.startMinutes - b.startMinutes);
  const merged = [sortedSlots[0]];
  
  for (let i = 1; i < sortedSlots.length; i++) {
    const last = merged[merged.length - 1];
    const current = sortedSlots[i];
    
    // 如果当前时段与最后一个合并时段重叠或连续,合并它们
    if (current.startMinutes <= last.endMinutes) {
      last.endMinutes = Math.max(last.endMinutes, current.endMinutes);
    } else {
      merged.push(current);
    }
  }
  
  return merged;
};

// 辅助函数:判断日期是否符合重复规则
const isDateMatch = (recordDate, queryDate, interval) => {
  const record = new Date(recordDate);
  const query = new Date(queryDate);
  
  // 确保两个日期是同一天(时分秒忽略)
  const isSameDay = record.setHours(0,0,0,0) === query.setHours(0,0,0,0);
  if (interval === 'D') {
    return isSameDay;
  }
  
  // 检查查询日期是否在记录日期之后
  if (query < record) return false;
  
  if (interval === 'W') {
    // 每周重复:检查两个日期是否为同一星期几(周日为0,周一为1...)
    return record.getDay() === query.getDay();
  }
  
  if (interval === 'M') {
    // 每月重复:检查日期是否为同一天(如每月18号)
    return record.getDate() === query.getDate();
  }
  
  return false;
};

app.get('/checkAvailability', (req, res) => {
  try {
    const { ids, date } = req.query;
    if (!ids || !date) {
      return res.status(400).json({ error: '缺少必要参数:ids 和 date' });
    }
    const selectedIds = ids.split(',');
    const results = [];

    fs.createReadStream('4busy.csv')
      .pipe(csv())
      .on('data', (data) => results.push(data))
      .on('end', () => {
        // 1. 按用户分组,过滤出指定日期下的有效占用时段
        const userOccupiedSlots = {};
        selectedIds.forEach(id => {
          userOccupiedSlots[id] = [];
        });
        
        results.forEach(availability => {
          const personId = availability.personId;
          if (!selectedIds.includes(personId)) return;
          
          // 验证日期是否符合重复规则
          if (!isDateMatch(availability.date, date, availability.interval.toUpperCase())) {
            return;
          }
          
          // 转换为分钟数便于计算
          const startMinutes = timeToMinutes(availability.start);
          const endMinutes = timeToMinutes(availability.end);
          
          userOccupiedSlots[personId].push({ startMinutes, endMinutes });
        });
        
        // 2. 合并每个用户的重叠时段
        Object.keys(userOccupiedSlots).forEach(id => {
          userOccupiedSlots[id] = mergeOccupiedSlots(userOccupiedSlots[id]);
        });
        
        // 3. 定义工作时间范围(可根据需求调整)
        const workStart = timeToMinutes('7:00'); // 早上7点开始
        const workEnd = timeToMinutes('18:00'); // 下午6点结束
        
        // 4. 查找所有用户的共同空闲时段,取最早的1小时区间
        let earliestAvailable = null;
        // 从工作开始时间遍历,检查每个可能的1小时区间
        for (let current = workStart; current + 60 <= workEnd; current += 15) { // 每15分钟检查一次
          const intervalEnd = current + 60;
          let isAllFree = true;
          
          // 检查所有用户在该区间是否都空闲
          for (const id of selectedIds) {
            const slots = userOccupiedSlots[id];
            for (const slot of slots) {
              // 如果当前区间与用户的某个占用时段重叠,则该区间不可用
              if (!(intervalEnd <= slot.startMinutes || current >= slot.endMinutes)) {
                isAllFree = false;
                break;
              }
            }
            if (!isAllFree) break;
          }
          
          if (isAllFree) {
            earliestAvailable = minutesToTime(current);
            break; // 找到最早的就停止遍历
          }
        }
        
        // 整理返回数据
        if (earliestAvailable) {
          // 可选:整理所有用户的占用时段用于返回
          const formattedOccupiedSlots = [];
          Object.keys(userOccupiedSlots).forEach(id => {
            userOccupiedSlots[id].forEach(slot => {
              formattedOccupiedSlots.push({
                personId: id,
                slot: `${minutesToTime(slot.startMinutes)} - ${minutesToTime(slot.endMinutes)}`
              });
            });
          });
          res.json({ 
            isAvailable: true, 
            earliestAvailableTime: earliestAvailable,
            occupiedSlots: formattedOccupiedSlots
          });
        } else {
          res.json({ isAvailable: false, message: '当天无共同空闲时段' });
        }
      });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});

代码说明

  1. 日期匹配逻辑:通过isDateMatch函数正确处理D/W/M三种重复规则,确保只有符合查询日期的时段才会被纳入占用数据。
  2. 时段合并:使用mergeOccupiedSlots函数合并单个用户的重叠/连续时段,避免重复计算占用区间。
  3. 空闲时段查找:从工作时间起始点开始,逐段检查每个可能的1小时区间是否为所有用户共同空闲,找到最早的区间后立即返回,保证效率。
  4. 错误处理:补充了参数校验和服务器错误处理,移除了浏览器端无效代码。

内容的提问来源于stack exchange,提问作者Michał Owsiejew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:27:33