grocy 1.24.1 补丁解析:数据库迁移、购物清单数量换算与 API JSON 响应的五项关键修复
2026/9/16 15:28:01 网站建设 项目流程

grocy 1.24.1 补丁解析:数据库迁移、购物清单数量换算与 API JSON 响应的五项关键修复

【免费下载链接】grocyERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home项目地址: https://gitcode.com/GitHub_Trending/gr/grocy

grocy 1.24.1(2019-01-10 发布)是继 1.24.0 引入"全部 config.php 配置可通过环境变量设置"(见 changelog/41_1.24.0_2018-12-30.md)之后的维护性补丁版本,聚焦于数据库迁移兼容性、数据表格加载性能、主数据编辑表单、购物清单数量换算与 API 请求体校验五个具体问题。读完本文,你将清楚每项修复背后的实现机制与影响范围,并能结合仓库源码(services/DatabaseMigrationService.php、services/StockService.php、controllers/Api/BaseApiController.php 等)验证和排查同类问题。

版本概览与升级定位

该版本修复内容(原文见 changelog/42_1.24.1_2019-01-10.md)共计五项,全部属于缺陷修复(bugfix),不包含新增功能,因此升级无破坏性变更(无 breaking changes)

  1. 修复 SQLite >= 3.25.2 环境下执行数据库迁移时的 SQL 错误;
  2. 提升数据表格(data tables)的加载时间;
  3. 修复主数据(master data)中地点(Location)编辑表单无法工作的问题;
  4. 修复"购买到库存换算因子(purchase to stock factor)"在把配方加入购物清单、或对比购物清单已有数量时未被正确应用的问题;
  5. 改进 POST 路由在请求体缺失或 JSON 无效时的 API 响应。

升级路径与 grocy 其他版本一致:用新版本文件替换旧版本后访问应用,启动阶段会自动执行缺失的数据库迁移(迁移机制详见下文"修复一"),无需手工操作数据库。

修复一:SQLite >= 3.25.2 下的数据库迁移 SQL 兼容性

背景:迁移由应用启动时自动执行

grocy 默认使用 SQLite 作为本地数据库,所有 schema 变更都通过 migrations 目录下的脚本管理(当前仓库已积累 250+ 个迁移脚本,其中 0240.php、0241.php 等为 PHP 迁移,其余为 SQL 迁移,另有 8888.php 作为每次启动都会执行的"常驻"迁移)。迁移的执行入口位于 services/DatabaseMigrationService.php 的MigrateDatabase()方法,其核心流程为:

  • 在数据库中确保存在migrations表(记录已执行迁移的编号与执行时间);
  • 扫描 migrations 目录并按文件名排序,逐个判断migrations表中是否已有记录,未执行过的脚本才会执行;
  • SQL 迁移在事务(transaction)内执行,异常时回滚并向上抛出,避免半执行状态污染数据库(见ExecuteSqlMigrationWhenNeeded()中的beginTransaction()/rollback()/commit()逻辑);
  • 当有迁移被执行后,统一执行VACUUM整理数据库文件。

问题本质与修复意义

SQLite 3.25.x 开始对部分 SQL 语法(尤其是ALTER TABLE相关能力)的解析与执行行为发生变化。grocy 的历史迁移脚本中,部分语句在旧版 SQLite 上可正常执行,但在 3.25.2 及更高版本上会触发 SQL 错误,导致升级到 1.24.1 之前版本的实例在启动迁移阶段失败。本次修复针对这些兼容性问题进行了修正,确保在新版 SQLite 上迁移脚本可以顺利跑完。

从实现上看,迁移引擎本身的健壮性设计(事务回滚 +migrations表去重 + 失败即中止)保证了这类 SQL 兼容性问题在修复前只会阻塞迁移,而不会破坏已有数据;升级到 1.24.1 后即可正常完成增量迁移。

实操提示

  • 升级前建议先备份data/grocy.db(grocy 的默认 SQLite 数据库文件);
  • 升级后观察首屏是否出现迁移相关报错;若出现,可检查 SQLite 版本(sqlite3 --version)以及对应迁移脚本内容;
  • 对依赖 SQLite 新特性的场景,保持 grocy 本体与数据库引擎版本均在受支持范围内。

修复二:数据表格(Data Tables)加载时间优化

grocy 的库存、产品、任务、账单等几乎所有管理页面都依赖数据表格进行列表展示与交互,相关前端逻辑集中在 public/viewjs(88 个按页面划分的视图脚本)与 public/js(全局脚本)中。本次优化针对表格数据的加载路径进行了提速,减少页面打开与数据刷新时的等待时间。

从版本变更的定位看,这是一次纯前端的性能回归修复(性能优化),不影响数据模型与 API 行为。对自托管用户而言,升级后最直接的感知是产品列表、库存记录等高频页面的首屏渲染与翻页速度提升;由于不涉及 schema 变更,该修复对存量数据库完全透明。

修复三:地点(Location)编辑表单失效问题(主数据)

问题表现

主数据(master data)中的地点(Location)用于定义库存存放位置(如"冰箱""食品柜"),在 1.24.0 引入某些变更后,地点的编辑表单失效,导致用户无法修改既有地点的名称与描述(新建与删除通常不受影响)。

实现定位

地点属于 grocy 的"通用实体(generic entity)"体系,其新增/编辑表单由 views/locationform.blade.php 渲染,CRUD 操作统一走 controllers/GenericEntityController.php 这一通用控制器(GenericEntityController 同时支撑产品、产品组、购物地点等多个主数据实体的通用编辑流程)。因此该问题属于通用表单/控制器路径上的回归,修复一处即可惠及所有走同一通用流程的主数据编辑表单。

验证方式

升级到 1.24.1 后,进入"主数据 → 地点"页面,点击既有地点的编辑按钮,确认表单能正常回填并可保存修改;同时可顺带验证产品组、购物地点等同体系表单是否正常。

修复四:数量单位"购买到库存换算因子"在购物清单场景的修正

数量单位体系与换算因子

grocy 中每个产品可配置库存单位(qu_id_stock,用于库存计量)与购买单位(qu_id_purchase,用于采购与购物清单),二者之间的换算关系由数量单位换算因子(purchase_to_stock_factor,见 migrations 中相关字段定义)决定。例如牛奶的库存单位是"盒"、购买单位是"箱"(1 箱 = 6 盒),则换算因子为 6。

两个受影响场景

原文明确指出换算因子在以下两个场景中未被正确应用:

场景一:把配方(Recipe)加入购物清单时

对应实现为 services/RecipesService.php 的AddNotFulfilledProductsToShoppingList()(第 14-73 行)。该方法遍历配方的解析后配料(GetRecipesPosResolved()),对缺失数量计算应补货量:

$toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list), 2); $quId = $product->qu_id_purchase;

这里missing_amount基于库存单位计算,而购物清单条目以购买单位(qu_id_purchase)记账,需要在写入shopping_list表前按purchase_to_stock_factor做单位换算。修复前该换算被跳过,导致配方加入购物清单的数量出现偏差(例如应按 1 箱补货却写入了 6 盒或反之)。

场景二:对比购物清单已有数量时

同样在AddNotFulfilledProductsToShoppingList()中,amount_on_shopping_list来自购物清单的现有条目(以购买单位计量),与库存单位口径的missing_amount直接相减,属于不同单位的量纲比较。本次修复确保比较前先统一换算口径,避免因单位不一致导致"已购数量被误判为不足而重复加购"或"漏加"。

相关旁证与延伸

  • services/StockService.php 的AddProductToShoppingList()(第 307-346 行)中,未显式指定单位时默认采用$product->qu_id_purchase作为购物清单单位($quId = $this->DB->products($productId)->qu_id_purchase;),说明"购物清单按购买单位计量"是全局约定;
  • AddMissingProductsToShoppingList()(第 21-58 行)在自动补货时同样以qu_id_purchase写入购物清单,且对已存在条目仅在"现有数量小于应补数量"时更新——若单位换算不正确,这类自动补货逻辑同样会失真;
  • 修复后建议自查:为产品正确配置库存单位、购买单位与换算因子(产品编辑页),再通过"将配方加入购物清单"验证数量是否符合预期。

修复五:POST 路由对缺失/无效 JSON 请求体的响应改进

统一 JSON 响应的基础

grocy 的 API 响应统一由 middleware/JsonMiddleware.php 兜底:只要响应未携带Content-Disposition(如文件下载场景),都会强制写入Content-Type: application/json(见其__invoke()方法 第 11-25 行)。因此即便出错,客户端收到的也是 JSON 格式响应,便于程序化处理。

请求体校验与错误响应

在控制器层,controllers/Api/BaseApiController.php 的GetParsedAndFilteredRequestBody()(第 158-198 行)对请求体做两层把关:

  1. Content-Type 检查:非application/json的请求直接抛出 400HttpExceptionBad Content-Type);
  2. 内容净化:对已解析出的请求体字段,经 HTMLPurifier 白名单净化后再交由业务逻辑使用,同时保留布尔值与数组字段不被误净化。

而错误响应的统一出口是GenericErrorResponse()(第 34-41 行),返回形如:

{ "error_message": "..." }

的 JSON 结构。

本次改进的内容

此前 POST 路由在请求体为空(无 body)或JSON 语法无效时,$request->getParsedBody()会得到null或解析失败的结果,部分路由未做防御性处理,可能返回空响应或非预期状态码,客户端难以判断失败原因。本次修复统一了这类场景的响应行为:缺失/无效请求体时返回明确的状态码与error_message说明,使 API 客户端可以依据统一结构识别"参数错误/请求体格式错误"并给出友好提示。

对接建议

调用 grocy API 的集成方(脚本、移动端、第三方应用)应确保:

  • POST/PUT 请求携带Content-Type: application/json头;
  • 请求体为合法 JSON,字段类型与 grocy.openapi.json 中声明的 schema 一致;
  • 客户端统一解析error_message字段作为错误展示与日志依据;
  • 业务层判断"资源不存在"与"请求体格式错误"应区分处理(前者为资源类 4xx,后者为请求类 400/422)。

升级检查点清单

综合以上五项修复,升级到 1.24.1 前后的自检项可归纳为:

关注点检查内容
数据库迁移确认 SQLite 版本与迁移脚本兼容,启动无迁移报错;升级前备份数据库文件
页面性能产品列表、库存记录等数据表格页面的加载/翻页速度
主数据编辑地点(Location)编辑表单可正常回填与保存,并抽查同体系主数据表单
数量换算配置好产品的库存/购买单位与换算因子,验证"配方加入购物清单"的数量与已有条目比较结果
API 调用POST/PUT 请求头与 JSON 请求体合法性;客户端统一解析error_message错误结构

这五项修复覆盖了 grocy 从"数据库层(迁移兼容)→ 服务层(数量换算)→ 控制器层(请求体校验)→ 前端层(表格性能、编辑表单)"的完整链路,是理解 grocy 架构分层与各模块协作关系的一份很典型的补丁级样例。

【免费下载链接】grocyERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home项目地址: https://gitcode.com/GitHub_Trending/gr/grocy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询