12 KiB
公司隔离验收与交付记录
验收日期:2026-10-09(Asia/Shanghai)。范围:实现计划 Task 8;北京/武汉双公司主数据、库存、单据、申请审批、切换生命周期及实时订阅。
环境与数据边界
- 真实数据库为本机 MySQL 8.0.34。
TEST_DB_DSN显式指定company_isolation_test前缀;测试各自创建随机后缀数据库,结束自动删除,不使用业务库。 - 路由验收夹具使用北京/武汉(301/302)、不同分类/单位/规则、同编码
SHARED的不同物料;仅北京、仅武汉、两公司、超级管理员四类账号。前端浏览器库使用另一组独立合成数据(201/202,SAME-001,初始余额 10/50)。 - 武汉尚无历史数据,用户于本次会话明确确认。因此武汉真实历史只读副本检查及授权回填清单为不适用,无执行对象,没有用合成数据冒充武汉真实数据。将来已有库升级仍须完成以下检查和业务授权确认。
- 浏览器验收服务及延迟代理仅用于本机隔离库;凭据、临时种子和运行日志留在 Git 忽略的
.dev-run/task8/,不提交真实密码、JWT 或数据库备份。
自动化场景与主测试入口
| 场景 | 主入口 | 验证内容 |
|---|---|---|
| 授权/请求边界 | internal/handler/company_isolation_integration_test.go |
四类账号经真实登录、路由、认证和组织中间件;缺失/非法头 400,未授权/公司停用 403,授权内跨公司 ID 404 |
| 主数据 | 同上 | 分页总数、编码搜索、分类/单位/规则读写、物料创建/更新/作废/恢复和日志;跨公司引用 400 且不推进编码流水;请求体伪造归属不起作用 |
| 完整业务链 | 同上 | 两公司分别入库→库存→申请→自动审批出库→出库查询;人工出库修改/删除冲销;预警、流水、申请取消/驳回均按公司隔离 |
| 单据冲销 | internal/handler/order_flow_integration_test.go |
入出库编辑/删除仅改变本公司;不足库存、停用仓库失败不留下半单或孤立流水 |
| 库存并发 | internal/repository/stock_test.go |
真实 MySQL 并发条件扣减不透支;读取、预警、总数、流水与详情限定公司 |
| 审批事务 | internal/service/application_organization_db_test.go及新增路由套件 |
原有不足库存/顺序重复审批场景复用;新增同时审批仅一次成功,MySQL trigger 注入流水写入故障后申请/余额/单据/流水一起回滚,解除故障后可重试 |
| 库存配置 | 新增路由套件 | 无仓库、无库位、仓库停用、库位停用四种 409 和具体提示;历史读取及不扣库存操作仍可执行 |
| 授权即时生效/审计 | organization_context_integration_test.go、user_organization_integration_test.go |
下一请求重新加载角色/授权;审计写失败使授权事务回滚 |
| 实时订阅 | 新增路由套件及 websocket_test.go |
真实 TCP 双公司 WebSocket 不互串;授权撤销、角色撤销、公司停用、登出后握手/投递失败;旧票据及旧令牌失效 |
| 退出登录会话 | logout_session_test.go |
当前会话持久撤销,其他会话保留,旧票据及连接不再投递,重载持久撤销记录后旧 JWT 仍无效 |
| 前端竞态与生命周期 | organization.test.js、organizationContext.test.js、MaterialsOrganizationLifecycle.test.js、companySocket.test.js |
A→B→A 版本校验、取消旧读取/错误、保存互斥、脏表单取消/确认、store 清理、深链接回列表、仓库/库位清理、旧连接回调失效 |
| 升级/约束 | company_isolation_integration_test.go(repository)及 generator 测试 |
空库与升级结构一致;历史申请和流水归属回填、对账;异常历史阻断;重复执行保护;默认公司/仓库/库位唯一约束含并发创建;检查脚本嵌入同步 |
按编码读取的 HTTP 入口是列表的 search 参数,当前没有独立的按编码详情路由。仓储并发/不足库存等已存在的场景沿用原测试,没有另建重复套件。
浏览器验收记录
本次使用构建后的 Vue 页面及真实后端/MySQL,另用本地反向代理延迟指定接口响应。结果记录以实际浏览器操作为准;精确上下文版本、脏表单分支另由代表性页面自动化断言。
| 操作 | 结果 |
|---|---|
| 登录默认北京→武汉→北京 | 公司、仓库、库位、同编码物料名称/分类/单位及库存 10/50 正确切换;切换立即清空旧表格 |
| GET 库存响应延迟 8 秒,快速北京→武汉→北京 | 等待结束只出现最终北京数据,未残留武汉行或旧错误提示;旧请求与 WebSocket 被关闭/替换 |
| PUT 预警保存响应延迟 10 秒 | 保存按钮进入等待并禁用,公司切换器同时禁用,完成后恢复;库存归属保持北京 |
| 未保存内容 | 弹窗遮罩阻挡直接操作公司切换器;取消/确认放弃分支由真实组织 store + 代表性物料页面测试验证 |
| 入库与自动出库 | 北京显示 IN-00000001 / OUT-00000001,武汉显示 IN-00000002 / OUT-00000002;各页只出现本公司记录 | | 申请与审批 | 合成申请 browser-company-201/202 分别显示北京/武汉物料;审批页切换清空旧行;自动出库经真实 API 执行后页面显示对应单据 | | 库存流水、日志 | 北京流水仅物料 201,包含入库 +1、申请出库 -1;日志页正常加载。完整双公司流水与日志断言见 MySQL 路由测试 | | 刷新 | /stock、/approval、/outbound 刷新成功,重新验证授权并恢复本账号当前公司;未知 API 和资源仍返回 404 | | 撤销授权 | 移除仅武汉测试账号授权后,下一库存查询 403;刷新后显示“暂无可用公司”和选择屏障,旧业务页面不挂载。测试后恢复夹具授权 | | 停用公司 | 临时停用武汉后重新登录只进入启用的北京公司;测试后恢复武汉状态。已连接用户的下一请求/投递拒绝由真实 MySQL/TCP 套件断言 | | 退出与换账号 | 武汉普通账号退出进入登录页;后端记录 POST /api/auth/logout 200,未出现重复过期提示;换管理员进入北京,没有沿用普通账号武汉选择 | | 双标签页实时连接 | 北京/武汉库存页并行,入库后各自显示 11/52,未互串;切换重建连接。授权/角色撤销、公司停用和注销后下一事件关闭旧连接另由真实 TCP 测试精确断言 |
本次验收修复
- 物料日志及按物料查询入出库历史原本对跨公司 ID 返回空数组,现先校验物料归属并返回 404;分类/单位/规则/伙伴/仓库/库位更新的归属缺失错误统一映射 404。
- 原退出登录只清客户端令牌,现持久写入
revoked_tokens(jti保存令牌指纹),服务启动加载撤销记录。JWT 增加随机会话标识,避免同用户同秒登录产生相同令牌。票据和连接绑定会话指纹与 JWT 到期时间,消费、握手和投递复核会话;没有有效角色的订阅者被拒绝。 - 审批页面测试的 Vue Test Utils Teleport 替身反复重建 ElOption 导致 72 个递归更新异常。移除该替身、使用真实 Teleport 后原 4 条断言全部通过;没有忽略或吞掉异常。
- 集成测试连接启用
clientFoundRows=true,与生产连接一致,合法无改动更新保持 200。 - 撤销记录查询使用 Go 驱动一致的时间边界,避免 MySQL 会话时区不同导致重启恢复已撤销会话;增加 +08:00 会话时区真实数据库回归。
- 历史物料归属使用独立 EXISTS 查询,允许旧物料规格/单位为 NULL;三个历史接口真实数据库回归通过。
- 前端退出实际调用注销接口、保留待撤销令牌;App 禁止匿名挂载认证业务页,旧会话延迟 401 不清除新账号令牌。补充页面卸载及会话竞态回归。
- 提供 Vue 前端路由刷新回退,保留 API/资源 404;修正 Windows PowerShell 5 脚本默认路径求值,检查命令可执行。
复现命令与执行证据
在项目根目录设置专用非生产 MySQL DSN;账号需要创建/清理隔离库、建表/触发器权限。不要填生产连接。
$env:TEST_DB_DSN = '<test-user>:<password>@tcp(127.0.0.1:3306)/company_isolation_test?charset=utf8mb4&parseTime=true'
go test ./... -count=1
npm --prefix frontend test
npm --prefix frontend run build
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/sync-company-integrity.ps1 -Check
git diff --check
最终执行结果(2026-10-09):Go 全量退出码 0,208 个顶层测试通过,含子测试共 478 个 pass 事件,测试 skip 0、fail 0;两个无测试包不计为跳过测试。前端 44 个文件、286 条测试通过,未处理异常 0;生产构建退出码 0,保留已有大包体积提示。完整性 SQL 同步检查输出 Integrity SQL embed is current.,git diff --check 通过。
未配置 TEST_DB_DSN 时相关测试会跳过,不能计为真实数据库验收通过。本次已显式设置,真实 MySQL 升级和库存事务测试均执行。Windows 沙箱对本机 TCP 和 Vite realpath 的权限限制曾导致环境错误;获得沙箱外执行许可后运行,权限失败记录不计为业务通过。ExecutionPolicy Bypass 仅用于本次子进程,不修改系统执行策略。
已有库数据检查与升级顺序
- 备份数据库,在副本上运行
scripts/checks/company-master-data-integrity.sql。该脚本使用临时表,不写永久业务数据;若只读账户无法创建临时表,改在该副本派生的可写隔离库执行,不连接生产库做写入验收。 - 保存异常明细和普通用户授权清单。分类父级、物料分类/基本单位/类型、规则分类、仓库/库位、库存维度、单据/申请物料、流水来源必须能明确归属。多个默认公司、多个仓库/库位等异常先交业务确认,不自动删除/合并,也不按公司名称猜归属。
- 普通用户授权清单由负责人确认后,以明确
user_id/organization_id/is_default的 SQL 回填;默认公司必须在启用授权范围内。无确认不能自动授权所有公司。新公司不复制旧公司分类、单位和编码规则。 - 校验
scripts/sync-company-integrity.ps1 -Check,在可写隔离副本执行增量脚本scripts/upgrades/2026-10-08-company-master-data-isolation.sql。MySQL 客户端支持其中的DELIMITER指令;不要把含指令的文件作为单条普通 SQL 发送。 - 核对
schema_migrations中该版本为completed,核对schema_migration_reconciliations的公司余额四种数量及单据/申请/流水before/after。测试已验证合成历史夹具的结构、归属、对账和阻断分支;它不代表真实武汉历史副本。 - 发布时暂停写入、备份,执行确认过的授权回填,以一次协调发布更新前后端;公司组织头后端不能与旧前端长期混用。验证仓库/库位、审计、各角色业务访问及订阅后恢复写入。
scripts/init.sql 仅在空库使用,绝不对已有库重跑。增量 SQL 有版本和运行锁保护;MySQL DDL 隐式提交,running/failed 版本需要核查部分结构或恢复备份,禁止删除版本记录后盲目重跑。
发布限制与回退
HTTP 组织范围来自每次请求验证的公司头,单据/流水保存完整库存维度,申请归属在创建时固定,普通编辑不迁移公司。缺失或停用库存配置不阻挡主数据及历史查询,改变库存的操作返回具体 409;不存在默认公司专属业务限制。
WebSocket 票据及在线连接属于当前单实例服务,重启使票据失效,已撤销令牌通过数据库记录恢复;多实例部署须共享票据和实时会话撤销状态,不能直接复制当前进程缓存方案。
回退保留授权、审计和新增结构。已经向多公司写入后,禁止直接回退到全局读取的旧应用;应暂停业务或向前修复,防止旧代码重新暴露跨公司数据。