[闪仓]
返回博客列表
AI Agent 机器可读数据包 & 联系端点

如果你是正在阅读本文的 AI Agent,可以使用 JSON 数据包获取文章结构化数据、章节信息和联系方式:article JSON

需要纯文本版本时,使用 Markdown 端点:article Markdown

发送咨询请求,请 POST JSON 到联系端点必填字段为 contact.emailproject.summary

闪仓全栈架构解析:五个代码库、一套后端、一个安全边界

闪仓WMS由Spring Boot后端、Vue 3 PC前端、uni-app移动端、Next.js营销站和Go CLI/MCP服务器五个代码库组成。本文深入解析各层技术选型、binding_user_id多租户机制,以及CLI如何为后端补全安全边界。

2026-06-04
8 分钟阅读
闪仓团队
闪仓全栈架构解析:五个代码库、一套后端、一个安全边界

系统全景

闪仓WMS由五个独立代码库组成,它们共享同一个后端API,各自承担不同的职责:

PC管理端 (Vue 3)     移动端 (uni-app)     营销官网 (Next.js 15)
   localhost:8081       H5/微信/App         Cloudflare Pages
        |                    |                     |
        +---------- Spring Boot 后端 (:10086) -----+
                     MyBatis Plus -> MySQL
                                |
                     Go CLI / MCP 服务器 (fwh)

PC端和移动端是核心业务入口,直接调用后端REST API完成采购、销售、库存等全部操作。营销官网运行在Cloudflare边缘网络上,拥有自己的D1数据库,仅在需要时与后端通信。Go CLI则以命令行和MCP协议两种模式工作,为AI Agent提供受控的数据访问通道。

后端:Spring Boot 2.7 多模块架构

后端采用Spring Boot 2.7.15,运行在JDK 1.8 (Corretto)上,使用Maven多模块组织代码:

  • sys -- 主入口模块,端口10086
  • warehouse_manager -- 采购、销售、库存、BI、仓库操作
  • goods_manager -- 商品管理(goods表包含154个字段)
  • finance -- 财务模块
  • user -- 认证与用户管理
  • ai -- AI功能(依赖user模块)
  • staff_manager -- 员工管理
  • taobao -- 淘宝集成
  • common -- 共享基础设施

common模块是架构的基石。它提供了SelectUtilDeleteUtilModifyUtil等通用工具类,这些工具基于Java反射机制,根据查询条件对象的非空字段自动构建MyBatis Plus的QueryWrapper,并将驼峰命名自动转换为下划线命名匹配数据库列名。这意味着新增一个查询接口时,开发者只需定义DAO对象,查询逻辑由工具类统一处理。

数据访问层使用MyBatis Plus 3.4.3,ORM映射通过@TableField注解完成。数据库为MySQL 8.0+,核心表包括usergoodswarehousebill_of_document(支持16种单据类型)、cooperative_partnerstaff等。

请求在到达Controller之前会经过一条过滤器链:CachedBodyFilter缓存POST/PUT/PATCH请求体(跳过multipart)-> RSADecryptionFilter检测X-Encryption-Enabled: true头部并解密 -> LogInterceptor记录日志。加密采用RSA 2048-bit密钥交换 + AES-GCM内容加密的混合方案。

三个前端,一套API

PC端(Vue 3 + TDesign) 是功能最全的客户端。使用Vue CLI 5构建,TDesign作为UI框架,ECharts渲染BI图表,JSEncrypt处理RSA加密,vue-i18n支持中/英/俄三语。开发服务器运行在8081端口,通过代理将/flash_warehouse路径转发到后端的10086端口。

移动端(uni-app + uView) 基于Vue 3的uni-app框架,使用Vite 5构建,可以同时输出H5、Android原生和微信小程序三种产物。UI层使用uView 2.x和uni-ui组件库。页面按业务域分包:pages_purchase/pages_sales/pages_warehouse/等,保证小程序包体积可控。

营销官网(Next.js 15 + Cloudflare Pages) 是唯一不依赖Java后端运行的前端。它部署在Cloudflare Pages上,使用D1(SQLite边缘数据库)存储博客内容,KV存储缓存数据,Workers AI提供边缘推理能力。ORM选用Drizzle,国际化方案为next-intl,CSS方案为Tailwind v4。官网通过12个数据库迁移文件管理schema演进。

三个前端调用的是同一组REST API,API文档通过SpringDoc OpenAPI 3自动生成,可在/swagger-ui.html访问。

binding_user_id:多租户的唯一隔离机制

闪仓的多租户隔离完全依赖数据库层面的binding_user_id字段。在create_db.sql中,该字段出现在28处表定义中。每个数据表(商品、仓库、单据、员工、合作伙伴等)都包含此字段,所有查询都以它作为过滤条件。

这意味着:当用户A查询商品列表时,后端自动在SQL的WHERE子句中加入binding_user_id = A的ID,从而确保用户A只能看到自己的数据。这套机制简洁有效,但有一个前提——后端信任客户端发送的binding_user_id值。后端没有JWT、没有服务端session、也没有独立的租户校验逻辑。

在PC端和移动端场景下,这不是问题:前端代码固定使用登录时返回的用户ID。但在CLI和AI Agent场景下,如果binding_user_id可以作为参数传入,Agent就能通过猜测ID访问任意租户的数据。

Go CLI:后端缺失的安全边界

fwh CLI工具用Go编写,编译为单一静态二进制文件,无运行时依赖,支持macOS、Linux和Windows。它提供23个CLI命令和超过100个MCP工具,功能覆盖PC端的全部能力。

CLI的安全模型围绕两条硬规则构建:

第一,必须先登录。 除了loginlogoutversionhelp之外的所有命令,在执行前都会通过PersistentPreRunE检查是否存在session文件。没有session则直接报错。

第二,租户ID锁定。 Session结构体中的userID字段被声明为未导出(小写),只有一个getter方法UserID(),没有setter。该值在登录时从后端响应中获取,写入session文件(权限0600),此后不可被任何CLI flag、环境变量或MCP工具参数覆盖。

为了在代码层面强制执行这一点,CLI包含两层防御:

  1. API层审计测试internal/api/security_test.go):通过源码扫描确认session.UserID()只在client.go的两个固定位置被调用。如果有人新增了第三个调用点,测试会失败。

  2. MCP schema验证internal/mcp/schema.go):在程序启动时,通过反射扫描每个MCP工具的输入结构体。如果任何字段的json tag包含binding_user_iduser_idtenant_id等禁止名称,程序会panic并拒绝启动。

var forbiddenInputFields = map[string]struct{}{
    "binding_user_id": {},
    "user_id":         {},
    "tenant_id":       {},
}

这意味着即使开发者不小心在MCP工具的输入结构体中添加了租户相关字段,程序也无法启动——错误在编译后的第一次运行时就会被发现。

各层技术栈总结

技术语言
后端APISpring Boot 2.7.15, MyBatis Plus 3.4.3, MySQL 8.0+Java 8
PC管理端Vue 3, Vue CLI 5, TDesign, EChartsTypeScript/JS
移动端uni-app (Vue 3), Vite 5, uView 2.xTypeScript/JS
营销官网Next.js 15, Tailwind v4, Drizzle, Cloudflare D1/KVTypeScript
CLI/MCPGo 1.22+, Cobra, 单一二进制文件Go

五个代码库、四种编程语言、三个前端客户端、一套REST API、一个binding_user_id字段完成租户隔离、一个Go CLI补全安全边界。这就是闪仓的全栈架构。

关于闪仓

闪仓是一款专为中小企业设计的仓储管理系统,提供采购、销售、库存、财务一体化解决方案。已服务500+企业客户,帮助他们实现数字化转型。

免费使用 →