☰
AI辅助开发库存管理系统:Flask+SQLite并发安全与成本核算
2026/10/23 23:24:43 网站建设 项目流程

库存管理系统这个东西,几乎是每个做业务系统的开发者都绕不开的一道坎:需求看起来简单到一句话就能说清,真动手做的时候才发现坑全在细节里。这次我把整套系统重做了一遍,思路是"AI辅助开发"——用WorkBuddy这类AI编程助手承担样板代码、接口骨架、前端表格渲染这些重复劳动,Python负责把业务逻辑和并发安全的硬骨头啃下来。整套系统覆盖商品档案、多仓库存、出入库流水、加权平均成本、盘点调整、低库存预警和报表导出,代码量不大,但每一行都经得起真实仓库的日常折腾。这篇内容适合三类人看:一是手上有Excel库存表、想做成系统的中小团队开发者;二是想搞清楚"AI写的代码到底能不能用、怎么改才能用"的工程同学;三是接了内部工具需求、预算和时间都紧的独立开发者。下面我把选型理由、表结构、核心事务逻辑、AI协作的具体姿势和踩过的坑,一条条摊开讲。

1. 需求拆解与技术选型:为什么是这套组合

1.1 从一张共享表格说起:库存管理的真实痛点

绝大多数库存系统都是从一张共享表格演化来的:几个仓管各存一份,每天下班前对一次,对不上的时候谁也说不清是哪一笔出的问题。这种模式的根本毛病不在于表格不好用,而在于它只记录了"当前是多少",没有记录"为什么变成这个数"。库存管理的核心资产其实是流水,当前数量只是流水的一个聚合结果。

所以我在做需求拆解的时候,第一件事就是把"当前库存"和"库存流水"拆成两个概念。当前库存是为了查询快,流水是为了追责和对账。这两个东西必须同时存在,任何只做其中一个的方案,半年内一定会被业务方骂。第二个痛点是多仓。哪怕公司只有一个物理仓库,也建议预留仓库维度,因为很快就会出现"待检区""退货区""样品区"这类逻辑仓,事后加字段的成本远高于一开始就留好。

第三个痛点是成本核算。很多自研系统只记数量不记成本,等到财务要算毛利的时候才发现数据全废了。入库单价的记录方式直接决定后面能不能算出加权平均成本,这个决策在表设计阶段就要定死,中途改几乎等于重构。想清楚这三点,需求边界基本就清晰了:商品档案、多仓库存、出入库单据、成本核算、盘点调整、预警和报表。剩下的功能都是锦上添花。

提示:如果你的业务存在"同一个SKU不同批次成本差异很大"的情况(比如生鲜、电子元器件),加权平均就不够用了,需要上批次管理。这个决策一定要在表设计前和业务方确认,不要自己拍脑袋。

1.2 技术栈取舍:Flask加SQLite,够用且好维护

技术选型上我给自己的约束是:单机能跑、零外部依赖、部署成本接近零。库存系统的并发量通常不高——一个中型仓库一天几百到几千笔单据,峰值集中在早晚两个时段,这种量级上MySQL和SQLite的差距可以忽略,但运维复杂度差了一个数量级。

后端选Flask,理由很实际:路由和蓝图机制简单,团队里随便谁接手都能两天内看懂;生态成熟,导出Excel、生成报表这些都有现成库。前端不引框架,就用原生HTML加少量JS,表格渲染直接用服务端模板,理由是这个系统的用户是仓管和财务,他们在意的是打开快、字大、能打印,不是单页应用的丝滑。用React反而会增加构建链路,出问题时排查成本陡增。

数据库用SQLite,但要开WAL模式。很多人对SQLite的印象停留在"玩具数据库",实际上在单机读多写少的场景里它非常能打,而且备份就是拷一个文件,对小团队极其友好。这里有个前提:所有写操作必须走事务,且事务粒度要小。后面第3节会展开讲并发处理的具体做法。

方案部署复杂度适合规模主要风险
SQLite + Flask极低,单文件单仓/多仓,日均千级单据写并发高时锁等待
MySQL + Flask中等,需运维多组织、日均万级运维与备份成本
云数据库 + 前后端分离较高多端协同成本与调试链路长

1.3 AI辅助开发的正确姿势:让AI做脚手架,你做决策

WorkBuddy在这个项目里的定位,我一开始就想清楚了:它不是"帮你写完整个系统"的魔法棒,而是"把重复劳动压缩到几分钟"的加速器。我实际让它承担的工作有三类:生成CRUD接口的样板代码、生成前端表格和表单的HTML结构、根据报错信息给出排查方向。反过来,有三类工作我坚决不让它做:表结构的最终决策、事务与并发逻辑、成本计算规则。

原因是AI生成的代码有一个共性特征——它在"平均值场景"下非常靠谱,在"边界场景"下容易想当然。比如让它写一个出库接口,它会给你先查库存、判断够不够、再更新这样的顺序逻辑,这段代码在单用户测试时百分百通过,一到两个人同时出库同一SKU就会超卖。它不是不会写原子更新,而是它默认你的场景是理想的。

所以我的协作方式是:先自己把数据模型和关键状态机想清楚,写成一段明确的约束说明丢给AI,让它按约束生成;生成之后逐行读,重点看三处——有没有事务、有没有边界判断、有没有把浮点数用来存钱。这三处只要有一处不对,整段重写比打补丁更省事。

2. 数据模型设计:库存系统的地基

2.1 五张核心表与字段取舍逻辑

表结构这块我改了三个版本才定稿。最终是五张表:商品表、仓库表、库存表、流水表、单据表。商品表存SKU、名称、分类、单位、参考售价、安全库存;仓库表存仓库编码和名称;库存表是商品与仓库的多对多关系加上数量;流水表记录每一笔变动;单据表记录一次操作的头部信息(操作人、时间、备注、单据号)。

字段取舍上有几个决策值得说明。第一,金额全部用整数存"分",不用浮点。浮点数在累加几百次之后会出现0.30000000000000004这种经典误差,库存成本一旦有分位误差,财务对账会非常痛苦。第二,时间统一存UTC的ISO字符串,展示层再转本地时区,避免服务器迁移后数据错乱。第三,库存表上给(product_id, warehouse_id)建唯一索引,这是防止重复行的最后一道保险——业务层写错了也不会产生两行库存。

流水表里我特意加了balance_after字段,记录这笔变动之后的结存数量。这个字段是冗余的,理论上可以聚合算出来,但它对排查问题的价值极高:当客户说"我这儿显示的数量不对",你只要看最后一条流水的balance_after和库存表的qty是否一致,就能立刻定位是流水漏记还是库存被脏写。多存一个整数换来的排查效率,非常值。

2.2 为什么流水表一分都不能省

我见过不止一个自研系统为了省事,只更新库存不记流水,理由是"反正能查到单据"。这个想法在有单据的情况下勉强成立,但现实是单据会被删、会被改、会有线下调整、会有历史数据迁移,一旦某个环节绕过了单据,库存就成了一个无法解释的数字。

流水表的设计原则是只增不改不删。任何导致库存变化的操作,都必须写一条流水,包括入库、出库、盘盈、盘亏、调拨、退货、初始化。流水上记录操作类型、关联单据号、变动数量、变动后结存、操作人和时间。有了这个,任何时候出现数量争议,你把某个SKU的流水按时间排一遍,加起来等于当前库存,账就平了;不等的话,缺口在哪个时间点、哪一笔,一目了然。

还有个容易被忽略的点:初始化库存也要走流水。很多系统在导入期初数据时图快,直接塞进库存表,结果后面所有对账都少一个基准点。我的做法是给每一条期初数据生成一条类型为INIT的流水,成本和数量都照常记,这样从第一天起账就是平的。

2.3 建表脚本与初始化配置

下面是核心建表语句,直接在SQLite里跑就行。注意开头那几行PRAGMA,尤其是外键开关,SQLite默认是关的,很多人不知道这一点,结果外键写了等于没写。

PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000; CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, sku TEXT NOT NULL UNIQUE, name TEXT NOT NULL, category TEXT, unit TEXT NOT NULL DEFAULT '件', price_cent INTEGER NOT NULL DEFAULT 0, cost_cent INTEGER NOT NULL DEFAULT 0, safety_stock INTEGER NOT NULL DEFAULT 0, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE warehouses ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, is_active INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE stock ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL REFERENCES products(id), warehouse_id INTEGER NOT NULL REFERENCES warehouses(id), qty INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL, UNIQUE (product_id, warehouse_id) ); CREATE TABLE stock_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL REFERENCES products(id), warehouse_id INTEGER NOT NULL REFERENCES warehouses(id), biz_type TEXT NOT NULL, biz_no TEXT, change_qty INTEGER NOT NULL, unit_cost_cent INTEGER NOT NULL DEFAULT 0, balance_after INTEGER NOT NULL, operator TEXT, remark TEXT, created_at TEXT NOT NULL ); CREATE INDEX idx_logs_product ON stock_logs(product_id, created_at); CREATE INDEX idx_logs_bizno ON stock_logs(biz_no); CREATE INDEX idx_logs_time ON stock_logs(created_at);

这里有个细节值得单独说:stock表用(product_id, warehouse_id)做唯一约束,同时我给它加了一个id主键。为什么不直接用联合主键?因为后面做分页和按行更新的时候,单列主键写起来更省事,ORM映射也更直观。这是纯粹工程便利性的取舍,不影响正确性。

3. 核心业务逻辑:入库、出库与并发安全

3.1 入库逻辑与加权平均成本的算法

入库要做三件事:加库存、写流水、更新加权平均成本。前两件是显而易见的,第三件决定这个系统能不能算账。加权平均成本的公式是:

新成本 = (原库存量 × 原成本 + 本次入库量 × 本次单价) ÷ (原库存量 + 本次入库量)

注意分母是新总量,如果原库存是0,直接取本次单价,不要出现除零。这个公式看起来简单,但有两个坑必须提前躲开。第一个坑是负数库存:如果历史数据里存在负库存(这在管理松散的公司很常见),加权平均算出来的成本会跑飞,出现负数成本或者天文数字。我的处理是入库时对原库存量做max(qty, 0)保护,同时在后台报表里单独标记负库存行,交给业务方清理。第二个坑是精度:成本用分做整数,除法必然产生余数,我的做法是四舍五入到分,并把"舍入损失"记录在流水上,方便财务追溯。

还有一个业务细节:调拨入库不应该更新平均成本。因为调拨只是货从一个仓移到另一个仓,公司层面的成本没变。如果调拨也走加权平均,成本会在仓库之间来回漂移,最后算不出真实毛利。所以我在biz_type里区分了PURCHASE_IN和TRANSFER_IN,只有前者触发成本重算。

3.2 出库逻辑:一次原子更新胜过十次查询

出库是整个系统最容易出问题的地方。新手写法是先查库存、判断够不够、再更新,这三步之间有窗口期,两个人同时出库就会超卖。正确做法是把判断和更新合成一条SQL,让数据库来保证原子性:

def outbound(conn, product_id, warehouse_id, qty, biz_no, operator, allow_negative=False): now = utc_now_iso() with conn: # 自动提交或回滚 cur = conn.execute( """UPDATE stock SET qty = qty - ?, updated_at = ? WHERE product_id = ? AND warehouse_id = ? AND qty >= ?""", (qty, now, product_id, warehouse_id, qty) ) if cur.rowcount != 1: if not allow_negative: raise StockNotEnough(f"商品{product_id}库存不足,需要{qty}") conn.execute( """UPDATE stock SET qty = qty - ?, updated_at = ? WHERE product_id = ? AND warehouse_id = ?""", (qty, now, product_id, warehouse_id) ) row = conn.execute( "SELECT qty FROM stock WHERE product_id=? AND warehouse_id=?", (product_id, warehouse_id) ).fetchone() balance = row["qty"] conn.execute( """INSERT INTO stock_logs (product_id, warehouse_id, biz_type, biz_no, change_qty, unit_cost_cent, balance_after, operator, created_at) VALUES (?,?,?,?,?,?,?,?,?)""", (product_id, warehouse_id, 'SALE_OUT', biz_no, -qty, 0, balance, operator, now) ) return balance

关键在那句AND qty >= ?。它把"库存够不够"的判断交给了数据库引擎,在行锁的保护下完成,rowcount返回0就说明条件不满足。这一招在SQLite和MySQL上写法略有差别但思路通用,是我做库存类系统十几年最舍不得换的一个模式。

allow_negative参数是给特定场景留的口子。有些公司允许先出库后补货(比如预售),这种情况下负库存是合法业务状态,不能一刀切拒绝。但一定要把它做成显式开关,而不是默认放开,同时流水里要能看出来哪些是负库存出库。

3.3 事务边界与并发:把锁等待时间压到毫秒级

用了原子更新不等于线程安全就万事大吉,事务粒度才是决定并发表现的关键。我踩过的一个坑是:把"更新库存 + 写流水 + 更新单据状态 + 写操作日志"全部包在一个大事务里,然后里面还夹了一次网络请求(调用外部接口校验),结果高峰期出现大量database is locked。

解决思路有三条。第一条,事务里只允许数据库操作,任何可能超时的外部调用必须挪到事务外面。第二条,保持加锁顺序一致,多个表都要更新时,先更新库存再写流水再更新单据,所有入口按同一个顺序走,避免死锁。第三条,SQLite上开启WAL并把busy_timeout设到5000毫秒,让写冲突自动重试而不是立刻报错。

批量操作还要额外处理。比如批量出库100个SKU,如果每条都开一个事务,SQLite的写锁开销会非常明显。我的做法是整个批次一个事务,但单条失败时记录错误并继续,最后汇总成功和失败明细返回给前端。这样既保证了吞吐,又不会因为一条数据有问题导致整批回滚——仓管最烦的就是"提交了50条,全部失败,还得重新填"。

注意:批量操作里千万不要用executemany去更新带条件的库存,因为rowcount会变成总数,你无法知道具体哪一条失败。逐条执行虽然慢一点,但可追溯性重要得多。

3.4 盘点与库存调整:让差异有理有据

盘点是库存系统里最容易被轻视的模块。很多系统只提供一个"修改库存数量"的按钮,这等于开了一个后门:谁都能改,改完没痕迹。正确做法是盘点单机制——盘点时录入实盘数,系统算差异,审核通过后生成盘盈或盘亏流水,库存随之调整。

盘盈盘亏的流水必须记录调整原因,常见的原因是漏记、错发、破损、计量误差。我在流水表上加了remark字段并做了枚举提示,让操作人从下拉里选,而不是自由输入。这个小小的约束让后面的差异分析变得可能:按月统计一下破损和漏记的比例,就知道仓库管理该往哪个方向改。

还有一个实操细节:盘点期间要"冻结"该仓库的出库。实现上不需要真的锁表,只要在出库接口里检查该仓库是否存在"进行中"的盘点单,有就拒绝并提示。这个检查成本很低,但能避免"盘着盘着又来一车货"的经典混乱。

4. 接口设计与AI协作:把生成的代码改到能用

4.1 接口分层与返回结构统一

接口这块我采用最朴素的三层:路由层负责参数解析和权限校验,服务层负责业务逻辑和事务,数据层负责SQL。这么分不是为了好看,是为了让AI生成的代码有明确的落位。当我对WorkBuddy说"给我写一个商品查询接口,返回结构统一为 code/message/data",它产出的代码就会自然落在路由层,我只需要检查它有没有越界写SQL。

返回结构一定要在第一天就统一,否则后期前端要写十种不同的解析逻辑。我的约定是:成功返回{"code": 0, "message": "ok", "data": {...}},业务异常返回code为非零的业务码加可读的中文提示,系统异常统一返回500并记录trace id。分页统一返回data: {list: [], total: 0, page: 1, size: 20}。

参数校验我用了一个轻量的装饰器方案,而不是引重型校验库。原因是这个系统的字段不多,手动校验反而更直观,而且AI生成的校验代码经常遗漏"空字符串和None不是一回事"这种细节,自己写一遍更放心。

4.2 AI生成代码的三类典型毛病与修正

用WorkBuddy写了大概两周,我总结出它生成的代码有三类高频问题,几乎每次都要人工修。

第一类是"相信入参"。它生成的函数默认前端传来的数量和单价都是合法正整数,不会做负数校验。而实际业务里,用户复制粘贴带空格、带千分位逗号、甚至是负数,都会稳定复现。所以我的习惯是在服务层入口加一道标准化:去空格、去逗号、转整数、判正负。

第二类是"忽略事务"。生成的多表写入代码往往是连续几条execute,没有包在事务里。只要中间报错,数据就半截。修正方式很简单,在服务层入口用with conn:包起来,但要注意把上下文管理器套在正确的位置上,别套到只读查询里。

第三类是"返回结构漂移"。同一个项目里,AI生成的接口一会儿返回{"data": [...]},一会儿返回{"items": [...]},一会儿直接返回数组。这个必须靠调用方统一约定来治,我在提示词里固定带上返回结构的模板,命中率就会高很多。

4.3 提示词模板:怎么问才拿到能用的代码

这个是我觉得最值得分享的部分。同样的工具,问法不同,产出质量差三倍。我摸索出的模板结构是四段:背景约束、数据结构、明确要求、禁止事项。

举个例子,写报表接口时我这样问:

背景:Flask + SQLite 库存系统,已有表 stock_logs(product_id, warehouse_id, biz_type, change_qty, unit_cost_cent, balance_after, created_at)。 约束:金额单位是分,时间是UTC ISO字符串,所有查询必须走索引。 需求:写一个函数 stats_by_product(product_ids, start, end),返回每个商品的 出库总量、出库金额(分)、退货总量。必须一次SQL聚合,不要循环查询。 禁止:不要用 f-string 拼SQL;不要返回浮点数金额;不要省略空结果的处理。

这段提示词的产出基本可以直接用。核心经验是:把"禁止事项"写清楚比把"需求"写清楚更重要,因为AI的默认习惯就是最省事的写法,你不明确禁止,它一定用字符串拼SQL、一定用浮点数、一定在循环里查数据库。

5. 完整实操:从零把系统跑起来

5.1 目录结构与依赖安装

目录结构保持扁平,一眼能看到全部文件。这样做的原因是这类内部工具的维护者经常换人,层级太深会增加理解成本。

inventory/ app.py # 入口与蓝图注册 db.py # 连接管理、PRAGMA设置 services/ inbound.py outbound.py stocktake.py report.py templates/ index.html product.html static/app.js data/inventory.db requirements.txt

依赖只有四个:Flask、openpyxl(导出Excel)、python-dateutil(时区处理)、pytest(测试)。安装一行搞定:

python -m venv venv && source venv/bin/activate pip install flask openpyxl python-dateutil pytest

db.py里最重要的一段是连接初始化,每个连接都要把PRAGMA重新设一遍,因为SQLite的PRAGMA是连接级别的,不是库级别的——这点很多人搞错,以为在初始化脚本里设一次就全局生效了。

import sqlite3 def get_conn(db_path): conn = sqlite3.connect(db_path, timeout=5.0, isolation_level=None) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") conn.execute("PRAGMA busy_timeout=5000") conn.execute("PRAGMA synchronous=NORMAL") return conn

isolation_level=None是关掉Python的隐式事务管理,改成手动控制,用with conn:显式开启事务。这样事务边界完全由业务代码决定,不会出现"以为提交了其实没提交"的诡异情况。

5.2 期初数据导入与第一次对账

系统上线前必须做一次期初导入。我的做法是让财务提供一份Excel,包含SKU、仓库、数量、单价四个字段,然后用一个一次性脚本导入,每条都生成INIT类型的流水。

导入脚本要注意三件事:一是全量校验先行,把所有行先过一遍,发现SKU重复、数量为负、单价为空的直接报出来,不要边导边报,那样导到一半失败很难恢复;二是导入前清空环境,避免重复导入导致库存翻倍;三是导入完成后立刻跑一次对账,校验stock.qty与SUM(stock_logs.change_qty)是否全部相等。

对账SQL很简单,但建议做成一个定时任务每天凌晨跑一次,有差异就发通知:

SELECT s.product_id, s.warehouse_id, s.qty, COALESCE(SUM(l.change_qty), 0) AS log_sum FROM stock s LEFT JOIN stock_logs l ON l.product_id = s.product_id AND l.warehouse_id = s.warehouse_id GROUP BY s.product_id, s.warehouse_id HAVING s.qty <> COALESCE(SUM(l.change_qty), 0);

这条查询在流水上百万行时会慢,所以我在stock_logs上建了(product_id, created_at)的联合索引。如果还是慢,可以改成增量对账——只核对最近7天有变动的商品,历史数据按月核一次。这个折中方案在我们日均两千单的场景下完全够用。

5.3 低库存预警与报表导出

低库存预警是最容易做出价值的小功能。规则很简单:qty < safety_stock就算预警,但要注意两个细节。第一,安全库存应该支持按仓设置,因为中心仓和前置仓的补货节奏完全不同。我在products表上放了一个全局默认值,如果后续需要细粒度控制,再加一张stock_policy表,不需要现在就把表结构复杂化。

第二,预警要做去重和抑制。如果每天推一次"某商品库存不足",推一个月之后就没有人看了。我的做法是分级:低于安全库存推送一次,低于安全库存的30%再推送一次,并且同一个商品7天内不重复推送。实现上就是查询流水表里最近一次该商品的预警记录,简单有效。

报表导出用openpyxl流式写入,不要一次性把所有行读进内存。这个系统单据量不大,但导出一年流水也有几十万行,一次性加载会让内存飙到几百兆。做法是分页查询,每1000行写一批,最后save。实测导出十万行在普通笔记本上大概三秒,完全可以接受。

6. 常见问题与排查技巧实录

6.1 高频问题速查表

下面这些问题都是我在实际项目中真实遇到并解决过的,按出现频率排序:

现象可能原因排查动作解决办法
库存数出现超卖先查后改,非原子更新看是否存在并发压测改为条件更新qty >= ?判断 rowcount
报 database is locked事务过大或长事务占用写锁检查事务内是否有外部调用缩小事务、开WAL、设busy_timeout
成本出现负数或异常大负库存参与加权平均查该商品历史流水对原库存做 max(qty,0) 保护
金额有分位误差用浮点存金额全局搜索 FLOAT/REAL统一改整数分,展示层除100
外键约束不生效PRAGMA 未开或连接级失效执行PRAGMA foreign_keys每个新连接都要设置一次
导出Excel内存爆一次性加载全表看导出时内存曲线分页流式写入
中文导出乱码编码未指定检查写入时的 encoding统一用 utf-8-sig
时间显示差8小时直接存本地时间看库里时间字符串存UTC,展示层转换

表格里"成本出现负数"那条我印象最深。当时是一家客户的期初数据里有二十多个SKU是负库存,加权平均一算,成本直接变成负数,报表上出现了负毛利。定位花了半小时,修复只花了十分钟——加一行max(qty, 0)就行。但教训是:脏数据不会因为你写了保护代码就消失,必须在导入阶段做强校验。

6.2 三个不容易想到的避坑技巧

第一个技巧关于备份。SQLite的备份不要用文件拷贝,因为WAL模式下还有-wal和-shm两个附属文件,直接拷主文件可能拿到不一致的数据。正确做法是用VACUUM INTO,一条命令生成一个干净快照:

VACUUM INTO '/backup/inventory_20240101.db';

这个命令是原子操作,执行期间读不受影响,生成的备份文件可以直接拿去用。我把它写进每日定时任务,保留最近30天,成本几乎为零。

第二个技巧关于索引陷阱。AI生成查询时特别喜欢在字符串字段上用LIKE '%关键字%',这种前导通配符用不上索引,数据量一大就全表扫。我的处理是把搜索拆成两类:SKU和单据号这种精确或前缀匹配的走索引;商品名称这种模糊搜索,如果数据量在几万行以内就接受全扫,多了就上FTS5全文索引。这个判断要提前做,不要等到卡了才补。

第三个技巧关于测试。库存系统的测试重点不是"正常流程能不能跑通",而是"并发下会不会错"。我写了一个简易的并发测试脚本,开10个线程同时对同一个SKU出库,每个线程出1件,初始库存50件,最后校验库存必须是40,流水总数必须是10条,且SUM(change_qty)等于 -10。这个测试跑起来只要两秒,但抓出过两次我自认为没问题的代码里的并发漏洞。

7. 上线之后的几点真实体会

系统跑了三个月之后,我回头看这个项目,有几件事和最初的预判不太一样,值得写下来。

第一件是AI辅助开发的实际收益比预期低,但方向是对的。我原以为能省掉70%的编码时间,实际大概省了40%。省下来的主要是样板代码和前端表格,而真正耗时的部分——事务设计、并发验证、数据清洗、业务方沟通——AI帮不上忙。但它确实把我的注意力从"怎么写"转移到了"怎么设计",这个转变本身价值很大。

第二件是业务方的关注点和开发者完全不同。我花了大量时间做加权平均成本、流水对账、并发安全,业务方最满意的功能是"按SKU搜索时能直接看到在哪个仓、还剩多少"和"一键导出当月出入库明细"。这提醒我:内部的工具型系统,第一优先级永远是"看得清、查得快、导得出",账务精确是底线但不产生惊喜。

第三件关于数据治理。系统上线后最大的问题不是代码bug,而是SKU重复创建。同一个商品,A仓管建了一个"螺丝M6",B仓管建了一个"螺丝 m6",中间带个空格,系统认成两个商品,库存就分裂了。后来我做了一个简单的相似度检测,在新建商品时提示"库里已存在相似名称",情况马上好转。这类问题在Excel时代永远发现不了,只有系统化之后才会暴露,也算是一种副作用。

如果再给一次机会,有几件事我会做得更早:一是把对账脚本从第一天就挂成定时任务,而不是等出问题才写;二是给所有金额字段统一加单元测试,杜绝浮点混入;三是把提示词模板沉淀成一个文档给团队共用,让每个人都用同一种方式跟AI协作,产出的一致性会高很多。

最后分享一个我一直在用的习惯。每次让AI生成一段关键业务代码,我都会问它一句:"这段代码在什么情况下会出错?"它给出的答案有一半是废话,但另一半往往能点出我忽略的边界。这比让它直接写代码有用得多。工具的价值不在于替你思考,而在于逼你把思考写清楚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询