From 71fb91f9bfedc3d5018cfd665227d7d2059c31e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E8=BF=9B?= Date: Mon, 26 Oct 2026 14:42:29 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=BC=E5=87=BA=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=A0=A1=E9=AA=8C=E5=8A=A0=E5=85=A5=E6=8C=89?= =?UTF-8?q?=E7=B1=BB=E5=88=AB=E6=8B=86=E5=88=86=E7=9A=84=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创建导出任务与导出任务列表的 scene 参数用 oneof 写死了允许的场景,order_platform、order_agent、order_online 被参数校验拒绝。 同时修正提案中资产订单接口按授权范围校验企业账号的不准确描述。 Co-Authored-By: Claude Opus 5.5 --- docs/engineering/后台订单按类别拆分列表技术方案.md | 2 +- internal/model/dto/export_task_dto.go | 6 +++--- .../changes/split-admin-order-list-by-category/design.md | 2 +- .../changes/split-admin-order-list-by-category/proposal.md | 2 +- .../specs/admin-order-category-list/spec.md | 6 +++--- .../changes/split-admin-order-list-by-category/tasks.md | 1 + 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/engineering/后台订单按类别拆分列表技术方案.md b/docs/engineering/后台订单按类别拆分列表技术方案.md index 31770196..46740f1b 100644 --- a/docs/engineering/后台订单按类别拆分列表技术方案.md +++ b/docs/engineering/后台订单按类别拆分列表技术方案.md @@ -164,7 +164,7 @@ 现状:后台认证允许企业账号,原列表对非代理账号不加买家条件,店铺范围对企业账号也不生效,接口权限中间件未接入路由,所以企业账号调用 `GET /orders` 与 `GET /orders/:id` 会看到全部订单。下单已拒绝企业账号,取消只允许代理,导出任务不允许企业账号创建。 -做法:`OrderHandler` 增加企业账号校验,原列表、三个新列表、订单详情、购买预检对企业账号返回无权限。校验放在 Handler,不放 Store 的 `applyOrderScope`,因为后者被资产订单查询等其他场景共用。企业账号查看自己资产的订单继续用 `GET /assets/{identifier}/orders`(按资产数据范围校验),不受影响。 +做法:`OrderHandler` 增加企业账号校验,原列表、三个新列表、订单详情、购买预检对企业账号返回无权限。校验放在 Handler,不放 Store 的 `applyOrderScope`,因为后者被资产订单查询等其他场景共用。资产订单接口 `GET /assets/{identifier}/orders` 本次不改;测试环境实测它对企业账号未按企业授权范围校验(企业 4 无授权卡仍能查到卡 11486 的订单),归入企业账号仅限 H5 的后续收口。 这是本方案中唯一的破坏性变更,只影响企业账号。 diff --git a/internal/model/dto/export_task_dto.go b/internal/model/dto/export_task_dto.go index 04a73e75..10aaf0d3 100644 --- a/internal/model/dto/export_task_dto.go +++ b/internal/model/dto/export_task_dto.go @@ -4,7 +4,7 @@ import "time" // CreateExportTaskRequest 创建导出任务请求。 type CreateExportTaskRequest struct { - Scene string `json:"scene" validate:"required,oneof=device iot_card order package agent_wallet_transaction agent_recharge refund exchange commission_record package_traffic_alert expiring_asset operations_activation operations_renewal stop_resume_rule_dry_run polling_pause polling_stop_refused_alert" required:"true" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, expiring_asset:临期资产, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` + Scene string `json:"scene" validate:"required,oneof=device iot_card order order_platform order_agent order_online package agent_wallet_transaction agent_recharge refund exchange commission_record package_traffic_alert expiring_asset operations_activation operations_renewal stop_resume_rule_dry_run polling_pause polling_stop_refused_alert" required:"true" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, order_platform:平台订单, order_agent:代理商订单, order_online:在线支付订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, expiring_asset:临期资产, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` Format string `json:"format" validate:"required,oneof=xlsx csv" required:"true" description:"导出格式 (xlsx:Excel, csv:CSV)"` Query map[string]interface{} `json:"query,omitempty" description:"导出筛选参数(JSON对象,可选);时间筛选固定使用 filters.start_time 与 filters.end_time,取值必须为带显式时区的 RFC3339 秒级时间"` } @@ -22,7 +22,7 @@ type CreateExportTaskResponse struct { type ListExportTaskRequest struct { Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"` PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"` - Scene string `json:"scene" query:"scene" validate:"omitempty,oneof=device iot_card order package agent_wallet_transaction agent_recharge refund exchange commission_record package_traffic_alert expiring_asset operations_activation operations_renewal stop_resume_rule_dry_run polling_pause polling_stop_refused_alert" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, expiring_asset:临期资产, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` + Scene string `json:"scene" query:"scene" validate:"omitempty,oneof=device iot_card order order_platform order_agent order_online package agent_wallet_transaction agent_recharge refund exchange commission_record package_traffic_alert expiring_asset operations_activation operations_renewal stop_resume_rule_dry_run polling_pause polling_stop_refused_alert" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, order_platform:平台订单, order_agent:代理商订单, order_online:在线支付订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, expiring_asset:临期资产, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=5" minimum:"1" maximum:"5" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"` StartTime string `json:"start_time" query:"start_time" description:"创建时间起始(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-01T00:00:00+08:00)"` EndTime string `json:"end_time" query:"end_time" description:"创建时间结束(带时区的 RFC3339 秒级时间,闭区间含该时刻,如 2026-09-30T23:59:59+08:00)"` @@ -33,7 +33,7 @@ type ExportTaskItem struct { ID uint `json:"id" description:"任务ID"` TaskID uint `json:"task_id" description:"任务ID"` TaskNo string `json:"task_no" description:"任务编号"` - Scene string `json:"scene" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` + Scene string `json:"scene" description:"导出场景 (device:设备, iot_card:IoT卡, order:订单, order_platform:平台订单, order_agent:代理商订单, order_online:在线支付订单, package:套餐, agent_wallet_transaction:代理主钱包流水, agent_recharge:代理充值, refund:退款, exchange:换货, commission_record:佣金明细, package_traffic_alert:套餐真流量达量预警, operations_activation:设备激活情况报表, operations_renewal:套餐续费情况报表, stop_resume_rule_dry_run:停复机规则演练明细, polling_pause:不轮询清单, polling_stop_refused_alert:停机被拒告警)"` Format string `json:"format" description:"导出格式 (xlsx:Excel, csv:CSV)"` Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"` StatusName string `json:"status_name" description:"任务状态名称(中文)"` diff --git a/openspec/changes/split-admin-order-list-by-category/design.md b/openspec/changes/split-admin-order-list-by-category/design.md index 0790caf1..f53fd3da 100644 --- a/openspec/changes/split-admin-order-list-by-category/design.md +++ b/openspec/changes/split-admin-order-list-by-category/design.md @@ -57,7 +57,7 @@ 现状:后台认证允许企业账号;原列表只对代理固定买家条件,`applyOrderScope` 对企业账号不加店铺范围(`GetSubordinateShopIDs` 为空即不限),接口权限中间件(`pkg/middleware/permission.go`)未接入路由,因此企业账号调用列表与详情能看到全部订单。下单已拒绝企业账号(`OrderHandler.Create`),取消只允许代理,导出任务不允许企业账号创建。 -做法:在 `OrderHandler` 增加一个内部校验,`List`、三个新列表、`Get`、`PurchaseCheck` 开头调用,企业账号返回 `CodeForbidden`("企业账号无权访问订单")。放在 Handler 而非 Store:Store 的 `applyOrderScope` 被订单相关的其他调用方共用(如资产订单查询、退款),在 Store 拦会影响企业账号合法的资产订单查询。 +做法:在 `OrderHandler` 增加一个内部校验,`List`、三个新列表、`Get`、`PurchaseCheck` 开头调用,企业账号返回 `CodeForbidden`("企业账号无权访问订单")。放在 Handler 而非 Store:Store 的 `applyOrderScope` 被订单相关的其他调用方共用(如资产订单查询、退款),在 Store 拦影响面不可控。资产订单接口对企业账号未按授权范围校验,是企业账号仅限 H5 的后续收口事项。 备选:在 `applyOrderScope` 对企业账号加 `1 = 0`。放弃:影响面不可控,且返回空列表而不是无权限,前端无法区分。 diff --git a/openspec/changes/split-admin-order-list-by-category/proposal.md b/openspec/changes/split-admin-order-list-by-category/proposal.md index 90167cdd..ac146b67 100644 --- a/openspec/changes/split-admin-order-list-by-category/proposal.md +++ b/openspec/changes/split-admin-order-list-by-category/proposal.md @@ -15,7 +15,7 @@ - 代理商订单:后台下单且从代理钱包扣款(`payment_method = wallet`),含代理自购、代理给下级购买、平台代扣代理钱包、代理 OpenAPI 下单。 - 平台订单:后台下单且不走代理钱包,含平台自营、赠送、平台代购(平台代购买家虽记为代理,但线下收款、不扣代理钱包)。 - 新接口与新导出场景的查询参数、分页、响应结构、表头、认证、权限与数据范围与原列表、原导出完全一致,只多一个固定的类别条件。 -- **BREAKING** 企业账号不能访问后台订单接口:原列表、三个新列表、订单详情、购买预检对企业账号返回无权限(下单、取消已不允许企业账号)。修复现有问题:企业账号调用订单列表与详情不受任何数据范围限制,能看到全部订单。企业账号查看自己资产的订单继续使用 `GET /api/admin/assets/{identifier}/orders`。 +- **BREAKING** 企业账号不能访问后台订单接口:原列表、三个新列表、订单详情、购买预检对企业账号返回无权限(下单、取消已不允许企业账号)。修复现有问题:企业账号调用订单列表与详情不受任何数据范围限制,能看到全部订单。资产订单接口 `GET /api/admin/assets/{identifier}/orders` 本次不改(该接口目前对企业账号未按企业授权范围校验,属于企业账号仅限 H5 的后续收口事项,不在本变更范围)。 - 订单详情、取消、购买预检、后台下单的其他行为不变。 ## Capabilities diff --git a/openspec/changes/split-admin-order-list-by-category/specs/admin-order-category-list/spec.md b/openspec/changes/split-admin-order-list-by-category/specs/admin-order-category-list/spec.md index 7bbac57b..bae73c5b 100644 --- a/openspec/changes/split-admin-order-list-by-category/specs/admin-order-category-list/spec.md +++ b/openspec/changes/split-admin-order-list-by-category/specs/admin-order-category-list/spec.md @@ -88,7 +88,7 @@ ### Requirement: 企业账号不能访问后台订单接口 -后台订单接口 `GET /api/admin/orders`、`GET /api/admin/orders/platform`、`GET /api/admin/orders/agent`、`GET /api/admin/orders/online`、`GET /api/admin/orders/{id}`、`POST /api/admin/orders/purchase-check` MUST 对企业账号返回无权限错误,不返回任何订单数据。超管、平台、代理账号的行为不变。企业账号查看自己资产的订单 SHALL 继续通过资产订单接口 `GET /api/admin/assets/{identifier}/orders`,该接口按资产数据范围校验。 +后台订单接口 `GET /api/admin/orders`、`GET /api/admin/orders/platform`、`GET /api/admin/orders/agent`、`GET /api/admin/orders/online`、`GET /api/admin/orders/{id}`、`POST /api/admin/orders/purchase-check` MUST 对企业账号返回无权限错误,不返回任何订单数据。超管、平台、代理账号的行为不变。资产订单接口 `GET /api/admin/assets/{identifier}/orders` 不在本需求范围,行为不变。 #### Scenario: 企业账号调用订单列表 - **WHEN** 企业账号调用 `GET /api/admin/orders` 或任一新列表接口 @@ -98,6 +98,6 @@ - **WHEN** 企业账号调用 `GET /api/admin/orders/{id}` - **THEN** 返回无权限错误,不返回订单详情 -#### Scenario: 企业账号查看自己资产的订单 -- **WHEN** 企业账号调用 `GET /api/admin/assets/{identifier}/orders` 查询本企业资产 +#### Scenario: 资产订单接口不受影响 +- **WHEN** 企业账号调用 `GET /api/admin/assets/{identifier}/orders` - **THEN** 行为与本次变更前一致 diff --git a/openspec/changes/split-admin-order-list-by-category/tasks.md b/openspec/changes/split-admin-order-list-by-category/tasks.md index c439499a..abac9d12 100644 --- a/openspec/changes/split-admin-order-list-by-category/tasks.md +++ b/openspec/changes/split-admin-order-list-by-category/tasks.md @@ -16,6 +16,7 @@ - [x] 3.1 `OrderDataSource` 增加类别字段,`NewOrderDataSource(db, category)`;`Scene()` 按类别返回场景,类别为空返回 `order`;`applyFilters` 末尾调用 1.1 的函数(别名 `o`)叠加类别条件 - [x] 3.2 `NewDefaultRegistry` 注册三个类别数据源;`timeFilterScenes` 加入三个新场景(`IsSupportedScene` 无调用方,同步加入保持一致) - [x] 3.3 `internal/routes/export_task.go` 场景列表说明补充 `order_platform`、`order_agent`、`order_online` +- [x] 3.4 `dto.CreateExportTaskRequest` 与导出任务列表请求的 `scene` 校验(`oneof`)加入 `order_platform`、`order_agent`、`order_online`(测试环境验证时发现漏改,创建新场景导出被参数校验拒绝) ## 4. 构建与文档