
gog contacts dedupe 深度解析gogcli 中 Google 通讯录重复联系人发现与合并的实现机制【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcligogcli 的gog contacts dedupe子命令用于在终端中排查并合并 Google 通讯录Google Contacts里的疑似重复联系人。本文基于当前仓库的命令参考页 gog-contacts-dedupe.md 与实现文档 contacts-dedupe.md 展开覆盖完整的命令用法、参数语义、JSON 输出结构与合并流程并结合 internal/cmd/contacts_dedupe.go 和 internal/cmd/contacts_dedupe_apply.go 的源码说明去重分组算法、主联系人选择策略与删除前的 etag 二次校验等安全机制。读完后你可以安全地把该命令用于人工审查与 CI 自动化两条路径。命令定位与默认行为gog contacts dedupe的核心能力是“找出疑似重复的个人联系人可选地执行合并”。其使用形态为gog contacts (contact) dedupe [flags]它属于 gog contacts 命令族命令索引见 docs/commands/README.md。行为上有一个关键默认预览preview是默认模式——不带--apply时命令只输出重复分组与合并计划不触碰任何联系人显式加上--apply后才会执行“合并字段 删除冗余联系人”的变更操作。从源码 ContactsDedupeCmd 的结构体定义可以看到该命令的四个专属参数参数类型默认值含义--matchstringemail,phone参与匹配的字段email、phone、name--max别名--limitint640最多扫描多少个联系人0表示不限--resource[]string无将去重范围限定到精确的联系人资源名people/...可重复指定--apply别名--mergeboolfalse执行合并并删除冗余联系人需要确认--fail-empty别名--non-empty、--require-resultsboolfalse未发现重复时以退出码 3 退出此外它还继承了 gog 的全局参数体系多账号、输出格式、确认与只读控制等完整参数表见下文“完整参数表”一节。基本用法预览重复分组三种最常用的预览形态继承自 docs/contacts-dedupe.md 的 Basic Use 章节gog contacts dedupe gog contacts dedupe --json gog contacts dedupe --max 500 --jsongog contacts dedupe以表格形式输出重复分组适合人工快速浏览。追加--json输出结构化 JSON便于脚本和 Agent 消费。追加--max 500限制扫描规模。--max 0仍会扫描所有不同的分页正数则达到该联系人数量即停止。匹配字段与归一化规则默认匹配使用归一化后的邮箱与电话gog contacts dedupe --match email,phone姓名的匹配是可选开启的因为可能产生误报gog contacts dedupe --match email,phone,name源码 parseContactsDedupeMatch 会忽略大小写与空格解析--match且要求至少启用一个字段否则返回 usage 错误。三种键的归一化函数决定了“什么算同一个值”邮箱normalizeContactEmailTrimSpace 转小写。因此ADAexample.com与adaexample.com会被判为同一键。电话normalizeContactPhone只保留数字字符。因此1 (555) 0100与15550100匹配。姓名normalizeContactName转小写、压缩内部空白。 ada lovelace 与Ada Lovelace会被判为同键——这也是官方文档提示姓名匹配会产生误报的原因。单元测试 TestBuildContactsDedupeGroupsTransitive 印证了上述规则邮箱大小写差异与电话格式差异都能把三条联系人连成一组且matched_on输出为排序后的键列表如email:adaexample.com、phone:15550100。分组算法基于并查集的传递性去重“疑似重复”的判定并非两两比较而是 buildContactsDedupeGroups 中的并查集union-find为每个联系人计算所有匹配键email:/phone:/name:前缀 归一化值相同键的联系人互相union因此重复关系是传递性的A 与 B 共享邮箱、B 与 C 共享电话则 A、B、C 会归入同一组只保留成员数 ≥ 2 的组并记录“组内至少出现两次”的键作为matched_on见 groupKeys 过滤逻辑组按主联系人资源名字典序排序输出稳定。每个组会选出primary合并时保留的联系人。chooseContactsDedupePrimary 按 contactsDedupeScore 打分有主名称 2每有一个邮箱/电话 2有组织 1有网址 1分数相同时资源名更小的优先。从测试用例看信息更完整邮箱 电话的people/2会胜过只有邮箱的people/1成为 primary。数据获取路径分页、范围限定与死循环防护扫描联系人有两条路径由是否传入--resource决定Run 中的分支全量分页扫描contactsDedupeList调用People.Connections.List仅读取READ_SOURCE_TYPE_CONTACT来源即个人通讯录数据每页请求 500 条当--max为正数时若剩余配额小于 500 会自动缩小pageSize并在累计达到--max时立即返回每页请求前都经过 pageTokenGuard.check若出现重复的 page token命令会报pagination loop: repeated page token并立即中止扫描。这是文档中“Repeated page tokens stop the scan before a plan or contact update”的实现依据TestContactsDedupeExecuteRejectsRepeatedPageToken 验证了服务返回卡死 token 时命令在第二次列表调用后即报错、且无任何部分输出。精确资源扫描contactsDedupeGetResources--resource必须是people/...形式normalizeContactsDedupeResources 会做前缀校验、去空格并按首次出现顺序去重逐个调用People.Get获取天然跳过分页——这也是为什么 Run 中--max与--resource不可同时使用直接返回 usage 错误。两条路径都只读取contactsReadMask中列出的字段names,emailAddresses,phoneNumbers,birthdays,organizations,urls定义于 contacts_crud.go即文档所述“只读取 contact-source 数据”。执行合并--apply的完整流程People API 不暴露 Google Contacts 原生的“合并”操作因此 gogcli 自己实现了“字段并集 顺序删除”的两阶段流程docs/contacts-dedupe.md 的 Safety 章节与 contacts_dedupe_apply.go 一致。推荐的操作序列先审查合并计划gog contacts dedupe --json再在不改动联系人的前提下检查精确的变更计划注意这里同时带--apply和--dry-rundry-run 会展示--apply将要执行的全部动作然后退出gog contacts dedupe --apply --dry-run --json在自动化场景中从已审查的预览结果中复制联系人资源名把 dry-run 与 apply 都限定到同一组精确资源上gog contacts dedupe \ --resource people/123 \ --resource people/456 \ --apply \ --dry-run \ --json交互式应用合并会要求确认gog contacts dedupe --apply非交互式自动化必须显式跳过确认--force/-y/--assume-yes/--yes四者等价gog contacts dedupe \ --resource people/123 \ --resource people/456 \ --apply \ --force \ --json源码中的 apply 阶段拆解--apply路径在 Run 中依次执行三步第一步准备计划refresh plan。prepareContactsDedupeApply 对每个组先调用 refreshContactsDedupeGroup用完整的 apply 读掩码可变字段 coverPhotos,metadata,photos,skills重新拉取每个成员并重新分组验证如果刷新后成员不再构成同一个重复组比如联系人刚被外部修改直接报错提示重新预览而不是继续执行陈旧计划。第二步构建单组计划。buildContactsDedupeApplyPlan 包含多重拒斥条件任何一条触发都会中止该组的合并primary 缺少 contact-source etag 时拒绝etag 提取见 contactSourceETag优先取CONTACT类型来源的 etag任何次级联系人带照片则拒绝——People API 无法通过updateContact迁移照片hasContactPhoto次级联系人含coverPhotos或skills等 API 无法更新的字段时提示改到 Google Contacts 网页端合并单例字段冲突names、birthdays、biographies、genders合并后若出现多于一个值mergeContactsDedupeSingleton 报conflicting ... cannot be merged safely错误。通过校验后mergeContactsDedupeFields 把全部 24 个 API 可更新字段contactsDedupeMutableFieldsaddresses、biographies、birthdays、calendarUrls、clientData、emailAddresses、events、externalIds、genders、imClients、interests、locales、locations、memberships、miscKeywords、names、nicknames、occupations、organizations、phoneNumbers、relations、sipAddresses、urls、userDefined取并集邮箱与电话按各自的归一化键去重mergeContactsDedupeKeyedItems保证adaexample.com/ADAexample.com只留一条其余字段按 JSON 序列化后整体去重cleanContactsDedupeItem 会先剔除metadata与formattedType再比较不可变字段照片、封面、技能等在克隆 primary 后被 clearNonMutableContactFields 清空避免误传给 API最终UpdateFields是合并结果中实际非空且可更新的字段列表populatedContactsDedupeFields 用反射检查各字段切片长度若为空则拒绝该组。第三步执行变更。applyContactsDedupePlans 按组顺序执行先UpdateContact把并集数据写入 primary再对每个冗余联系人在删除前立即重新People.Get拉取 metadata 并比对 etagL397-L415——如果该联系人在预览之后被修改过命令中止且不删除该联系人提示重新运行。这一“更新在前、删除在后、删除前再校验”的顺序保证了数据已经复制到 primary 的冗余联系人即使删除失败也不会丢失中途失败时命令停止并报告“已完成几组/删除几条”已复制的数据留在 primary 上未删除的联系人保持完整。确认环节由 dryRunAndConfirmDestructive 统一处理带--dry-run时只打印计划含update_fields与delete明细并成功退出否则在交互终端要求确认--force跳过确认--no-input模式下遇到需要确认的场景会直接失败而非挂起适合 CI。JSON 输出结构预览模式输出writeContactsDedupe字段含义scanned扫描的联系人总数groups疑似重复组列表groups[].primary假设执行合并时 gog 将保留的联系人摘要含resource、name、emails、phonesgroups[].merged合并后的邮箱/电话并集用于预览groups[].matched_on触发该组的重复键email:.../phone:.../name:...已排序groups[].members组内全部联系人primary 在前应用模式额外输出contactsDedupeApplyPayload 与 writeContactsDedupeApplyResult字段含义applied是否真正执行了变更groups_merged完成合并的组数contacts_deleted删除的冗余联系人数groups[].update_fields本次 union 写入 primary 的 People API 字段列表groups[].delete数据复制后被删除的冗余联系人列表非 JSON 场景表格输出重复分组--plain/-p时应用结果输出applied\ttrue等稳定 TSV 行普通终端则打印一行Merged N duplicate contact group(s); deleted M redundant contact(s)汇总。未发现重复时打印No duplicate contacts found (scanned N)。安全边界汇总contacts dedupe在没有--apply时是完全只读的。启用 apply 后对照 docs/contacts-dedupe.md 的 Safety 章节与源码逐条对应需要确认除非提供--force遵守--dry-run别名--dryrun、--noop、--preview即-n支持可重复的--resource范围限定让自动化只作用于已审查的精确联系人集合只读取 contact-source 数据READ_SOURCE_TYPE_CONTACT规划前刷新每个联系人且刷新后重新验证分组仍成立先更新选定的 primary再删除冗余联系人删除每个冗余联系人前重新校验其 etag拒斥存在冲突单例字段names、birthdays、biographies、genders的组拒斥带照片或含 People API 无法通过updateContact保留字段的次级联系人。另外全局--readonly标志可以在运行时直接拦截所有变更类 API 请求为 agent 场景提供额外保险。定时检查--fail-empty退出码约定在计划任务中如果希望把“没有重复联系人”作为可区分的信号使用--fail-empty别名--non-empty、--require-resultsgog contacts dedupe --fail-empty未发现重复时命令以退出码 3 退出。实现见 failEmptyExitemptyResultsExitCode 3未启用该标志时返回 nil正常退出。这意味着调度系统可以把 0有重复/正常预览与 3无重复分别处理。完整参数表以下参数表继承自自动生成的命令参考页 docs/commands/gog-contacts-dedupe.md该页由gog schema --json生成make docs-commands刷新其中全局参数适用于所有 gog 命令此处一并列出以便检索参数类型默认值说明--access-tokenstring直接使用给定 access token绕过已存 refresh tokentoken 约 1 小时过期-a/--account/--acctstring账号邮箱、别名或auto--apply/--mergebool合并重复组并删除冗余联系人需要确认--clientstringOAuth 客户端名选择已存凭据 token bucket--colorstringauto颜色输出auto/always/never--disable-commandsstring禁用命令列表逗号分隔支持点路径-n/--dry-run/--dryrun/--noop/--previewbool不做变更打印将执行的动作后成功退出--enable-commandsstring启用命令前缀列表逗号分隔支持点路径收窄 CLI--enable-commands-exactstring精确启用的命令列表父命令不会启用子命令--fail-empty/--non-empty/--require-resultsbool无重复时以退出码 3 退出-y/--force/--assume-yes/--yesbool跳过破坏性命令的确认--gmail-no-sendboolfalse阻止 Gmail 发送操作agent 安全-h/--helpkong.helpFlag显示上下文帮助--homestring覆盖 gogcli 配置/数据/状态/缓存根目录等价于 GOG_HOME-j/--json/--machineboolfalse向 stdout 输出 JSON最便于脚本使用--matchstringemail,phone匹配字段email,phone,name--max/--limitint640最多扫描的联系人数量0 全部--no-input/--non-interactive/--noninteractivebool从不交互提问需要提问时直接失败CI 友好-p/--plain/--tsvboolfalse输出稳定、可解析的文本TSV无颜色--quota-projectstring用于 API 计费的 GCP 项目以 X-Goog-User-Project 发送部分 API 在配合 --access-token 或 ADC 时必需--readonlyboolfalse运行时阻止变更类 API 请求auth add 同时请求只读 OAuth scope--resource[]string将去重限定到精确联系人资源名people/...可重复--results-onlyboolJSON 模式下只输出主结果丢弃 envelope 字段如 nextPageToken--select/--pick/--projectstringJSON 模式下选择逗号分隔的字段尽力而为支持点路径-v/--verbosebool启用详细日志--versionkong.VersionFlag打印版本并退出--wrap-untrustedboolfalseJSON/raw 输出中为拉取到的文本字段包裹外部不可信内容标记相关页面Raw API Dumps通过原始 API 通道直接访问 People API 的说明。生成的 contacts 命令页gog contacts全部子命令参考。gog contacts export导出联系人。gog contacts rawcontacts 原始 API 调用。实现与测试证据集中在 internal/cmd/contacts_dedupe.go、internal/cmd/contacts_dedupe_apply.go测试覆盖见 internal/cmd/contacts_dedupe_test.go 与 internal/cmd/contacts_dedupe_apply_test.go设计文档见 docs/contacts-dedupe.md。【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考