[Bug] 系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI)
摘要
当用户选择"系统日历"或"两者都用 (Both)"作为数据源时,WinCal 会显示一批完全不存在的虚构日程(标题如"客户电话"、"代码审查"、"团队周会"、"产品评审",日历标为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A")。
这些数据并非来自用户真实的系统日历,而是 MockCalendarService 生成的假数据。根本原因有两个:
- Bug:
CalendarViewModel.CreateSystemService() 在拿不到 WindowsCalendarService 类型时,错误地回退到了 MockCalendarService(本该回退到空的 EmptyCalendarService)。
- 功能缺失:WinCal 只读取 WinRT 的
Windows.ApplicationModel.Appointments(即 Windows 自带"邮件和日历"App 的日历仓库),不支持 Outlook classic(Exchange/MAPI,数据存于 .ost 文件)。大量用户(尤其是企业用户)的真实日程在 Outlook classic 里,WinCal 完全读不到。
两者叠加,造成"用户有真实日程(在 Outlook classic 里)却读不到,反而显示一堆虚构日程"的荒谬现象。
环境
- WinCal 版本:1.3(
scavin/WinCal main 分支,commit 1346dcc)
- 操作系统:Windows 11
- 账户情况:仅使用 Outlook classic 连接公司 Exchange 账户(
@corp.example.com),未在 Windows"邮件和日历"App 中添加任何账户
复现步骤
- 安装并启动 WinCal。
- 打开设置,将"日历数据源"设为 "系统日历" 或 "两者都用 (Both)"。
- 观察日历面板中的事件列表 / 日期圆点。
实际结果:出现一批随机、不存在的日程("客户电话"、"代码审查"、"季度总结"、"1:1 会议"等),且日历来源显示为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A"。
预期结果:要么显示空的、要么显示用户真正的系统日历日程,绝不应显示随机生成的假日程。
根因分析
1. MockCalendarService 兜底 bug(显示虚假日程的直接原因)
Core/Services/MockCalendarService.cs 中的标题、日历名、地点为:
var titles = new[] {
"团队周会", "产品评审", "季度总结", "1:1 会议", "午餐",
"客户电话", "代码审查", "设计讨论", "项目排期", "培训"
};
var calendars = new[] { "Outlook", "Google Calendar", "iCloud" };
// ...
Location: rng.Next(3) < 1 ? "会议室 A" : "",
这些字面量与用户报告的"虚构日程"一一对应。
而 ViewModels/CalendarViewModel.cs 的 CreateSystemService() 中:
private static ICalendarService CreateSystemService()
{
try
{
var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
if (type != null)
return (ICalendarService)Activator.CreateInstance(type)!;
}
catch { } // WinRT not available
return new MockCalendarService(); // ← 错误:生产环境兜底不应返回 Mock
}
当 WindowsCalendarService 类型反射不到(或初始化抛出异常)时,应返回 EmptyCalendarService(空数据),却返回了 MockCalendarService(随机假数据)。MockCalendarService 明确标注为"开发调试用",不应出现在生产代码路径中。
2. 不支持 Outlook classic(真实日程读不到的原因)
WindowsCalendarService 通过 AppointmentManager.RequestStoreAsync(...) 读取的是 WinRT AppointmentStore,即 Windows 自带"邮件和日历"App 的数据仓库。
而 Outlook classic 的日程数据存储于 .ost(MAPI/Exchange)文件,属于另一套完全独立、互不同步的仓库。因此:
- 用户真实日程(Outlook classic / Exchange)→ 存在于
.ost → WinCal 读不到。
- WinCal 能读到的 AppointmentStore → 可能为空 → 触发上面的 Mock 兜底 → 显示假数据。
修复建议
针对 Bug 1(必须修复)
将 CreateSystemService() 的兜底返回从 MockCalendarService 改为 EmptyCalendarService:
private static ICalendarService CreateSystemService()
{
try
{
var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
if (type != null)
return (ICalendarService)Activator.CreateInstance(type)!;
}
catch { }
return new EmptyCalendarService(); // 修正:拿不到系统日历时返回空,而非造假数据
}
同时建议:MockCalendarService 应仅通过编译开关(如 #if DEBUG)或显式配置启用,避免任何生产环境误用。
针对功能缺失 2(增强请求)
考虑增加对 Outlook classic 日历的读取支持,可通过以下方式之一:
- Outlook COM Interop:通过
Outlook.Application + Namespace.GetDefaultFolder(olFolderCalendar) 读取用户 Exchange/本地日历。
- MAPI(如 MFCMAPI / Redemption 库)直接读取
.ost 中的日历项。
- 或者,在设置界面提供明确的提示:"当前系统日历('邮件和日历'App)为空;若你的日程在 Outlook classic 中,请使用 ICS 订阅或将其账户添加到 Windows'邮件和日历'App"。
至少应在系统日历为空/不可用且未配置任何数据源时,向用户明确提示原因,而不是静默显示 Mock 假数据。
补充证据
通过 PowerShell 调用相同的 WinRT API 可复现:系统 AppointmentStore 中仅有一个空日历(SourceDisplayName=Microsoft 账户,LocalId=b,5,13),近期无任何日程;而用户机器上存在多个 Exchange .ost 文件(真实日程所在),确认两套数据互不相通。
相关代码位置:
Core/Services/MockCalendarService.cs
Core/Services/EmptyCalendarService.cs
ViewModels/CalendarViewModel.cs(CreateSystemService)
Core/Services/WindowsCalendarService.cs
[Bug] 系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI)
摘要
当用户选择"系统日历"或"两者都用 (Both)"作为数据源时,WinCal 会显示一批完全不存在的虚构日程(标题如"客户电话"、"代码审查"、"团队周会"、"产品评审",日历标为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A")。
这些数据并非来自用户真实的系统日历,而是
MockCalendarService生成的假数据。根本原因有两个:CalendarViewModel.CreateSystemService()在拿不到WindowsCalendarService类型时,错误地回退到了MockCalendarService(本该回退到空的EmptyCalendarService)。Windows.ApplicationModel.Appointments(即 Windows 自带"邮件和日历"App 的日历仓库),不支持 Outlook classic(Exchange/MAPI,数据存于.ost文件)。大量用户(尤其是企业用户)的真实日程在 Outlook classic 里,WinCal 完全读不到。两者叠加,造成"用户有真实日程(在 Outlook classic 里)却读不到,反而显示一堆虚构日程"的荒谬现象。
环境
scavin/WinCalmain 分支,commit1346dcc)@corp.example.com),未在 Windows"邮件和日历"App 中添加任何账户复现步骤
实际结果:出现一批随机、不存在的日程("客户电话"、"代码审查"、"季度总结"、"1:1 会议"等),且日历来源显示为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A"。
预期结果:要么显示空的、要么显示用户真正的系统日历日程,绝不应显示随机生成的假日程。
根因分析
1. MockCalendarService 兜底 bug(显示虚假日程的直接原因)
Core/Services/MockCalendarService.cs中的标题、日历名、地点为:这些字面量与用户报告的"虚构日程"一一对应。
而
ViewModels/CalendarViewModel.cs的CreateSystemService()中:当
WindowsCalendarService类型反射不到(或初始化抛出异常)时,应返回EmptyCalendarService(空数据),却返回了MockCalendarService(随机假数据)。MockCalendarService明确标注为"开发调试用",不应出现在生产代码路径中。2. 不支持 Outlook classic(真实日程读不到的原因)
WindowsCalendarService通过AppointmentManager.RequestStoreAsync(...)读取的是 WinRT AppointmentStore,即 Windows 自带"邮件和日历"App 的数据仓库。而 Outlook classic 的日程数据存储于
.ost(MAPI/Exchange)文件,属于另一套完全独立、互不同步的仓库。因此:.ost→ WinCal 读不到。修复建议
针对 Bug 1(必须修复)
将
CreateSystemService()的兜底返回从MockCalendarService改为EmptyCalendarService:同时建议:
MockCalendarService应仅通过编译开关(如#if DEBUG)或显式配置启用,避免任何生产环境误用。针对功能缺失 2(增强请求)
考虑增加对 Outlook classic 日历的读取支持,可通过以下方式之一:
Outlook.Application+Namespace.GetDefaultFolder(olFolderCalendar)读取用户 Exchange/本地日历。.ost中的日历项。至少应在系统日历为空/不可用且未配置任何数据源时,向用户明确提示原因,而不是静默显示 Mock 假数据。
补充证据
通过 PowerShell 调用相同的 WinRT API 可复现:系统
AppointmentStore中仅有一个空日历(SourceDisplayName=Microsoft 账户,LocalId=b,5,13),近期无任何日程;而用户机器上存在多个 Exchange.ost文件(真实日程所在),确认两套数据互不相通。相关代码位置:
Core/Services/MockCalendarService.csCore/Services/EmptyCalendarService.csViewModels/CalendarViewModel.cs(CreateSystemService)Core/Services/WindowsCalendarService.cs