如何修改API端点以正确识别参会者最早共同空闲时段?
查找多人最早共同空闲时段的API问题修复
问题背景
开发一个API端点,接收人员ID列表和指定日期作为查询参数,从CSV读取可用时段数据,筛选出选定人员的时段,最终返回他们的最早共同空闲时段。当前实现存在错误:例如查询人员9和39在2023-07-18的空闲时段时,API错误返回"11:00",但实际最早共同空闲时段应为"13:00"——因为11:00时人员9仍处于9:00-13:00的占用状态。
现有代码的核心问题
日期匹配逻辑错误
- 对于
D(每日)类型的时段,未检查记录的date是否与查询日期完全匹配,导致所有D类型记录都被纳入占用时段,无论日期是否符合。 W(每周)类型的判断仅通过天数差取模7,未验证两个日期是否为同一星期几,且未确保查询日期在起始日期之后的重复周期内。M(每月)类型的weekCount函数未定义,逻辑完全不可靠。
- 对于
未合并用户的重叠/连续占用时段
代码仅将所有用户的占用时段简单排序,未对单个用户的重叠时段进行合并(例如用户9在2023-07-18有8:00-8:30和9:00-13:00,用户39有9:00-11:00,这些时段合并后应为8:00-13:00的连续占用区间),导致空闲间隔判断完全错误。空闲时段判断逻辑错误
- 未从当天工作时间起始点开始检查空闲,直接跳过了早间可能的空闲时段。
- 错误地将所有用户的占用时段混排后找间隔,未确保这些间隔是所有用户共同的空闲时段——某个间隔可能只是部分用户空闲,并非全部。
- 存在浏览器端代码
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: '服务器内部错误' }); } });
代码说明
- 日期匹配逻辑:通过
isDateMatch函数正确处理D/W/M三种重复规则,确保只有符合查询日期的时段才会被纳入占用数据。 - 时段合并:使用
mergeOccupiedSlots函数合并单个用户的重叠/连续时段,避免重复计算占用区间。 - 空闲时段查找:从工作时间起始点开始,逐段检查每个可能的1小时区间是否为所有用户共同空闲,找到最早的区间后立即返回,保证效率。
- 错误处理:补充了参数校验和服务器错误处理,移除了浏览器端无效代码。
内容的提问来源于stack exchange,提问作者Michał Owsiejew
相关产品推荐
相关产品推荐

