--- todos: - id: "internal-layer" content: "新建 internal DeviceController + 迁移 logic/dto/validate(DTO 含 user_id)" status: pending - id: "cleanup-api" content: "从 api DeviceController 移除绑定三接口并删除 api 侧绑定文件" status: pending - id: "remove-user-auth" content: "删除 UserAuth、UserRequestContext 及无引用错误码" status: pending - id: "docs-update" content: "更新 device.md §10、03-user-binding.md、06-internal-api.md 路径与调用方说明" status: pending - id: "verify" content: "docker php -l + slot-backend-completion-report" status: pending isProject: false --- # SR-3 设备绑定迁入 internal 应用 ## 架构判断(同意你的方向) 与 [device.md](device/docs/requirements/device.md) 中已有分层一致: | 应用 | 调用方 | 鉴权 | 职责 | |------|--------|------|------| | **api** | 物理设备 | `device_sn` + 签名 | 激活、心跳、OTA 入口 | | **admin** | 后台 BFF / admin-service | `Authorization`(骨架) | 产品/型号/设备档案管理 | | **internal** | user-service、message-service、ota-service 等 | `X-Internal-Token` | 跨服务读写设备主数据 | 绑定关系是 **device-service 的领域数据**,但 **用户登录态不应在本服务校验**——应由 user-service(或 App BFF)完成 session/JWT 校验后,再调用 device-service 的 internal 接口写入绑定。这与 §4.3「后台 → BFF → device-service」模式同构。 ```mermaid sequenceDiagram participant User as 用户App participant UserSvc as user_service_BFF participant Device as device_service_internal User->>UserSvc: 扫码绑定 device_sn Note over UserSvc: 校验登录态,解析 user_id UserSvc->>Device: POST /internal/device/bind Note over Device: X-Internal-Token + user_id + device_sn Device-->>UserSvc: code=0 UserSvc-->>User: 绑定成功 ``` **结论**:SR-3 应落在 **internal**,而不是 api;当前 [DeviceController.php](device/app/api/controller/DeviceController.php) 上的 `#[Middleware(UserAuth)]` + `UserRequestContext` 应移除。 --- ## 目标接口(默认路由,id 走 body/query) | 方法 | 路径 | 入参 | |------|------|------| | POST | `/internal/device/bind` | `user_id`, `device_sn` | | POST | `/internal/device/unbind` | `user_id`, `device_id` | | GET | `/internal/device/list` | `user_id`(query) | 鉴权:应用级 [InternalTokenAuth](device/app/middleware/InternalTokenAuth.php)(已挂在 `internal`),**不再**使用 [UserAuth](device/app/middleware/UserAuth.php)。 --- ## 代码迁移步骤 ### 1. 新建 internal 分层(从 api 平移) - 新增 [app/internal/controller/DeviceController.php](device/app/internal/controller/DeviceController.php)(`extends InternalController`) - `bind` / `unbind` / `list` 三个 action - 将以下文件 **改 namespace 为 `app\internal\*`**(逻辑基本不变): - [app/api/logic/DeviceBindLogic.php](device/app/api/logic/DeviceBindLogic.php) → `app/internal/logic/DeviceBindLogic.php` - [app/api/dto/DeviceBindDTO.php](device/app/api/dto/DeviceBindDTO.php) → 增加 `userId` 字段 - [app/api/dto/DeviceUnbindDTO.php](device/app/api/dto/DeviceUnbindDTO.php) → 增加 `userId` 字段 - [app/api/validate/DeviceBindValidate.php](device/app/api/validate/DeviceBindValidate.php) → 增加 `user_id` 规则(`require|integer|gt:0`)及 validation 文案 Controller 从 DTO 读取 `userId`,**不再**调用 `UserRequestContext::getUserId()`。 ### 2. 清理 api 应用 - 从 [app/api/controller/DeviceController.php](device/app/api/controller/DeviceController.php) 删除 `bind` / `unbind` / `list` 及 `DeviceBindLogic`、`DeviceBindValidate`、`UserAuth` 相关 use/构造注入 - 删除 api 侧已迁移的 logic/dto/validate 文件 ### 3. 删除用户鉴权骨架(若无其它引用) - 删除 [app/middleware/UserAuth.php](device/app/middleware/UserAuth.php) - 删除 [app/support/UserRequestContext.php](device/app/support/UserRequestContext.php) - `OtaErrorCode::USER_AUTH_FAILED (1004)` 可保留翻译条目(无害),或一并移除——以「无引用」为准 ### 4. Logic 行为 [DeviceBindLogic](device/app/api/logic/DeviceBindLogic.php) 业务规则 **不变**(幂等、唯一约束、状态校验);仅入参来源从「上下文 user_id」改为「请求体/Query 显式 `user_id`」。 **安全说明**(写入 SR-3/SR-6 文档):internal 信任「调用方已在网关/BFF 完成用户鉴权」;`user_id` 由调用方传入,device-service 只负责绑定域规则。与 SR-6 §5「服务间 token」一致。 ### 5. 需求文档同步 - [03-user-binding.md](device/docs/requirements/device/03-user-binding.md):路径改为 `/internal/device/*`,注明调用方为 user-service/BFF - [device.md](device/docs/requirements/device.md) §10:同上;§10 标题可改为「用户绑定(内部接口,由 user-service 暴露给 App)」 - [06-internal-api.md](device/docs/requirements/device/06-internal-api.md):补充 bind/unbind/list 三条(扩展 SR-6 范围或交叉引用 SR-3) ### 6. 调用方 SDK(可选,本仓库外) 按 [cross-service-sdk](.cursor/rules/cross-service-sdk.mdc),若 user-service 在 monorepo 内,后续在 `slot_sdk` 增加 `DeviceClient`(或 `DeviceBindService`)方法指向上述三条 internal 路径;**本次 device 仓库内可不实现**,仅在计划中标注为调用方跟进项。 --- ## 不涉及 - `DeviceBindModel`、错误码 `4208`/`4209`、翻译文案:已存在,无需改业务码 - SR-6 只读查询(单设备档案/批量/在线状态):可另 PR 实现,与本次迁移独立 - 「查询设备绑定用户」:admin 详情已只读;若 internal 需要可加 `GET /internal/device/bind-owner?device_id=`,**非本次必须**,除非你要求一并做 --- ## 验收 1. `api` 仅保留 `activate`、`heartbeat`(及后续 OTA) 2. `internal` 三条绑定接口带 `X-Internal-Token` 可通,缺 token 返回 `1001` + HTTP 403 3. 绑定/解绑/列表业务行为与现 SR-3 验收要点一致 4. `docker php -l` + completion-report 通过