Skip to content

fix: prevent asset dedup key from collapsing distinct IPs under the same domain - #243

Open
keyblues wants to merge 2 commits into
Ed1s0nZ:mainfrom
keyblues:fix/asset-import-dedup-key
Open

fix: prevent asset dedup key from collapsing distinct IPs under the same domain#243
keyblues wants to merge 2 commits into
Ed1s0nZ:mainfrom
keyblues:fix/asset-import-dedup-key

Conversation

@keyblues

@keyblues keyblues commented Aug 8, 2026

Copy link
Copy Markdown

问题描述

批量导入资产(XLSX/CSV)的去重逻辑对测绘平台导出格式(如 FOFA)的数据会产生大面积误判:文件中的 domain 列为注册根域名、真实资产标识为 host 列的子域名与 ip 列的 IP 时,大量行被错误标记为「与第 X 行重复」,仅有少量行判定有效;手动删除文件中的 domain 列后重新导入则基本全部有效。

根因

去重键采用 domain > ip > host 优先级的单标识符设计(assetDedupKey,前后端各实现一份):domain 存在时 ip 和 host 被完全忽略。

而测绘平台导出的数据中,domain 列通常是注册根域名,真实资产标识在 host 列(子域名)和 ip 列。于是同根域名下不同资产的键全部坍缩为 根域名|端口|协议,被误判为重复。

修复方案

去重键改为 ip + domain 联合标识host兜底|ip|domain|port|protocol,其中 host 仅在 ip 与 domain 均缺失时参与(纯 URL 类资产兜底),避免自由文本 host 带来的格式敏感。该设计:

  • 修复同根域名不同 IP 的坍缩问题;
  • 保留原有语义:纯域名/纯 IP 资产的重复扫描合并更新行为不变,既有测试无需修改;
  • 前后端规则保持一致(前端预览去重键同步更新)。

存量数据迁移

migrateAssetsTable 新增启动迁移:在 Go 侧调用 assetDedupKey 本体幂等重算存量 dedup_key(单事务批量写回),覆盖 upgrade.sh 保留 data/ 的升级场景,无需改动升级脚本。

安全性:新键区分度严格细于旧键(新键相同 ⇒ host/ip/domain/port/protocol 全等 ⇒ 旧键相同),而旧键受 UNIQUE 约束,故存量记录重算后不可能撞 UNIQUE 约束。Go 侧计算而非 SQL 表达式,避免了 SQLite 内置 lower() 仅处理 ASCII 与 Go Unicode 小写化的偏差。

附带修复

  • UpdateAsset/MergeAssets:新语义允许同 domain+port+protocol 不同 IP 的资产共存后,编辑标识字段使其与已共存资产完全一致会触发 UNIQUE 冲突(此前不可达)。现转译为可读的 AssetValidationError,不再透出裸 SQLite 错误(HTTP 状态码不变)。
  • 同步更新 MCP create_asset 工具描述、OpenAPI /api/assets/import 描述及中英文文档共 8 处。

测试

新增 3 个回归测试(数据库层测试需 CGO,请 CI 验证):

  • TestAssetUpsertKeepsDistinctIPsUnderSameDomain:同域名不同 IP 保持独立,完全相同行仍合并更新;
  • TestAssetDedupKeyMigrationRecomputesLegacyKeys:旧格式键在重新打开数据库时被自动重算;
  • TestUpdateAssetRejectsDedupKeyConflict:UNIQUE 冲突被转换为校验错误。

本地已通过 go build ./...go vet;迁移逻辑已用 SQLite 实测验证幂等性与正确性。

Copilot AI lite review requested due to automatic review settings August 8, 2026 15:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect deduplication during bulk asset import where the previous domain > ip > host single-identifier key could collapse distinct IPs under the same (registrable root) domain. It updates both frontend preview deduplication and backend persistence semantics, and adds an automatic migration to recompute legacy dedup_key values.

Changes:

  • Update asset dedup key semantics to host(fallback only) | ip | domain | port | protocol, preventing distinct IPs under the same domain from being merged.
  • Add startup migration to recompute existing assets.dedup_key values to the new format.
  • Improve UNIQUE-conflict UX by translating dedup_key collisions into a readable validation error; update docs/OpenAPI/MCP tool description and add regression tests.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/static/js/assets.js Align frontend import preview dedup key with backend assetDedupKey (ip+domain, host fallback).
internal/handler/openapi.go Update OpenAPI description to reflect new deduplication semantics.
internal/database/database.go Add migration logic to recompute legacy dedup_key values.
internal/database/asset.go Change assetDedupKey format; translate dedup UNIQUE conflicts for Update/Merge.
internal/database/asset_test.go Add regression tests for distinct IPs under same domain, migration recompute, and conflict translation.
internal/app/asset_tools.go Update MCP create_asset tool description to match new dedup semantics.
docs/zh-CN/asset-management.md Update deduplication explanation for bulk import behavior (CN).
docs/zh-CN/api-reference.md Update API deduplication rules wording (CN).
docs/zh-CN/api-recipes.md Update recipe notes to new dedup semantics (CN).
docs/en-US/asset-management.md Update deduplication explanation for bulk import behavior (EN).
docs/en-US/api-reference.md Update API deduplication rules wording (EN).
docs/en-US/api-recipes.md Update recipe notes to new dedup semantics (EN).
Suppressed comments (1)

internal/database/asset.go:902

  • UpdateAsset 中 if key == "|||0|" { ... } 判断同样不可达:validateAsset 已保证 host/ip/domain 至少其一非空,因此 dedup_key 不会是全空形式。建议移除该判断,避免维护时误解这里可能返回非校验错误的 fmt.Errorf。
	key := assetDedupKey(a)
	if key == "|||0|" {
		return fmt.Errorf("资产目标不能为空")
	}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/database/database.go
Comment thread internal/database/asset.go
Comment thread internal/database/asset.go Outdated
@keyblues
keyblues force-pushed the fix/asset-import-dedup-key branch from 94727f2 to 1061316 Compare August 8, 2026 15:56
…ame domain

批量导入资产时,去重键采用 domain > ip > host 优先级的单标识符设计。
FOFA 等测绘平台导出的数据中 domain 列通常是注册根域名,真实资产标识
在 host 列(子域名)和 ip 列。domain 存在时 ip/host 被完全忽略,
导致同根域名下的不同资产全部坍缩为同一个键,被大面积误判为重复行。

修复:
- 去重键改为 ip + domain 联合标识(host 仅在 ip 与 domain 均缺失时
  兜底,纯 URL 类资产),前后端规则保持一致;空资产哨兵同步更新。
- migrateAssetsTable 新增启动迁移:在 Go 侧调用 assetDedupKey 幂等
  重算存量 dedup_key(单事务批量写回),覆盖 upgrade.sh 保留 data/
  的升级场景。新键区分度严格细于旧键,可证明不会撞 UNIQUE 约束。
- UpdateAsset/MergeAssets 中新语义引入的 dedup_key UNIQUE 冲突路径
  转译为可读的 AssetValidationError,不再透出裸 SQLite 错误。
- 同步更新 MCP create_asset 工具描述、OpenAPI 描述及中英文文档。
- 新增 3 个回归测试:同域名不同 IP 保持独立、存量旧键迁移重算、
  UNIQUE 冲突转译。
@keyblues
keyblues force-pushed the fix/asset-import-dedup-key branch from 1061316 to 11401b9 Compare August 8, 2026 16:02
- migrateAssetsTable: add a lightweight EXISTS probe comparing stored keys
  against the new-format expression, and skip the full-table recompute when
  all rows already match. Post-migration startups now pay only one cheap SQL
  scan instead of scanning every row into Go. The probe compares values
  rather than counting '|' separators because host is free-form text and may
  itself contain '|'. The probe cannot miss stale keys (normalizeAsset keeps
  ip/domain/protocol lowercase at write time); in the extreme case of
  host-fallback keys with non-ASCII uppercase it may only produce a
  write-free extra pass.
- translateAssetDedupConflict: match the UNIQUE constraint error
  case-insensitively, consistent with other err.Error() substring checks in
  this repository.
- Remove the unreachable "|||0|" empty-key guards in UpsertAssets and
  UpdateAsset: normalizeAsset + validateAsset already guarantee at least one
  of host/ip/domain is non-empty before the dedup key is computed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants