fix(syb): 按采购员设备归属隔离一键关联与一键替换候选 #328

Open
opened 2026-09-21 00:55:26 +08:00 by ila · 6 comments
Owner

需求与确认方案

多人同时使用系统、每个采购员账号拥有自己的一个或多个 Android 手机且不混用时,SYB 商品行“一键关联/一键替换”不得取到其他采购员的临时采集结果。

  • 管理员维护采购员账号与手机归属;一个账号可拥有多台手机。
  • 两个入口只在当前登录采购员拥有的手机范围取候选;一台自动使用,多台复用现有行内小下拉箭头选择并记忆。
  • 候选必须是所选手机最近一次可用临时采集;点击后冻结 taskId、deviceId、PDD 商品和规格上下文,不得重新查询“最新”而漂移。
  • 最近一次不完整或不可用时提示重新采集/等待,不回退旧任务、其他手机或全局最近任务。

前置依赖(未满足前不得实施)rnrn本单是设备归属数据的消费方,依赖一个前置单完成:rn- 设备归属数据模型/迁移;rn- 管理员维护采购员与手机归属的入口及已确认原型;rn- 现存 8 台设备的明确初始化归属;rn- 管理员可见范围和归属变更规则。rnrn前置单完成并验收前,本单保持待实施;不得用设备名称、历史采集记录或浏览器偏好推断归属,也不得用全局设备列表作为兜底。rnrn## 基线与范围

基线 main:8d9c3d47e084e6b0e6db013024c9f38aedd3d0a7(2026-09-21)。#253 一键关联、#258 一键替换已上线,为依赖。当前 quick-link.js 请求 listDevices 后展示全部 online/offline 手机;偏好按登录用户保存,但选择不是服务端归属授权。前置归属数据完成后,本单才补充按当前登录用户过滤和服务端最终校验。请求已带 deviceId 和 latestCollection 上下文。本单只补归属范围和服务端最终校验,不改变现有匹配、替换确认、并发冻结和失败回读。

交付单元 Server + Web;Android、采集器、全局菜单权限、采购流程、付款/下单及历史采购快照不改。服务端必须按当前登录用户过滤并校验 deviceId,不能只依赖前端;越权、未归属、停用设备返回明确错误。采购员只使用本人归属设备;管理员范围按前置单确认的规则执行,不能在本单自行扩大。继续复用现有行内小下拉,不增加顶部设备选择或新流程。实施时核对设备模型是否已有归属字段;没有则另提最小迁移方案,未经授权不实施。

设计门禁、验收与风险

如需新增设备归属管理界面或改变设备列表交互,先制作并确认 QuantUX/低保真原型,覆盖正常、空、加载、失败、禁用和权限边界。数据库迁移、权限写入、并发/安全策略、发布均需实施前明确授权。

验收:A 看不到且不能改请求使用 B 的设备;多台时只能选自己的设备;候选是选定设备最近一次可用采集;点击后冻结上下文;不完整不回退;关联和替换同样校验;补充契约、越权、排序、并发和 Web 测试,不执行真实采购/下单/付款。长期设备归属/API事实变化时更新 Wiki 并 sync/check,否则说明无影响。

不根据历史记录推断归属,不以全局最近采集兜底;设备转移、批量归属和菜单权限另建工单。本单创建后待实施。前置归属单未完成时本单明确阻塞;本单不自行创建归属数据、不执行迁移或权限写入。实施、发布或重启仍需明确授权。Gitea MCP 不可用,使用项目安全配置 API 回退创建。

## 需求与确认方案 多人同时使用系统、每个采购员账号拥有自己的一个或多个 Android 手机且不混用时,SYB 商品行“一键关联/一键替换”不得取到其他采购员的临时采集结果。 - 管理员维护采购员账号与手机归属;一个账号可拥有多台手机。 - 两个入口只在当前登录采购员拥有的手机范围取候选;一台自动使用,多台复用现有行内小下拉箭头选择并记忆。 - 候选必须是所选手机最近一次可用临时采集;点击后冻结 taskId、deviceId、PDD 商品和规格上下文,不得重新查询“最新”而漂移。 - 最近一次不完整或不可用时提示重新采集/等待,不回退旧任务、其他手机或全局最近任务。 ## 前置依赖(未满足前不得实施)`r`n`r`n本单是设备归属数据的消费方,依赖一个前置单完成:`r`n- 设备归属数据模型/迁移;`r`n- 管理员维护采购员与手机归属的入口及已确认原型;`r`n- 现存 8 台设备的明确初始化归属;`r`n- 管理员可见范围和归属变更规则。`r`n`r`n前置单完成并验收前,本单保持待实施;不得用设备名称、历史采集记录或浏览器偏好推断归属,也不得用全局设备列表作为兜底。`r`n`r`n## 基线与范围 基线 main:8d9c3d47e084e6b0e6db013024c9f38aedd3d0a7(2026-09-21)。#253 一键关联、#258 一键替换已上线,为依赖。当前 quick-link.js 请求 listDevices 后展示全部 online/offline 手机;偏好按登录用户保存,但选择不是服务端归属授权。前置归属数据完成后,本单才补充按当前登录用户过滤和服务端最终校验。请求已带 deviceId 和 latestCollection 上下文。本单只补归属范围和服务端最终校验,不改变现有匹配、替换确认、并发冻结和失败回读。 交付单元 Server + Web;Android、采集器、全局菜单权限、采购流程、付款/下单及历史采购快照不改。服务端必须按当前登录用户过滤并校验 deviceId,不能只依赖前端;越权、未归属、停用设备返回明确错误。采购员只使用本人归属设备;管理员范围按前置单确认的规则执行,不能在本单自行扩大。继续复用现有行内小下拉,不增加顶部设备选择或新流程。实施时核对设备模型是否已有归属字段;没有则另提最小迁移方案,未经授权不实施。 ## 设计门禁、验收与风险 如需新增设备归属管理界面或改变设备列表交互,先制作并确认 QuantUX/低保真原型,覆盖正常、空、加载、失败、禁用和权限边界。数据库迁移、权限写入、并发/安全策略、发布均需实施前明确授权。 验收:A 看不到且不能改请求使用 B 的设备;多台时只能选自己的设备;候选是选定设备最近一次可用采集;点击后冻结上下文;不完整不回退;关联和替换同样校验;补充契约、越权、排序、并发和 Web 测试,不执行真实采购/下单/付款。长期设备归属/API事实变化时更新 Wiki 并 sync/check,否则说明无影响。 不根据历史记录推断归属,不以全局最近采集兜底;设备转移、批量归属和菜单权限另建工单。本单创建后待实施。前置归属单未完成时本单明确阻塞;本单不自行创建归属数据、不执行迁移或权限写入。实施、发布或重启仍需明确授权。Gitea MCP 不可用,使用项目安全配置 API 回退创建。
Author
Owner

范围核查(2026-09-21,只读核验,未改动代码)

结论:问题真实、方向正确、安全考虑到位,但当前范围不可实施——工单把一个不存在的前提写成了「实施时核对」项。

一、已核验属实的部分

工单说法 核验结果
基线 8d9c3d47e084e6b0e6db013024c9f38aedd3d0a7 存在(#327 文档提交)
前端展示全部 online/offline 手机 属实。web/src/views/goauto/syb-products/quick-link.js:57 按 status === 'online' || status === 'offline' 过滤,等于全量展示
偏好按登录用户保存、但不是服务端归属授权 属实。web/src/utils/quick-link-device-preference.js 存于浏览器 localStorage
服务端未按当前用户校验 deviceId 属实。server/app/goauto/shopeeproduct/latest_collection.go:99 直接按请求传入的 ID 加锁查 agent_device,:109 按 device_id 查采集任务,全程无归属或用户校验
多采购员前提 属实。生产 sys_user 3 个账号(admin/zhangwj、caigou1/采购1、caigou2);agent_device 8 台

「不能只依赖前端」的要求符合项目安全规则;点击后冻结 taskId/deviceId/规格上下文以防漂移是合理设计;非目标(不改采购流程、不碰付款下单、设备转移与批量归属另建工单)划分清楚。

二、关键问题:设备归属字段不存在,且不是可选项

核验 server/app/goauto/models/schema.go 的 AgentDevice,全部字段为:

InstallID / Name / Manufacturer / Model / AndroidVersion / AgentVersion / PDDVersion
CapabilitiesJSON / Status / TokenDigest / TokenIssuedAt / TokenRevokedAt
RecoveryCodeDigest / RecoveryExpiresAt / RecoveryUsedAt
LastRegisterRequestID / LastHeartbeatRequestID / LastHeartbeatAt / CreatedAt / UpdatedAt

没有任何 UserID / OwnerID / PurchaserID 字段;全库也不存在任何 user↔device 关联表(已按 UserDevice / DeviceUser / DeviceOwner / DeviceAssign 等命名检索,无结果)。

工单将此写为「实施时核对设备模型是否已有归属字段;没有则另提最小迁移方案,未经授权不实施」,即当作待核对的条件分支。但核验结果是确定的——它不存在,因此它不是条件,而是整个功能的地基。

据此,工单正文中这一句不成立:

本单只补归属范围和服务端最终校验,不改变现有匹配、替换确认、并发冻结和失败回读

实际需要新增一个领域概念(设备归属),连带数据库迁移、归属维护界面,以及该界面按本工单自身门禁所要求的原型确认。

三、由此产生的三个自洽性缺口

  1. 归属数据从何而来:需求要求「管理员维护采购员账号与手机归属」,但归属管理界面被推给原型门禁、设备转移与批量归属被推给其他工单。本单交付后没有可供过滤的数据,功能无法生效。
  2. 管理员可见范围未定义:admin 是否同样受「只在当前登录采购员拥有的手机范围取候选」约束,工单未说明。
  3. 存量设备归属未定义:生产 8 台设备中仅「采购1」(id 8)、「采购2」(id 7) 按采购员命名,其余 6 台为硬件名。迁移默认归属、以及某采购员名下无任何设备时两个入口的表现均未定义——工单现有的「提示重新采集/等待」针对的是采集结果不可用,不是「无归属设备」这一情形。

四、建议

拆为两单,使本单的「只补范围和校验」真正成立:

  • 前置单:设备归属数据模型(迁移)+ 归属维护界面(含原型确认)+ 存量 8 台设备归属方案 + 管理员可见范围规则。迁移与权限写入属高风险,本就需要单独授权。
  • #328 改为消费方:按归属过滤候选、服务端强制校验 deviceId、越权/未归属/停用设备返回明确错误。

说明

本次核查为只读:未修改任何代码、未创建迁移、未改动工单正文。以上结论中,第一节与第二节为代码与生产数据的直接核验结果,第三、四节为在此之上的判断。

## 范围核查(2026-09-21,只读核验,未改动代码) 结论:问题真实、方向正确、安全考虑到位,但**当前范围不可实施**——工单把一个不存在的前提写成了「实施时核对」项。 ### 一、已核验属实的部分 | 工单说法 | 核验结果 | |---|---| | 基线 `8d9c3d47e084e6b0e6db013024c9f38aedd3d0a7` | 存在(#327 文档提交) | | 前端展示全部 online/offline 手机 | 属实。`web/src/views/goauto/syb-products/quick-link.js:57` 按 `status === 'online' \|\| status === 'offline'` 过滤,等于全量展示 | | 偏好按登录用户保存、但不是服务端归属授权 | 属实。`web/src/utils/quick-link-device-preference.js` 存于浏览器 `localStorage` | | 服务端未按当前用户校验 deviceId | **属实**。`server/app/goauto/shopeeproduct/latest_collection.go:99` 直接按请求传入的 ID 加锁查 `agent_device`,`:109` 按 `device_id` 查采集任务,全程无归属或用户校验 | | 多采购员前提 | **属实**。生产 `sys_user` 3 个账号(admin/zhangwj、caigou1/采购1、caigou2);`agent_device` 8 台 | 「不能只依赖前端」的要求符合项目安全规则;点击后冻结 taskId/deviceId/规格上下文以防漂移是合理设计;非目标(不改采购流程、不碰付款下单、设备转移与批量归属另建工单)划分清楚。 ### 二、关键问题:设备归属字段不存在,且不是可选项 核验 `server/app/goauto/models/schema.go` 的 `AgentDevice`,全部字段为: ``` InstallID / Name / Manufacturer / Model / AndroidVersion / AgentVersion / PDDVersion CapabilitiesJSON / Status / TokenDigest / TokenIssuedAt / TokenRevokedAt RecoveryCodeDigest / RecoveryExpiresAt / RecoveryUsedAt LastRegisterRequestID / LastHeartbeatRequestID / LastHeartbeatAt / CreatedAt / UpdatedAt ``` 没有任何 `UserID` / `OwnerID` / `PurchaserID` 字段;全库也不存在任何 user↔device 关联表(已按 `UserDevice` / `DeviceUser` / `DeviceOwner` / `DeviceAssign` 等命名检索,无结果)。 工单将此写为「实施时核对设备模型是否已有归属字段;没有则另提最小迁移方案,未经授权不实施」,即当作待核对的条件分支。但核验结果是确定的——**它不存在**,因此它不是条件,而是整个功能的地基。 据此,工单正文中这一句不成立: > 本单只补归属范围和服务端最终校验,不改变现有匹配、替换确认、并发冻结和失败回读 实际需要新增一个领域概念(设备归属),连带数据库迁移、归属维护界面,以及该界面按本工单自身门禁所要求的原型确认。 ### 三、由此产生的三个自洽性缺口 1. **归属数据从何而来**:需求要求「管理员维护采购员账号与手机归属」,但归属管理界面被推给原型门禁、设备转移与批量归属被推给其他工单。本单交付后没有可供过滤的数据,功能无法生效。 2. **管理员可见范围未定义**:admin 是否同样受「只在当前登录采购员拥有的手机范围取候选」约束,工单未说明。 3. **存量设备归属未定义**:生产 8 台设备中仅「采购1」(id 8)、「采购2」(id 7) 按采购员命名,其余 6 台为硬件名。迁移默认归属、以及某采购员名下无任何设备时两个入口的表现均未定义——工单现有的「提示重新采集/等待」针对的是采集结果不可用,不是「无归属设备」这一情形。 ### 四、建议 拆为两单,使本单的「只补范围和校验」真正成立: - **前置单**:设备归属数据模型(迁移)+ 归属维护界面(含原型确认)+ 存量 8 台设备归属方案 + 管理员可见范围规则。迁移与权限写入属高风险,本就需要单独授权。 - **#328 改为消费方**:按归属过滤候选、服务端强制校验 deviceId、越权/未归属/停用设备返回明确错误。 ### 说明 本次核查为只读:未修改任何代码、未创建迁移、未改动工单正文。以上结论中,第一节与第二节为代码与生产数据的直接核验结果,第三、四节为在此之上的判断。
Author
Owner

实施完成,待验收(2026-09-21)

实现提交:0661b22;文档提交:2491a85,均已推送 main。

已实现

  • 采购员调用设备列表时,服务端按当前登录用户过滤,只返回本人归属的 online/offline 设备;管理员仍可查看全部设备。
  • 一键关联/一键替换服务端最终校验请求 deviceId 的归属;越权、未归属设备返回 DEVICE_OWNERSHIP_FORBIDDEN,不会写入关联。
  • 继续按所选 deviceId 查询该设备最近的 completed/completed_partial 临时采集;不使用账号其他设备或全局最新记录。
  • 保留原有 taskId、deviceId、PDD 商品、规格上下文冻结和并发保护;未修改 Android、采集器、采购及付款流程。
  • 增加归属越权回归测试;复用 #329 的 owner_user_id 字段和管理接口。

验证

  • go test ./app/goauto/device ./app/goauto/shopeeproduct 通过。
  • 新增“其他采购员设备不可关联且不写入”测试通过。
  • #329 的迁移和设备归属实现作为前置依赖已验收。
  • 未执行线上数据库迁移、生产设备归属初始化、发布或重启。

文档

已更新长期业务规则并同步 Wiki,记录设备归属和一键关联/替换按所选设备取最新采集的规则。

## 实施完成,待验收(2026-09-21) 实现提交:`0661b22`;文档提交:`2491a85`,均已推送 main。 ### 已实现 - 采购员调用设备列表时,服务端按当前登录用户过滤,只返回本人归属的 online/offline 设备;管理员仍可查看全部设备。 - 一键关联/一键替换服务端最终校验请求 deviceId 的归属;越权、未归属设备返回 `DEVICE_OWNERSHIP_FORBIDDEN`,不会写入关联。 - 继续按所选 deviceId 查询该设备最近的 completed/completed_partial 临时采集;不使用账号其他设备或全局最新记录。 - 保留原有 taskId、deviceId、PDD 商品、规格上下文冻结和并发保护;未修改 Android、采集器、采购及付款流程。 - 增加归属越权回归测试;复用 #329 的 owner_user_id 字段和管理接口。 ### 验证 - `go test ./app/goauto/device ./app/goauto/shopeeproduct` 通过。 - 新增“其他采购员设备不可关联且不写入”测试通过。 - #329 的迁移和设备归属实现作为前置依赖已验收。 - 未执行线上数据库迁移、生产设备归属初始化、发布或重启。 ### 文档 已更新长期业务规则并同步 Wiki,记录设备归属和一键关联/替换按所选设备取最新采集的规则。
Author
Owner

本地迁移已完成:owner_user_id 已创建并记录迁移版本 1789800300000,重复执行无新增迁移。当前需在本地设备列表为手机分配采购员后,#328 才能进行采购员范围验证。

本地迁移已完成:owner_user_id 已创建并记录迁移版本 1789800300000,重复执行无新增迁移。当前需在本地设备列表为手机分配采购员后,#328 才能进行采购员范围验证。
Author
Owner

范围追加:手动关联/更换 PDD 搜索列表(2026-09-21)

用户确认:采购员登录后,在 SYB 商品详情中手动“关联/更换 PDD 商品”的搜索列表,也必须限制为当前账号归属手机采集过的 PDD 商品;Admin 账号仍可查看全部商品。

规则:

  • 采购员只显示本人归属手机产生的 agent_current_page 采集结果,任务状态为 completed 或 completed_partial。
  • 按该账号归属手机最近一次有效采集排序;不显示其他账号、未归属手机或仅 Admin 手工录入的商品。
  • 一键关联/一键替换继续使用所选具体手机和最新记录,不改变既有冻结、并发和失败回读。
  • 手动关联/更换必须由服务端过滤,不能只依赖前端参数;Admin 全局范围保持不变。

本次追加只涉及 Server/Web 查询过滤和越权测试,不改 Android、数据库模型、采集流程、采购流程或付款。

## 范围追加:手动关联/更换 PDD 搜索列表(2026-09-21) 用户确认:采购员登录后,在 SYB 商品详情中手动“关联/更换 PDD 商品”的搜索列表,也必须限制为当前账号归属手机采集过的 PDD 商品;Admin 账号仍可查看全部商品。 规则: - 采购员只显示本人归属手机产生的 agent_current_page 采集结果,任务状态为 completed 或 completed_partial。 - 按该账号归属手机最近一次有效采集排序;不显示其他账号、未归属手机或仅 Admin 手工录入的商品。 - 一键关联/一键替换继续使用所选具体手机和最新记录,不改变既有冻结、并发和失败回读。 - 手动关联/更换必须由服务端过滤,不能只依赖前端参数;Admin 全局范围保持不变。 本次追加只涉及 Server/Web 查询过滤和越权测试,不改 Android、数据库模型、采集流程、采购流程或付款。
Author
Owner

手动关联/更换列表过滤已实现(2026-09-21)

提交:4261a54,已推送 main。

  • 采购员访问 PDD 商品列表时,服务端只返回本人归属设备产生的 agent_current_page completed/completed_partial 采集商品。
  • Admin 账号不受该过滤限制,仍可查看全部商品。
  • Admin 手工录入、其他账号设备、未归属设备和非终态采集不会出现在采购员手动关联/更换列表。
  • 过滤使用服务端 EXISTS 查询,不能通过前端参数绕过。
  • 一键关联/替换逻辑、设备选择、任务冻结和 Android 保持不变。

验证:go test ./app/goauto/product ./app/goauto/shopeeproduct ./app/goauto/device 通过;新增归属设备/来源/状态过滤回归测试通过。无需新增数据库迁移。当前待验收,未发布或重启线上服务。

## 手动关联/更换列表过滤已实现(2026-09-21) 提交:4261a54,已推送 main。 - 采购员访问 PDD 商品列表时,服务端只返回本人归属设备产生的 agent_current_page completed/completed_partial 采集商品。 - Admin 账号不受该过滤限制,仍可查看全部商品。 - Admin 手工录入、其他账号设备、未归属设备和非终态采集不会出现在采购员手动关联/更换列表。 - 过滤使用服务端 EXISTS 查询,不能通过前端参数绕过。 - 一键关联/替换逻辑、设备选择、任务冻结和 Android 保持不变。 验证:go test ./app/goauto/product ./app/goauto/shopeeproduct ./app/goauto/device 通过;新增归属设备/来源/状态过滤回归测试通过。无需新增数据库迁移。当前待验收,未发布或重启线上服务。
Author
Owner

线上发布完成(2026-09-22)

已按授权发布 main 提交 5e0a9d1,发布标识 20260922-5e0a9d1-328-330。

  • Server/Web 已构建并上传;线上 /home/goauto/current 已原子切换到该 release。
  • 执行线上幂等迁移:新增执行 1 个(设备归属 1789800300000),其余 53 个已应用;未修改定时任务启停状态。
  • goauto.service 状态为 active,Nginx reload 成功。
  • 本机 API 健康检查和公网 Admin 首页 HTTP 200;线上二进制 SHA-256:4a6806759108901808acaf162959909ece6c46d5dab02acdd56df30bccd315e8。
  • 未执行真实采购、下单或付款;未初始化生产设备归属,存量设备仍需管理员明确分配后才会进入采购员候选范围。

状态:待线上功能验收。

## 线上发布完成(2026-09-22) 已按授权发布 main 提交 `5e0a9d1`,发布标识 `20260922-5e0a9d1-328-330`。 - Server/Web 已构建并上传;线上 `/home/goauto/current` 已原子切换到该 release。 - 执行线上幂等迁移:新增执行 1 个(设备归属 `1789800300000`),其余 53 个已应用;未修改定时任务启停状态。 - `goauto.service` 状态为 active,Nginx reload 成功。 - 本机 API 健康检查和公网 Admin 首页 HTTP 200;线上二进制 SHA-256:`4a6806759108901808acaf162959909ece6c46d5dab02acdd56df30bccd315e8`。 - 未执行真实采购、下单或付款;未初始化生产设备归属,存量设备仍需管理员明确分配后才会进入采购员候选范围。 状态:待线上功能验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#328