<< Back to Blog
·6 min read

Flash Warehouse Full-Stack Architecture: Five Codebases, One Backend, One Security Boundary

Flash Warehouse WMS spans five codebases: a Spring Boot backend, Vue 3 PC frontend, uni-app mobile app, Next.js marketing site, and Go CLI/MCP server. This article dissects how they connect, how binding_user_id enforces multi-tenancy, and how the CLI adds the security boundary the backend lacks.

Flash Warehouse Full-Stack Architecture: Five Codebases, One Backend, One Security Boundary

System Overview

Flash Warehouse WMS consists of five independent codebases that share a single backend API, each serving a distinct role:

PC Management (Vue 3)   Mobile App (uni-app)   Marketing Site (Next.js 15)
   localhost:8081          H5/WeChat/App          Cloudflare Pages
        |                       |                        |
        +--------- Spring Boot Backend (:10086) ---------+
                    MyBatis Plus -> MySQL
                               |
                    Go CLI / MCP Server (fwh)

The PC and mobile clients are the primary business interfaces, calling backend REST APIs for purchasing, sales, inventory, and more. The marketing website runs on Cloudflare's edge network with its own D1 database, communicating with the backend only when needed. The Go CLI operates in both command-line and MCP protocol modes, providing AI Agents with a controlled data access channel.

Backend: Spring Boot 2.7 Multi-Module Architecture

The backend runs Spring Boot 2.7.15 on JDK 1.8 (Corretto), organized as a Maven multi-module project with 9 modules:

  • sys -- main entry point, port 10086
  • warehouse_manager -- purchasing, sales, inventory, BI, warehouse operations
  • goods_manager -- product management (the goods table has 154 fields)
  • finance -- financial operations
  • user -- authentication and user management
  • ai -- AI features (depends on user module)
  • staff_manager -- employee management
  • taobao -- Taobao marketplace integration
  • common -- shared infrastructure

The common module is the architectural foundation. It provides utility classes like SelectUtil, DeleteUtil, and ModifyUtil that use Java reflection to auto-build MyBatis Plus QueryWrapper conditions from non-null fields of a query object, converting camelCase names to snake_case for database column matching. Adding a new query endpoint requires only defining a DAO object; the query logic is handled uniformly by these utilities.

The data access layer uses MyBatis Plus 3.4.3 with @TableField annotation-based ORM mapping against MySQL 8.0+. Core tables include user, goods, warehouse, bill_of_document (supporting 16 bill types), cooperative_partner, and staff.

Requests pass through a filter chain before reaching controllers: CachedBodyFilter (caches POST/PUT/PATCH bodies, skips multipart) -> RSADecryptionFilter (checks for X-Encryption-Enabled: true header and decrypts) -> LogInterceptor. Encryption uses a hybrid scheme of RSA 2048-bit key exchange plus AES-GCM content encryption.

Three Frontends, One API

PC Client (Vue 3 + TDesign) is the most feature-complete client. Built with Vue CLI 5, it uses TDesign for UI components, ECharts for BI charts, JSEncrypt for RSA encryption, and vue-i18n for Chinese/English/Russian localization. The dev server runs on port 8081 and proxies /flash_warehouse to the backend on port 10086.

Mobile App (uni-app + uView) is built on the uni-app framework with Vue 3 and Vite 5, producing H5, Android native, and WeChat Mini Program builds from a single codebase. UI uses uView 2.x and uni-ui component libraries. Pages are organized into subpackages by business domain -- pages_purchase/, pages_sales/, pages_warehouse/ -- to keep the mini program bundle size manageable.

Marketing Website (Next.js 15 + Cloudflare Pages) is the only frontend that does not depend on the Java backend to function. Deployed on Cloudflare Pages, it uses D1 (edge SQLite) for blog content, KV for caching, and Workers AI for edge inference. The ORM is Drizzle, internationalization uses next-intl, and styling uses Tailwind CSS v4. The site manages schema evolution through 12 database migration files.

All three frontends call the same set of REST APIs. API documentation is auto-generated via SpringDoc OpenAPI 3, accessible at /swagger-ui.html.

binding_user_id: The Sole Multi-Tenancy Mechanism

Flash Warehouse's tenant isolation relies entirely on the binding_user_id column at the database level. This field appears in 28 table definitions in create_db.sql. Every data table -- goods, warehouses, bills, staff, partners -- contains this column, and every query filters by it.

When User A requests a goods list, the backend automatically adds binding_user_id = A's ID to the SQL WHERE clause, ensuring User A sees only their own data. This mechanism is simple and effective, but it rests on one assumption: the backend trusts whatever binding_user_id value the client sends. There is no JWT, no server-side session, and no independent tenant verification.

For the PC and mobile clients, this is acceptable -- the frontend code always uses the user ID returned at login. But in the CLI and AI Agent context, if binding_user_id were accepted as an input parameter, an Agent could access any tenant's data by guessing IDs.

The Go CLI: The Security Boundary the Backend Lacks

The fwh CLI tool is written in Go, compiled to a single static binary with zero runtime dependencies, and runs on macOS, Linux, and Windows. It exposes 23 CLI commands and over 100 MCP tools, covering the full feature set of the PC client.

The CLI's security model is built around two hard rules:

First, login is mandatory. Every command except login, logout, version, and help checks for an active session file via PersistentPreRunE. No session means an immediate error.

Second, tenant ID is locked. The Session struct's userID field is unexported (lowercase in Go), with only a getter method UserID() and no setter. The value is captured from the backend's login response, written to a session file (mode 0600), and cannot be overridden by any CLI flag, environment variable, or MCP tool argument.

Two layers of defense enforce this at the code level:

  1. API-layer audit test (internal/api/security_test.go): A source-code scan confirms that session.UserID() is called in exactly two fixed locations in client.go. If someone adds a third call site, the test fails.

  2. MCP schema validation (internal/mcp/schema.go): At program startup, reflection scans every MCP tool's input struct. If any field's json tag contains binding_user_id, user_id, tenant_id, or their variants, the program panics and refuses to start.

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

This means even if a developer accidentally adds a tenant-related field to an MCP tool input struct, the program will not start -- the error is caught on the very first run after compilation.

Technology Stack Summary

LayerTechnologyLanguage
Backend APISpring Boot 2.7.15, MyBatis Plus 3.4.3, MySQL 8.0+Java 8
PC ManagementVue 3, Vue CLI 5, TDesign, EChartsTypeScript/JS
Mobile Appuni-app (Vue 3), Vite 5, uView 2.xTypeScript/JS
Marketing SiteNext.js 15, Tailwind v4, Drizzle, Cloudflare D1/KVTypeScript
CLI/MCP ServerGo 1.22+, Cobra, single static binaryGo

Five codebases, four programming languages, three frontend clients, one REST API, one binding_user_id column for tenant isolation, and one Go CLI to close the security gap. That is the full-stack architecture of Flash Warehouse.