fix(assets): 企业归属重算不再被单行非法 ip 打挂,并明确告知是哪些资产

assets.ip 是自由文本(Agent 的 insert_assets 和资产 API 都可能写进主机名),
而 db/companies.go 的 IP/CIDR 归属匹配裸转 a.ip::inet。只要表里有一行不是
合法 IP,PostgreSQL 就抛 22P02 中断整条语句,连带同事务的新增/修改企业资产
范围、删除企业全部失败回滚。这个裸转从项目初始化提交就存在。

- schema.sql 新增 try_inet(text):非法值返回 NULL 而不是抛错。没用
  pg_input_is_valid 是因为那要 PG16+,这里要兼容更老的存量库。
- 四处 IP/CIDR 匹配统一改用 try_inet。原先 task_scope.go 和
  task_assets_context.go 用的正则 a.ip !~ '[^0-9a-fA-F:.]' 只检查字符集,
  abc.def 这类全 hex 字母的主机名照样漏过去后炸,一并换掉。
- 跳过不能是静默的:重算会统计并抽样列出这些资产(id + 取值),说明它们不会
  被网段规则归属。信息通过企业范围接口新增的 warnings 字段返回并在前端以
  长停留 toast 展示,同时无条件写入服务端日志,覆盖没有前端响应的路径
  (删除企业、scopesentry 同步、Agent 写入)。
- warnings 与 errors 分开:errors 仍只表示本次提交里不合法的行,既有数据
  问题不该被记到用户这次输入头上。

验证: 干净库上 go test -p 1 ./... 仅剩 1 个既有失败(TestCompanyScopeInvalid,
与本改动无关,stash 后同样失败);改动前在含真实数据的库上有 14 个失败,现已
全部通过。go build / vet / gofmt / tsc / biome 均通过。
This commit is contained in:
Autumn-27 committed 2026-08-24 10:16:17 -04:00
1 parent fea78b2d6f
commit 9e98407528
9 files changed
+151 -17

No files matched your search

@@ -1321,6 +1321,14 @@ function CompanyAvatar({ name, logo }: { name: string; logo?: string }) {
);
}
// 后端返回的 warnings 说的是既有数据问题(不是本次提交的行有错),保存本身已经
// 成功。给更长的停留时间,因为它需要用户去处理具体的资产,扫一眼标题不够。
function showScopeWarnings(warnings?: string[]) {
for (const warning of warnings ?? []) {
toast.warning(warning, { duration: 15000 });
}
}
function savedScopeRules(company: Company): CompanyScopeRule[] {
return (company.scope ?? [])
.map((scope) => ({ kind: scope.kind, value: scope.raw.trim() }))
@@ -1442,6 +1450,7 @@ function EditScopeDialog({ company, onSaved }: { company: Company; onSaved: () =
const errCount = res.invalid ?? 0;
if (errCount > 0) toast.warning(`已保存;${errCount} 行无效`);
else toast.success(`范围已更新,共 ${res.added} 条`);
showScopeWarnings(res.warnings);
setOpen(false);
onSaved();
} catch (e) {
@@ -1528,6 +1537,7 @@ function AppendScopeDialog({ company, onSaved }: { company: Company; onSaved: ()
const errCount = res.invalid ?? 0;
if (errCount > 0) toast.warning(`已保存;${errCount} 行无效`);
else toast.success(`已追加 ${res.added} 条范围`);
showScopeWarnings(res.warnings);
setOpen(false);
onSaved();
} catch (e) {
+3 -2
View File
@@ -17,6 +17,7 @@ import type {
ChatAttachment,
CommandRecord,
Company,
CompanyScopeMutation,
CompanyScopeRule,
Conversation,
ConvTokenSummary,
@@ -395,12 +396,12 @@ export const api = {
},
),
addCompanyScope: (id: number, scope: CompanyScopeRule[], reason = "") =>
post<{ added: number; skipped: number; invalid: number; errors?: string[] }>(`/companies/${id}/scope`, {
post<CompanyScopeMutation>(`/companies/${id}/scope`, {
scope,
reason,
}),
updateCompanyScope: (id: number, scope: CompanyScopeRule[], reason = "") =>
post<{ added: number; skipped: number; invalid: number; errors?: string[] }>(`/companies/${id}/scope`, {
post<CompanyScopeMutation>(`/companies/${id}/scope`, {
scope,
reason,
reset: true,
+10
View File
@@ -298,6 +298,16 @@ export interface CompanyScopeRule {
value: string;
}
// 资产范围写入的结果。errors 是本次提交里不合法的行;warnings 是与本次提交无关、
// 但会让归属结果不符合预期的既有数据问题(如 ip 字段存了主机名的资产)。
export interface CompanyScopeMutation {
added: number;
skipped: number;
invalid: number;
errors?: string[];
warnings?: string[];
}
// 公司资产范围规则的一条(归属唯一真值来源)。
export interface ScopeRow {
id: number;