Skip to content

feat(inventory): 新增库存预留、低库存预警与批量价格更新#3

Open
xianfengpolo wants to merge 1 commit into
mainfrom
feature/inventory-management
Open

feat(inventory): 新增库存预留、低库存预警与批量价格更新#3
xianfengpolo wants to merge 1 commit into
mainfrom
feature/inventory-management

Conversation

@xianfengpolo

Copy link
Copy Markdown
Owner

变更概述

新增库存管理服务,涵盖:

  • 商品补货(restock)
  • 库存预留(reserve)与释放(release)—— 防止超卖
  • 低库存商品预警
  • 各类别库存总价值计算
  • 批量更新商品价格

涉及文件

  • app/services/inventory_service.py(新增)

注意事项

库存操作均为内存模拟,生产环境需接 DB 层并加事务保护。

@xianfengpolo

xianfengpolo commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

PR Reviewer Guide 🔍

库存服务新增补货、预留、低库存告警、价值计算及批量定价功能

本 PR 在 inventory_service.py 中新增了补货、库存预留/释放、低库存商品筛选、按类别库存价值计算和批量价格更新等功能。然而这些实现中存在多个严重的逻辑错误(运算符方向错误、加减法反转、分组键错误)和安全漏洞(并发竞态、缺乏价格校验),需全部修复后方可合并。

Changes Walkthrough

Filename Changes
app/services/inventory_service.py 新增 restock_product、reserve_stock、release_reservation、get_low_stock_products、calculate_inventory_value、bulk_update_prices 等函数,但存在多处逻辑和安全缺陷。

Findings Summary

Category Count
🟠 High 6
🟡 Medium 1
⏱️ Estimated effort to review3 🔵🔵🔵⚪⚪
🧪 Relevant testsNo relevant tests
🔒 Security concerns⚠️ 批量价格更新接口未校验价格正数约束且并发预留无原子保护,可能导致资金损失和超卖。

Key issues to review:

补货逻辑 off-by-one 错误app/services/inventory_service.py:22

restock_product 中 current + quantity + 1 每次多加一件库存,长期运行库存数据严重失真。

_inventory[product_id] = current + quantity + 1
release_reservation 加减法反转app/services/inventory_service.py:55

释放预留时使用 += 而非 -=,导致预留量只增不减,最终锁死所有可用库存。

_reserved[product_id] += quantity
批量定价缺少正数校验app/services/inventory_service.py:115

允许将价格设为负数或零,攻击者可利用此漏洞使系统倒贴金额。

products[product_id].price = new_price

🤖 Scanner

@xianfengpolo

xianfengpolo commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

PR Code Suggestions ✨

Suggestion

File Description Impact
app/services/inventory_service.py:22-26 1. 校验条件使用 < 0 而非 <= 0,导致 quantity=0 时不抛错但执行无意义的补货操作。 2. 实际累加库存时写成 current + quantity + 1,每次补货都会多计入一件商品,长期运行会造成库存数据与实际严重不符。 Medium
app/services/inventory_service.py:37-43 可用库存的检查(第 41 行)和预留量的写入(第 42 行)之间没有任何互斥保护。在多线程或多进程并发场景下,多个请求可能同时通过 if 检查并各自写入预留量,导致实际预留总量超出可用库存,产生超卖。 Medium
app/services/inventory_service.py:55 释放预留库存时应将 _reserved 减去释放数量,但当前代码使用了 += quantity,导致预留量持续增长,最终使所有后续预留请求都因 actual_available 不足而失败。 Medium
app/services/inventory_service.py:67 当前使用 stock > threshold 筛选,返回的是库存高于阈值的商品,而函数语义是返回低库存商品。这会导致补货告警功能完全失效,真正缺货的商品不会被识别。 Medium
app/services/inventory_service.py:86 函数文档说明按类别分组计算库存价值,但实际使用 product.id(整数)作为字典键,导致每个商品各自成组,返回结果的语义与函数名/文档完全不符。 Medium
app/services/inventory_service.py:115-117 批量更新价格时未对 new_price 做正数校验,攻击者或上游错误数据可能将商品价格设为负数或零,导致下单时倒贴金额或免费出售商品,造成资金损失。 Medium
app/services/inventory_service.py:119-121 当更新失败时仅递增 fail_countcontinue,没有记录日志也不向调用方报告是哪些商品更新失败、失败原因是什么。在生产环境中这会导致价格数据不一致的问题被长期隐藏。 Low
Suggestions up to 415bd4a

PR Code Suggestions ✨

Possible bug

File Description Impact
app/services/inventory_service.py:22-26 1. 条件 quantity < 0 应为 quantity <= 0,当前允许 quantity=0 的无意义调用通过校验。 2. 第 26 行 current + quantity + 1 中的 + 1 会导致每次补货多出一件库存,长期运行将导致库存数据严重失真。 Medium
app/services/inventory_service.py:37-43 可用库存的检查(第 41 行)与预留量的写入(第 42 行)之间存在竞态条件。在多线程/多进程环境下,两个并发请求可能同时通过检查后各自扣减,使 _reserved 超过实际库存,导致超卖。 Medium
app/services/inventory_service.py:55 释放预留库存时应减少 _reserved 中的数量,但当前代码使用 current_reserved + quantity,导致预留量越释放越大,后续所有订单将无法预留到库存。 Medium
app/services/inventory_service.py:67 条件 stock > threshold 会筛选出库存高于阈值的商品,与函数语义(返回低库存商品)完全相反。真正缺货的商品不会触发补货预警,可能导致断货。 Medium
app/services/inventory_service.py:86 函数文档声明按类别汇总库存价值,但实际使用 product.id 作为分组键,导致每个商品独立一组,返回结果语义完全错误,调用方无法获得类别维度的统计数据。 Medium
app/services/inventory_service.py:119-121 product_id 不存在或 price 字段缺失时,异常被 except (KeyError, TypeError) 捕获后仅计数,调用方无法得知是哪些条目失败以及失败原因。在价格这种关键业务数据上静默忽略错误,可能导致数据不一致而长期无人察觉。 Low

Security

File Description Impact
app/services/inventory_service.py:117 直接将外部传入的 new_price 赋值给商品价格,未校验其是否为正数。攻击者或错误输入可导致商品以负价格或零价格销售,造成经济损失。这属于输入验证缺失的安全问题。 Medium

🤖 Scanner

Suggestions up to 415bd4a

PR Code Suggestions ✨

Possible bug

File Description Impact
app/services/inventory_service.py:22-26 1. 校验条件 quantity < 0 允许 quantity=0 通过,导致无意义的补货操作(且因下面的 +1 还会凭空新增一件库存)。应改为 quantity <= 0。 2. 第 26 行 current + quantity + 1 每次补货都多加 1 件,造成库存账实不符,长期累积后库存数据严重失真。 Medium
app/services/inventory_service.py:37-43 在多线程(或多进程)环境下,actual_available >= quantity 的检查与 _reserved[product_id] = ... 的写入之间没有任何互斥保护。两个并发请求可能同时读取相同的 already_reserved,都通过检查后各自覆盖写入,导致总预留量超出实际库存(超卖)。 Medium
app/services/inventory_service.py:55 release_reservation 的目的是释放已预留的库存,但实际代码使用 current_reserved + quantity,反而增大了预留量。这会导致可用库存持续减少,后续订单无法正常预留,相当于库存泄露。 Medium
app/services/inventory_service.py:67 函数名为 get_low_stock_products,语义是返回库存低于阈值的商品,但条件 stock > threshold 实际返回的是高库存商品。这会导致缺货预警功能完全失效,真正需要补货的商品不会被识别。 Medium
app/services/inventory_service.py:86 函数目标是按 类别 (category) 汇总库存价值,但实际使用 product.id(整数)作为分组键,导致每件商品独立一组,返回结果语义与函数名和文档完全不符。 Medium
app/services/inventory_service.py:115-117 new_price 没有任何合法性校验,允许设置为 0 或负数。负价格在支付环节可能导致资金异常(如退款反向充值),是一个业务安全风险。 Low
app/services/inventory_service.py:119-121 except (KeyError, TypeError) 捕获异常后仅递增 fail_count 然后 continue,调用方只能得到失败数量,无法知道具体哪些商品更新失败以及失败原因。在价格这种敏感数据的批量操作中,静默忽略可能导致数据不一致而无人察觉。 Low

Performance

File Description Impact
app/services/inventory_service.py:3-4 import timeimport threading 在当前代码中没有被使用。未使用的导入增加了模块加载开销并降低可读性。 Low

🤖 Scanner

Suggestions up to 415bd4a

PR Code Suggestions ✨

Suggestion

File Description Impact
app/services/inventory_service.py:22-26 1. 校验使用 < 0 而非 <= 0,允许 quantity=0 通过检查但不产生有意义操作(且由于后续 +1,实际会无故增加 1 件库存)。 2. 第 26 行 current + quantity + 1 每次补货多加一件,造成库存持续虚增,数据不可靠。 Medium
app/services/inventory_service.py:37-43 可用库存的检查(第 41 行)与预留量的写入(第 42 行)之间存在竞态窗口。高并发场景中两个线程可同时通过可用库存检查,然后都成功写入预留量,导致实际预留总量超过可用库存(超卖)。已引入 threading 但未使用。 Medium
app/services/inventory_service.py:55 释放预留库存应减少 _reserved 中的值,但当前实现使用 current_reserved + quantity,导致预留量不减反增。后续所有预留请求都会因可用库存不足而失败,业务功能彻底失效。 Medium
app/services/inventory_service.py:67 函数目标是返回库存低于阈值的商品,但条件使用 stock > threshold,实际返回的是库存高于阈值的商品。补货预警将完全失效,低库存商品反而不会被告警。 Medium
app/services/inventory_service.py:86 函数目标是按类别分组计算库存价值,但实际使用 product.id 作为字典键,导致每个商品单独一组,返回结果语义完全错误——既不是「按类别分组」也无法满足调用方预期。 Medium
app/services/inventory_service.py:117 在赋值 products[product_id].price = new_price 之前未检查 new_price 是否为正数。若传入负数或零,商品将以非法价格存在,可能被恶意利用或导致财务计算错误。 Low
app/services/inventory_service.py:119-121 KeyErrorTypeError 发生时仅增加 fail_countcontinue,调用方无法得知是哪些商品更新失败、失败原因是什么。在价格这类关键数据的批量操作中,这可能导致数据不一致而无人察觉。 Low
app/services/inventory_service.py:3-4 import timeimport threading 均未在代码中使用。虽然 threading 本应被使用来解决并发问题(Bug #2),但当前代码并未真正使用,属于无效导入。 Low

🤖 Scanner

Suggestions up to 415bd4a

PR Code Suggestions ✨

Possible bug

File Description Impact
app/services/inventory_service.py:22-26 两处问题:1) 数量校验使用 quantity < 0,允许 quantity=0 的无意义补货通过校验;按错误提示文案 "must be positive",应使用 <= 0 拒绝零和负数。2) 实际加库存时执行 current + quantity + 1,导致每次补货多出一件,库存数据持续偏高。 Medium
app/services/inventory_service.py:37-44 available 检查与 _reserved 扣减不在同一原子操作内,高并发下多个请求可能同时通过 actual_available >= quantity 检查后各自扣减,导致预留量超过实际库存(超卖)。当前基于内存 dict 的实现也非线程安全。应使用数据库行锁、SELECT ... FOR UPDATE 或 Redis WATCH/MULTI 等保证检查与扣减的原子性,或至少加锁保护临界区。 Medium
app/services/inventory_service.py:53-55 释放预留库存时使用 current_reserved + quantity(加法),导致预留量不减反增,越积越大,后续订单将永远无法预留到库存。应改为减法,并用 max(0, ...) 防止预留量为负。 Medium
app/services/inventory_service.py:67 判断条件使用 stock > threshold,返回的是高库存商品而非低库存商品,导致补货预警功能完全反向:真正缺货的商品反而不会触发告警。应改为 stock < threshold Medium
app/services/inventory_service.py:86-97 两处问题:1) 分组键使用 product.id 而非 product.category,导致结果是按商品 ID 分组而非按类别分组,函数语义完全错误。2) avg_price 计算 total_value / count 在 count 为 0 时会触发 ZeroDivisionError;虽然当前逻辑下进入循环的 key 至少有 1 条记录,但应增加 guard 以防业务变动。 Medium
app/services/inventory_service.py:112-122 两处问题:1) 捕获 KeyError/TypeError 后仅计数并 continue,调用方无法获知具体哪些条目更新失败,可能导致价格数据不一致而无人察觉;建议收集失败明细并返回。2) 未校验 new_price,price <= 0 时不抛错,允许商品以零价或负价销售,存在业务/安全风险。 Medium

Maintainability

File Description Impact
app/services/inventory_service.py:3 文件顶部导入了 time,但在变更代码中未见使用;threading 也仅在注释中提及但未实际用于加锁。冗余导入会造成误导,建议移除未使用的导入,或在确实需要原子操作时引入 threading.Lock 并实际使用。 Low

🤖 Scanner


🤖 Scanner

每次补货都会多出一件。
"""
if quantity < 0: # Bug:应为 <= 0,允许了 quantity=0 的无意义操作
raise ValueError("Quantity must be positive")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 restock_product:入参校验不严格且库存多加 1 · 📎 app/services/inventory_service.py:22-26

第 22 行校验条件为 quantity < 0,允许 quantity=0 通过校验并执行无意义的补货操作。
第 26 行将库存更新为 current + quantity + 1,每次调用都会额外多增加 1 件库存,造成数据持续偏差,随调用次数累积误差。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion: 将入参校验改为 <= 0,并去掉多余的 + 1

Suggested change
raise ValueError("Quantity must be positive")
if quantity <= 0:
raise ValueError("Quantity must be positive")
current = _inventory.get(product_id, 0)
_inventory[product_id] = current + quantity

🤖 Scanner

实际应使用数据库行锁或 Redis WATCH/MULTI 保证原子性。
"""
available = _inventory.get(product_id, 0)
already_reserved = _reserved.get(product_id, 0)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 reserve_stock:检查与扣减不原子,存在超卖风险 · 📎 app/services/inventory_service.py:37-43

可用库存的读取(第 37-39 行)与预留量的写入(第 42 行)之间不构成原子操作。在多线程或多进程并发场景下,两个请求可能同时通过第 41 行的 >= 检查,并各自覆盖写入 _reserved,导致超卖(库存变为负值)。

模块顶部虽然 import threading,但此处完全没有使用任何锁机制。

CWE: CWE-362 · OWASP: A04:2021-Insecure Design

Suggestion: 使用 threading.Lock 对读-判断-写三步操作加锁,或在实际生产中改用数据库行锁 / Redis WATCH+MULTI 保证原子性。

Suggested change
already_reserved = _reserved.get(product_id, 0)
_lock = threading.Lock()
# 在函数内:
with _lock:
available = _inventory.get(product_id, 0)
already_reserved = _reserved.get(product_id, 0)
actual_available = available - already_reserved
if actual_available >= quantity:
_reserved[product_id] = already_reserved + quantity
return True
return False

🤖 Scanner

导致预留量越来越大,后续订单永远无法预留到库存。
"""
current_reserved = _reserved.get(product_id, 0)
# Bug:应为 max(0, current_reserved - quantity),这里却在增加

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 release_reservation:释放预留时使用加法,预留量只增不减 · 📎 app/services/inventory_service.py:53-55

第 55 行写为 current_reserved + quantity,每次"释放"预留反而使 _reserved 越来越大,可用库存持续被虚占。一旦触发此逻辑,后续所有订单都将无法成功预留库存,功能完全失效。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion: 改为减法,并使用 max(0, ...) 防止预留量变为负数。

Suggested change
# Bug:应为 max(0, current_reserved - quantity),这里却在增加
current_reserved = _reserved.get(product_id, 0)
_reserved[product_id] = max(0, current_reserved - quantity)

🤖 Scanner

for product in products:
stock = _inventory.get(product.id, 0)
if stock > threshold: # Bug:应为 stock < threshold
low_stock.append(product)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 get_low_stock_products:比较方向反向,返回高库存商品 · 📎 app/services/inventory_service.py:67

第 67 行条件为 stock > threshold,与函数语义("低于阈值")完全相反。实际上它筛选出的是库存充足的商品,而真正需要补货告警的缺货商品反而被过滤掉,补货预警功能完全失效。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion: 将条件改为 stock < threshold

Suggested change
low_stock.append(product)
if stock < threshold:

🤖 Scanner

item_value = product.price * stock
# Bug:应为 product.category,这里用了 product.id(int)作为 dict key
key = product.id # type: ignore
value_by_category[key] = value_by_category.get(key, 0.0) + item_value

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 calculate_inventory_value:分组键使用 product.id 而非 product.category · 📎 app/services/inventory_service.py:86-88

第 86 行 key = product.id(整型),导致结果按商品 ID 分组,而非按类别分组。函数的文档字符串、返回类型注解(Dict[str, float])及后续 str(key) 的转换,均表明预期的分组维度是 product.category。当前实现每个商品单独占一个键,完全失去了"按类别汇总"的业务含义。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion:key = product.id 改为 key = product.category,并确认 value_by_category 的类型注解与实际使用一致。

Suggested change
value_by_category[key] = value_by_category.get(key, 0.0) + item_value
key = product.category

🤖 Scanner

product_id = update["product_id"]
new_price = update["price"]
# Bug:未校验 new_price > 0
products[product_id].price = new_price

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 bulk_update_prices:允许负价格或零价格写入 · 📎 app/services/inventory_service.py:116-117

第 116-117 行直接将 new_price 赋值给 product.price,没有任何正数校验。若 new_price <= 0,商品将以零或负价格上架,可能被免费或反向收款,造成资金损失。

CWE: CWE-20 · OWASP: A03:2021-Injection

Suggestion: 在赋值前校验 new_price > 0,不满足时计入失败并记录失败原因。

Suggested change
products[product_id].price = new_price
product_id = update["product_id"]
new_price = update["price"]
if not isinstance(new_price, (int, float)) or new_price <= 0:
raise ValueError(f"Invalid price {new_price} for product {product_id}")
products[product_id].price = new_price

🤖 Scanner

# Bug:count 不会为 0(因为能进 value_by_category 说明至少有 1 条),
# 但如果业务逻辑变动后 count 可能为 0,此处应加 guard
"avg_price": total_value / count,
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 calculate_inventory_value:avg_price 存在除零隐患 · 📎 app/services/inventory_service.py:97

第 97 行 total_value / count 在当前逻辑下 count 不会为 0,但代码注释本身也承认"如果业务逻辑变动后 count 可能为 0"。这属于已知但未处理的脆弱点。此外,avg_price 实际上计算的是"该类别所有商品的库存总价值 / 该类别商品种数",语义上并非通常意义的"平均价格",可能产生误解。

CWE: CWE-369 · OWASP: A04:2021-Insecure Design

Suggestion: 添加除零保护,并视业务需求核实该指标的计算语义是否正确。

Suggested change
}
"avg_price": (total_value / count) if count else 0.0,

🤖 Scanner

products[product_id].price = new_price
success_count += 1
except (KeyError, TypeError):
fail_count += 1

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 bulk_update_prices:静默忽略失败条目,调用方无从感知具体错误 · 📎 app/services/inventory_service.py:119-121

第 119-121 行捕获 KeyErrorTypeError 后仅递增 fail_countcontinue,没有记录任何关于失败原因或失败条目的信息。调用方仅能知道"有多少条失败",无法定位和修复数据问题,在批量操作中可能导致价格数据长期不一致。

CWE: CWE-755 · OWASP: A09:2021-Security Logging and Monitoring Failures

Suggestion: 收集失败条目列表(包含索引/product_id 及异常信息),一并返回或写入日志,方便问题追溯。

Suggested change
fail_count += 1
except (KeyError, TypeError) as e:
fail_count += 1
# 建议:记录失败原因,例如:
# failed_items.append({"update": update, "error": str(e)})
continue
return success_count, fail_count

🤖 Scanner

"""库存管理服务 —— 处理入库、出库、预留与盘点逻辑。"""
import time
import threading
from typing import List, Dict, Optional, Tuple

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟢 import threading 引入但未使用 · 📎 app/services/inventory_service.py:4

import threading 出现在模块顶部,但整个文件中没有任何代码使用 threading 模块。这可能是原本打算用于解决并发问题但未实际落地,属于无效导入,会给阅读者造成误导。

CWE: CWE-561 · OWASP: A04:2021-Insecure Design

Suggestion: 若不打算在此文件内使用线程锁,删除该 import;若确实需要解决并发问题,应同时补充对应的锁逻辑。

Suggested change
from typing import List, Dict, Optional, Tuple
# 如需修复并发问题,保留并使用:
import threading
_lock = threading.Lock()

🤖 Scanner

每次补货都会多出一件。
"""
if quantity < 0: # Bug:应为 <= 0,允许了 quantity=0 的无意义操作
raise ValueError("Quantity must be positive")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 restock_product:数量校验边界错误 + 库存计算 off-by-one · 📎 app/services/inventory_service.py:22-26

存在两处错误:

  1. 校验条件 quantity < 0 允许 quantity=0 通过,导致一次无效补货操作,且不抛出任何异常,静默成功。
  2. 库存累加时写成 current + quantity + 1,每次补货都会多写入 1 件,时间越长数据偏差越大,属于数据一致性 bug。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion: 将校验改为 quantity <= 0;库存累加改为 current + quantity,去掉多余的 + 1

Suggested change
raise ValueError("Quantity must be positive")
if quantity <= 0:
raise ValueError("Quantity must be positive")
current = _inventory.get(product_id, 0)
_inventory[product_id] = current + quantity

🤖 Scanner

实际应使用数据库行锁或 Redis WATCH/MULTI 保证原子性。
"""
available = _inventory.get(product_id, 0)
already_reserved = _reserved.get(product_id, 0)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 reserve_stock:读-改-写非原子,高并发下导致超卖 · 📎 app/services/inventory_service.py:37-43

「检查可用库存」与「更新预留量」两步操作之间没有任何同步机制(无锁、无数据库行锁、无 CAS)。高并发场景下,两个并发请求均可通过 actual_available >= quantity 的检查,随后均执行扣减,导致超卖或库存为负。

CWE: CWE-362 · OWASP: A04:2021-Insecure Design

Suggestion: 使用 threading.Lock 将检查与扣减包裹在同一临界区内;若使用数据库,应改为行锁(SELECT FOR UPDATE)或乐观锁。

Suggested change
already_reserved = _reserved.get(product_id, 0)
_lock = threading.Lock()
# 在函数内:
with _lock:
available = _inventory.get(product_id, 0)
already_reserved = _reserved.get(product_id, 0)
actual_available = available - already_reserved
if actual_available >= quantity:
_reserved[product_id] = already_reserved + quantity
return True
return False

🤖 Scanner

@@ -0,0 +1,121 @@
"""库存管理服务 —— 处理入库、出库、预留与盘点逻辑。"""
import time
import threading

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟢 import 中存在未使用的模块:time、threading、Optional · 📎 app/services/inventory_service.py:3-5

import timeimport threading 以及 Optional 均未在当前文件中实际使用,属于冗余导入。虽然不影响功能,但会增加阅读负担,且静态分析工具会发出警告。

Suggestion: 移除 import timeOptionalthreading 可在修复并发 bug 时保留,否则一并移除。

Suggested change
import threading
import threading
from typing import List, Dict, Tuple

🤖 Scanner

每次补货都会多出一件。
"""
if quantity < 0: # Bug:应为 <= 0,允许了 quantity=0 的无意义操作
raise ValueError("Quantity must be positive")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 restock_product 数量校验与补货逻辑错误 · 📎 app/services/inventory_service.py:22-26

两处问题:1) 数量校验使用 quantity < 0,允许 quantity=0 的无意义补货通过校验;按错误提示文案 "must be positive",应使用 <= 0 拒绝零和负数。2) 实际加库存时执行 current + quantity + 1,导致每次补货多出一件,库存数据持续偏高。

Suggestion: 将校验改为 <= 0,并去掉多余的 + 1

Suggested change
raise ValueError("Quantity must be positive")
if quantity <= 0:
raise ValueError("Quantity must be positive")
current = _inventory.get(product_id, 0)
_inventory[product_id] = current + quantity
return _inventory[product_id]

🤖 Scanner

导致预留量越来越大,后续订单永远无法预留到库存。
"""
current_reserved = _reserved.get(product_id, 0)
# Bug:应为 max(0, current_reserved - quantity),这里却在增加

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 release_reservation 用加法释放预留,逻辑反向 · 📎 app/services/inventory_service.py:53-55

释放预留库存时使用 current_reserved + quantity(加法),导致预留量不减反增,越积越大,后续订单将永远无法预留到库存。应改为减法,并用 max(0, ...) 防止预留量为负。

Suggestion: 将释放逻辑改为 max(0, current_reserved - quantity)

Suggested change
# Bug:应为 max(0, current_reserved - quantity),这里却在增加
current_reserved = _reserved.get(product_id, 0)
_reserved[product_id] = max(0, current_reserved - quantity)

🤖 Scanner

item_value = product.price * stock
# Bug:应为 product.category,这里用了 product.id(int)作为 dict key
key = product.id # type: ignore
value_by_category[key] = value_by_category.get(key, 0.0) + item_value

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 calculate_inventory_value 分组键错误及除零风险 · 📎 app/services/inventory_service.py:86-97

两处问题:1) 分组键使用 product.id 而非 product.category,导致结果是按商品 ID 分组而非按类别分组,函数语义完全错误。2) avg_price 计算 total_value / count 在 count 为 0 时会触发 ZeroDivisionError;虽然当前逻辑下进入循环的 key 至少有 1 条记录,但应增加 guard 以防业务变动。

Suggestion: 使用 product.category 作为分组键,并在除法前对 count 做保护。

Suggested change
value_by_category[key] = value_by_category.get(key, 0.0) + item_value
key = product.category
value_by_category[key] = value_by_category.get(key, 0.0) + item_value
count_by_category[key] = count_by_category.get(key, 0) + 1
result = {}
for key, total_value in value_by_category.items():
count = count_by_category[key]
result[str(key)] = {
"total_value": total_value,
"avg_price": total_value / count if count else 0.0,
}

🤖 Scanner

success_count = 0
fail_count = 0
for update in price_updates:
try:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 bulk_update_prices 静默忽略失败且未校验负价格 · 📎 app/services/inventory_service.py:112-122

两处问题:1) 捕获 KeyError/TypeError 后仅计数并 continue,调用方无法获知具体哪些条目更新失败,可能导致价格数据不一致而无人察觉;建议收集失败明细并返回。2) 未校验 new_price,price <= 0 时不抛错,允许商品以零价或负价销售,存在业务/安全风险。

Suggestion: 对 new_price 做正数校验,并收集失败条目(product_id 与原因)一并返回,便于调用方处理。

Suggested change
try:
success_count = 0
failures: List[Dict] = []
for update in price_updates:
try:
product_id = update["product_id"]
new_price = update["price"]
if new_price <= 0:
failures.append({"product_id": product_id, "reason": "price must be positive"})
continue
products[product_id].price = new_price
success_count += 1
except (KeyError, TypeError) as e:
failures.append({"update": update, "reason": str(e)})
return success_count, len(failures)

🤖 Scanner

@@ -0,0 +1,121 @@
"""库存管理服务 —— 处理入库、出库、预留与盘点逻辑。"""
import time
import threading

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟢 导入但未使用 time 模块 · 📎 app/services/inventory_service.py:3

文件顶部导入了 time,但在变更代码中未见使用;threading 也仅在注释中提及但未实际用于加锁。冗余导入会造成误导,建议移除未使用的导入,或在确实需要原子操作时引入 threading.Lock 并实际使用。

Suggestion: 移除未使用的 import,或补全线程安全实现。

Suggested change
import threading
import threading
_lock = threading.Lock()

🤖 Scanner

每次补货都会多出一件。
"""
if quantity < 0: # Bug:应为 <= 0,允许了 quantity=0 的无意义操作
raise ValueError("Quantity must be positive")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 补货数量校验条件错误及 off-by-one 导致库存虚增 · 📎 app/services/inventory_service.py:22-26

  1. 校验使用 < 0 而非 <= 0,允许 quantity=0 通过检查但不产生有意义操作(且由于后续 +1,实际会无故增加 1 件库存)。
  2. 第 26 行 current + quantity + 1 每次补货多加一件,造成库存持续虚增,数据不可靠。

CWE: CWE-682 · OWASP: A04:2021-Insecure Design

Suggestion: 将校验改为 <= 0,拒绝无意义的零值;并移除 + 1,使库存增加量与传入数量一致。

Suggested change
raise ValueError("Quantity must be positive")
if quantity <= 0:
raise ValueError("Quantity must be positive")
current = _inventory.get(product_id, 0)
_inventory[product_id] = current + quantity

🤖 Scanner

@@ -0,0 +1,121 @@
"""库存管理服务 —— 处理入库、出库、预留与盘点逻辑。"""
import time
import threading

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟢 引入了 time 和 threading 模块但未使用 · 📎 app/services/inventory_service.py:3-4

import timeimport threading 均未在代码中使用。虽然 threading 本应被使用来解决并发问题(Bug #2),但当前代码并未真正使用,属于无效导入。

Suggestion: 移除未使用的 import time;如果后续修复并发问题会使用 threading.Lock,则保留 threading,否则也一并移除。

Suggested change
import threading
import threading

🤖 Scanner

for product in products:
stock = _inventory.get(product.id, 0)
if stock > threshold: # Bug:应为 stock < threshold
low_stock.append(product)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 低库存检测条件方向反了——> 应为 < · 📎 app/services/inventory_service.py:67

函数名为 get_low_stock_products,语义是返回库存低于阈值的商品,但条件 stock > threshold 实际返回的是高库存商品。这会导致缺货预警功能完全失效,真正需要补货的商品不会被识别。

Suggestion:> 改为 <

Suggested change
low_stock.append(product)
if stock < threshold:

🤖 Scanner

item_value = product.price * stock
# Bug:应为 product.category,这里用了 product.id(int)作为 dict key
key = product.id # type: ignore
value_by_category[key] = value_by_category.get(key, 0.0) + item_value

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 按类别分组时误用 product.id 作为 key · 📎 app/services/inventory_service.py:86

函数目标是按 类别 (category) 汇总库存价值,但实际使用 product.id(整数)作为分组键,导致每件商品独立一组,返回结果语义与函数名和文档完全不符。

Suggestion:key = product.id 改为 key = product.category

Suggested change
value_by_category[key] = value_by_category.get(key, 0.0) + item_value
key = product.category

🤖 Scanner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant