Series Article

Day01 · 项目认知 & 系统架构设计

课程目标:从零开始认识跨境出海电商和柔性供应链,理解整个项目的业务全貌,完成项目工程初始化和数据库架构设计。

今日交付物

  • 理解跨境电商和柔性供应链的完整业务链路
  • 理解 SaaS 多租户架构的核心概念
  • 完成 SpringBoot 项目初始化
  • 完成数据库设计规范和公共表结构
  • 完成 Git 仓库初始化

第一节 行业背景认知

1.1 什么是跨境出海电商

跨境电商,简单说就是:中国的商品,卖给全球的消费者。

举个例子:

  • 广州的一家工厂生产了一批蓝牙耳机
  • 通过亚马逊平台,这批耳机被美国、英国、德国的消费者买走
  • 消费者在平台上下单 → 工厂发货 → 国际物流运输 → 消费者收货

这整个过程就叫做跨境出海电商


1.2 主流跨境电商平台介绍

目前市场上主流的跨境电商平台有以下几个,我们的系统需要对接这些平台:

平台主要市场特点对我们系统的需求
亚马逊(Amazon)北美、欧洲、日本合规要求最严格,FBA 仓储体系完善,流量大精细化数据分析、库存管理、FBA 对接
TikTok Shop东南亚、欧美内容电商,直播带货,起量快快速发货、内容与订单一体化管理
Shopee东南亚多国站点、本地化强,需按国家分别管理多站点协同管理、多语言
Temu北美、欧洲拼多多出海,拼低价,管控严格供应链成本控制、快速响应补货
独立站(如 Shopify)全球品牌自建,不受平台管控用户生命周期管理、营销闭环

你可以把这些平台类比为”国际版淘宝/拼多多/抖音”,区别只是消费者在海外,货物需要跨境运输。

FBA = Fulfillment by Amazon 意思是:卖家把货提前发到 Amazon 的仓库,后续有订单后,由 Amazon 负责仓储、拣货、打包、发货、部分客服和退货。 卖家负责选品、备货、运营 Amazon 负责仓储和履约 消费者看到的是 Amazon 配送体验

FBM = Fulfillment by Merchant 意思是: 卖家自己发货

1.3 跨境电商的参与角色

graph LR
    A["🏭 供应商/工厂<br>生产商品"] -->|"提供货源"| B["🛒 跨境卖家<br>我们的客户"]
    B -->|"在平台开店上架"| C["🌐 电商平台<br>亚马逊/TikTok等"]
    C -->|"展示商品"| D["👤 海外消费者<br>美/欧/东南亚"]
    D -->|"下单购买"| C
    C -->|"订单通知"| B
    B -->|"安排发货"| E["🚢 国际物流<br>顺丰/EMS/DHL"]
    E -->|"跨境运输"| D
    B -->|"使用我们的系统<br>管理整个业务"| F["💻 FlexChain<br>供应链 SaaS 平台"]

角色说明:

  • 供应商/工厂:提供货源,负责生产和国内发货
  • 跨境卖家:我们系统的直接用户(客户),负责选品、运营、管理供应链
  • 电商平台:撮合买卖双方的中间平台,抽取佣金(通常 5%-15%)
  • 海外消费者:最终购买商品的用户
  • 国际物流商:负责将商品从中国运到全球消费者手中
  • FlexChain(我们的产品):帮助跨境卖家管理整个供应链的 SaaS 系统

1.4 跨境电商卖家的核心痛点

这是我们做这个系统的根本原因,理解痛点才能理解为什么要设计这些功能。

痛点具体表现我们系统的解决方案
数据孤岛亚马逊、TikTok、Shopee 的订单分散在不同后台,需要人工汇总,容易出错多平台订单统一接入,一个界面管理全部
库存混乱同一商品在多个平台售卖,库存分散不透明,经常超卖或积压实时统一库存管理,自动扣减,超卖防护
补货靠感觉不知道什么时候该补货、补多少,要么缺货断销要么积压资金智能补货建议,基于销售数据自动计算
利润算不清扣除平台手续费、物流费、退货损失后到底赚了多少钱,很难核算SKU 级利润核算,每一分钱去哪了都清楚
供应商难管供应商质量参差不齐,发货拖延、质量问题无法追溯供应商全生命周期管理,绩效评分体系

第二节 什么是柔性供应链

2.1 先理解什么是”供应链”

供应链 = 从原材料到消费者手中,所有环节的连接和协作

一个普通跨境卖家的供应链长这样:

flowchart LR
    A["原材料<br>供应商"] -->|"原料"| B["工厂<br>生产"]
    B -->|"成品"| C["国内仓库<br>备货"]
    C -->|"发货"| D["国际物流<br>运输"]
    D -->|"入仓"| E["海外仓<br>FBA仓"]
    E -->|"配送"| F["消费者<br>收货"]

2.2 传统”刚性”供应链的问题

刚性供应链 就像一条固定的流水线,每个环节提前固定好,很难临时改变。

真实场景举例:

某卖家生产了一款手机支架,提前备了 5000 个到亚马逊仓库。 结果:某网红做了一个测评视频,三天之内 5000 个全部卖光,后续没货,爆款就这样错过了。 另一种情况:预备了 5000 个,但因为某个竞品爆火,这款完全卖不动,5000 个积压在仓库里,资金全部压死。

刚性供应链的核心矛盾:

  • 市场需求变化快(爆款起量快、滞销也快)
  • 供应链调整慢(生产、备货、运输都需要时间)

2.3 什么是”柔性”供应链

柔性供应链 = 能快速响应市场变化,灵活调整产能、库存和物流的供应链体系。

graph TB
    subgraph 刚性供应链
        A1["固定生产计划<br>3个月不变"] --> B1["固定备货量<br>一次备3个月"]
        B1 --> C1["固定物流渠道<br>只用一家快递"]
        C1 --> D1["结果:爆款没货<br>滞销积压"]
    end

    subgraph 柔性供应链
        A2["动态生产计划<br>按销售数据实时调整"] --> B2["智能补货建议<br>按需补货不积压"]
        B2 --> C2["多渠道物流<br>根据时效价格自动选择"]
        C2 --> D2["结果:快速响应<br>降本增效"]
    end

柔性的体现(对应我们系统的功能):

柔性能力含义系统功能支撑
快速响应爆款起量后,系统自动触发补货流程库存预警 + 自动补货建议
多源供货同一商品有多家供应商备选,某家断货立刻切换供应商管理 + 询价比价
弹性仓储可同时使用国内仓、海外仓、FBA 多种仓库多仓库 WMS 管理
动态物流根据包裹重量、目的国、紧急程度自动选最优物流物流渠道智能分配引擎
精准预测基于历史销量数据预测未来需求AI 销售预测模块

第三节 什么是 SaaS

3.1 三种软件交付模式对比

这是理解我们项目商业模式的关键,大家必须搞清楚。

模式全称类比理解特点
传统软件On-Premise买一套软件装到自己电脑上,买断制一次性购买,自己维护,升级麻烦
IaaSInfrastructure as a Service租用服务器,自己装系统和软件如:阿里云 ECS 服务器
PaaSPlatform as a Service租用开发平台,专注写业务代码如:阿里云容器服务
SaaSSoftware as a Service直接使用云端软件,按月付费如:企业微信、钉钉、Salesforce

SaaS 的核心特征:

graph TD
    A["SaaS 软件即服务"] --> B["☁️ 云端部署<br>供应商维护,用户无需安装"]
    A --> C["💳 订阅制付费<br>按月/年付费,随时取消"]
    A --> D["🔄 持续更新<br>新功能自动生效,无需手动升级"]
    A --> E["👥 多租户<br>一套系统服务多个企业客户"]
    A --> F["📱 随时随地<br>有浏览器就能用,无需安装"]

3.2 什么是多租户(Multi-tenant)

这是 SaaS 系统最核心的概念,整个系统架构都围绕它展开。

类比理解: 把多租户想象成一栋写字楼

graph TD
    A["🏢 写字楼 = 我们的 SaaS 平台"] --> B["1楼:公司A<br>张三的跨境公司<br>用自己的钥匙进门"]
    A --> C["2楼:公司B<br>李四的跨境公司<br>用自己的钥匙进门"]
    A --> D["3楼:公司C<br>王五的跨境公司<br>用自己的钥匙进门"]
    B --> E["公司A的数据<br>只有公司A能看到"]
    C --> F["公司B的数据<br>只有公司B能看到"]
    D --> G["公司C的数据<br>只有公司C能看到"]

技术层面: 同一套代码和数据库,通过 tenant_id(租户ID)字段将不同公司的数据完全隔离。

概念解释
租户(Tenant)使用我们系统的每一家跨境卖家公司,就是一个租户
租户ID(tenant_id)每个租户的唯一标识符,所有数据表都有这个字段
数据隔离A公司绝对看不到B公司的订单、库存、供应商等任何数据

3.3 SaaS 的盈利模式

我们的系统采用 “基础订阅 + 按量付费 + 增值服务” 的混合盈利模式:

套餐价格核心限制
基础版199元/月≤20家供应商,≤500单/月,1个仓库
专业版599元/月≤100家供应商,≤5000单/月,5个仓库
企业版1999元/月无限制,含AI分析、API开放、专属客服

第四节 项目业务全景图

4.1 七大核心业务模块

我们的系统围绕跨境供应链的完整业务链路,分为七大核心模块:

flowchart TD
    subgraph 供应链上游
        SRM["📋 供应商管理<br>SRM<br>供应商引入/审核/评级"]
        PMS["🛒 采购管理<br>PMS<br>询价/下单/入库/对账"]
    end

    subgraph 供应链中游
        WMS["📦 仓储管理<br>WMS<br>库位/库存/出入库/盘点"]
        PIM["🏷️ 商品管理<br>PIM<br>SPU/SKU/多语言/多平台"]
        OMS["📑 订单管理<br>OMS<br>多平台接单/状态跟踪/售后"]
    end

    subgraph 供应链下游
        TMS["🚚 物流管理<br>TMS<br>运单/面单/轨迹/费用"]
        FMS["💰 财务结算<br>FMS<br>对账/利润核算/多货币"]
    end

    subgraph 支撑层
        BI["📊 数据分析<br>BI<br>KPI看板/智能补货/AI决策"]
        AUTH["🔐 权限安全<br>Auth<br>RBAC/多租户/审计日志"]
    end

    SRM -->|"供应商资质审核通过"| PMS
    PMS -->|"采购入库"| WMS
    WMS -->|"库存联动"| PIM
    PIM -->|"商品上架"| OMS
    OMS -->|"发货需求"| WMS
    WMS -->|"拣货完成"| TMS
    TMS -->|"运单完成"| FMS
    OMS -->|"订单数据"| FMS
    FMS -->|"财务数据"| BI
    WMS -->|"库存数据"| BI
    BI -->|"补货建议"| PMS

4.2 完整业务流转地图

这是最重要的一张图,理解这张图就理解了整个项目。

flowchart TB
    A["市场有需求<br>(某款产品卖得好)"] --> B["1️⃣ 选品决策<br>运营专员决定销售这款商品"]
    B --> C["2️⃣ 寻找供应商<br>SRM模块:录入供应商信息<br>提交审核,通过后合作"]
    C --> D["3️⃣ 商品入库<br>PMS模块:向供应商下采购单<br>货物到仓库,完成入库"]
    D --> E["4️⃣ 商品上架<br>PIM模块:录入商品信息<br>设置多语言/多平台价格"]
    E --> F["5️⃣ 平台销售<br>OMS模块:亚马逊/TikTok订单<br>自动同步到系统"]
    F --> G["6️⃣ 仓库发货<br>WMS模块:库存扣减<br>拣货、打包、出库"]
    G --> H["7️⃣ 安排物流<br>TMS模块:创建运单<br>打印面单,交给物流商"]
    H --> I["8️⃣ 运输签收<br>TMS模块:实时追踪轨迹<br>买家签收确认"]
    I --> J["9️⃣ 财务对账<br>FMS模块:平台结算对账<br>计算SKU级利润"]
    J --> K["🔟 数据分析<br>BI模块:库存周转/利润报表<br>智能补货建议"]
    K -->|"库存不足时自动触发"| D

4.3 各模块间的数据依赖关系

数据生产模块消费模块说明
供应商信息SRMPMS采购下单时选择已审核通过的供应商
商品/SKU信息PIMPMS/WMS/OMS采购、库存、订单都需要引用 SKU
入库记录PMSWMS采购入库后库存增加
库存数量WMSOMS/PIM订单扣减库存、商品可售数量展示
订单信息OMSWMS/TMS/FMS仓库拣货、物流发货、财务对账
运单信息TMSOMS/FMS订单状态更新、物流费用结算
结算数据FMSBI利润报表和数据分析

第五节 五流合一深度解读

“五流合一”是供应链管理的核心理念,也是我们系统设计的底层逻辑。

5.1 什么是五流

graph TD
    A["五流合一<br>供应链管理的核心"] --> B["🏷️ 商品流<br>实物货物的流动"]
    A --> C["📑 订单流<br>业务单据的流转"]
    A --> D["💰 资金流<br>钱的收付流向"]
    A --> E["📊 信息流<br>数据与信息传递"]
    A --> F["🚚 物流<br>货物的运输配送"]

5.2 五流的具体含义

① 商品流 — 货物在哪里,流向哪里

供应商仓库 → 采购运输 → 我方仓库 → 国际运输 → 海外仓/FBA → 消费者手中

对应系统功能:库存管理(WMS)追踪货物的实时位置和数量。


② 订单流 — 业务单据怎么流转

采购申请单 → 采购订单 → 入库单 → 销售订单 → 拣货单 → 发货单 → 退货单

对应系统功能:各业务模块的单据管理和状态流转。


③ 资金流 — 钱怎么流动

消费者付款 → 平台收款 → 平台结算给卖家 → 卖家付款给供应商

                              扣除:平台手续费 + 物流费 + 广告费

对应系统功能:财务结算系统(FMS)对账和利润核算。


④ 信息流 — 数据怎么传递

平台订单数据 → 系统同步 → WMS下发拣货 → TMS创建运单 → 物流轨迹回传 → 平台更新状态

对应系统功能:各模块之间的数据联动和 API 对接。


⑤ 物流 — 货物怎么运输

国内发货 → 报关出境 → 国际干线运输 → 目的国清关 → 本地派送 → 签收

对应系统功能:物流管理系统(TMS)运单和轨迹管理。

5.3 五流打通的价值

未打通(传统模式)打通后(我们的系统)
订单下了不知道有没有货下单即时扣减库存,超卖 0 发生
货发出去不知道到哪了全程轨迹可视,异常自动预警
钱收了不知道赚了多少SKU 级精准利润核算
库存盘点靠人工数格子每次出入库自动记账,随时看库存
供应商发了什么全靠自己记采购全流程数字化,一单不落

第六节 技术选型与理由

6.1 技术栈总览

为什么选这些技术?每一个选择都有理由,而不是随便选。

graph TD
    subgraph 前端技术栈
        FE1["Vue 3<br>主流前端框架"]
        FE2["Element Plus<br>企业级UI组件库"]
        FE3["Axios<br>HTTP请求工具"]
        FE4["Pinia<br>状态管理"]
        FE5["ECharts<br>数据可视化图表"]
    end

    subgraph 微服务框架
        MS1["Spring Cloud Alibaba<br>微服务整体解决方案"]
        MS2["Spring Cloud Gateway<br>API 网关"]
        MS3["Nacos<br>服务注册 + 配置中心"]
        MS4["OpenFeign<br>声明式服务间调用"]
        MS5["Sentinel<br>限流 / 熔断 / 降级"]
    end

    subgraph 后端技术栈
        BE1["SpringBoot 3.x<br>各微服务核心框架"]
        BE2["MyBatis-Plus<br>数据库ORM框架"]
        BE3["Sa-Token<br>权限认证框架"]
        BE4["XXL-Job<br>分布式定时任务调度"]
        BE5["Knife4j<br>接口文档"]
    end

    subgraph 数据存储
        DB1["MySQL 8.0<br>业务数据库(读写分离)"]
        DB2["Redis 7.x<br>缓存 / 分布式锁"]
        DB3["阿里云 OSS / MinIO<br>文件存储"]
        DB4["RocketMQ<br>异步消息 / 服务解耦"]
    end

    subgraph 基础设施
        INF1["Docker + Docker Compose<br>容器化部署"]
        INF2["Nginx<br>前端静态资源 / HTTPS"]
        INF3["Git + GitLab<br>代码版本管理"]
        INF4["GitHub Actions<br>CI/CD 流水线"]
    end

6.2 每项技术选型理由

后端框架 — SpringBoot 3.x

选择理由说明
行业标准企业级 Java 开发的事实标准,大量公司使用
自动配置开箱即用,大量依赖自动配置,减少配置工作量
生态完善与 MyBatis、Redis、MQ、Spring Cloud 等组件无缝集成
招聘市场99% 的 Java 后端岗位要求 SpringBoot 经验

微服务框架 — Spring Cloud Alibaba

选择理由说明
国内主流阿里巴巴出品,国内使用率最高的微服务解决方案,招聘市场匹配度极高
组件齐全Nacos + Gateway + OpenFeign + Sentinel 覆盖微服务全场景
与 SpringBoot 无缝集成基于 Spring Cloud 标准,升级成本低
社区活跃中文文档完善,问题排查资料丰富

API 网关 — Spring Cloud Gateway

使用场景说明
路由转发将前端请求统一转发到对应微服务,屏蔽内部服务地址
Token 鉴权在网关层统一验证 Token,各业务服务无需重复鉴权逻辑
限流防刷基于 Redis + Lua 对接口进行全局限流,防止恶意访问
跨域处理统一配置跨域,前端无需逐接口处理

服务注册与配置中心 — Nacos

使用场景说明
服务注册与发现每个微服务启动后向 Nacos 注册,Gateway 通过 Nacos 动态感知服务地址
配置中心数据库连接、Redis 配置、业务开关等统一在 Nacos 管理,修改无需重启服务
配置隔离支持 dev / test / prod 多环境配置隔离

服务间调用 — OpenFeign

选择理由说明
声明式调用像写普通接口一样调用其他服务,无需手写 HTTP 请求
负载均衡内置 LoadBalancer,自动在多实例间分发请求
与 Nacos 联动自动从 Nacos 获取目标服务地址,服务扩缩容无感知

数据库 ORM — MyBatis-Plus(MP)

选择理由说明
封装 CRUD基础的增删改查无需写 SQL,提升效率
多租户插件内置多租户插件,自动拼接 tenant_id 条件
分页插件内置分页组件,不需要手写分页 SQL
代码生成可根据数据库表自动生成 Entity/Mapper/Service 代码

缓存 — Redis

使用场景说明
接口缓存商品列表、数据报表等频繁查询的数据缓存
分布式锁防止库存超卖时的并发问题,跨服务实例安全协调
接口限流配合 Gateway 限制同一用户的请求频率
Session 存储存储用户登录状态(配合 Sa-Token),多服务共享登录态

消息队列 — RocketMQ

选择理由 / 使用场景说明
阿里巴巴出品与 Spring Cloud Alibaba 原生集成,配置简单,国内使用率极高
超高吞吐单机可达百万级 TPS,远超 RabbitMQ,应对大促订单洪峰更从容
服务异步解耦订单服务下单后异步通知仓储服务备货,不阻塞主流程
延迟消息采购单超时未支付自动关单,直接使用 RocketMQ 内置延迟级别
削峰填谷大促时订单消息堆积到队列,仓储服务按自己节奏消费,防止服务压垮
消息可靠性支持事务消息,保证分布式场景下消息投递的最终一致性

文件存储 — 阿里云 OSS / MinIO

选择理由说明
专业存储存储供应商资质文件、商品图片等,比自建可靠
CDN 加速商品图片通过 CDN 全球加速,海外用户加载快
双模式支持私有化部署选 MinIO,公有云环境选 OSS,接口统一切换零改动

第七节 系统架构设计

7.1 整体分层架构

我们的系统采用前后端分离 + Spring Cloud Alibaba 微服务架构。

为什么选微服务? FlexChain 是一个 SaaS 平台,需要同时支撑 1000+ 租户并发,各业务模块(供应链/仓储/订单/物流/财务)的负载特征差异显著,适合按模块独立部署、独立扩容。Spring Cloud Alibaba 微服务架构让我们能针对高并发的订单服务单独扩容,而不必整体扩容,同时各模块可独立发布迭代,不互相影响。

graph TB
    subgraph 用户端
        U1["🖥️ 卖家后台<br>Vue3 Web应用"]
        U2["📱 移动端<br>SaaS管理平台"]
        U3["🏪 供应商Portal<br>独立登录入口"]
    end

    subgraph 接入层
        N["Nginx<br>HTTPS / 静态资源 / 负载均衡"]
        GW["Spring Cloud Gateway<br>路由转发 / Token鉴权 / 全局限流"]
    end

    subgraph 注册与配置中心
        NACOS["Nacos<br>服务注册发现 + 配置中心"]
    end

    subgraph 认证服务
        AUTH["flexchain-auth<br>登录 / Token签发 / 刷新"]
    end

    subgraph 业务微服务层
        M1["flexchain-srm<br>供应商服务"]
        M2["flexchain-pms<br>采购服务"]
        M3["flexchain-wms<br>仓储服务"]
        M4["flexchain-pim<br>商品服务"]
        M5["flexchain-oms<br>订单服务"]
        M6["flexchain-tms<br>物流服务"]
        M7["flexchain-fms<br>财务服务"]
        M8["flexchain-bi<br>数据分析服务"]
        M9["flexchain-system<br>系统管理服务"]
    end

    subgraph 中间件层
        R["Redis<br>缓存 / 分布式锁 / 限流"]
        MQ["RocketMQ<br>异步消息 / 服务解耦"]
        J["XXL-Job<br>分布式定时任务"]
    end

    subgraph 存储层
        DB["MySQL 8.0<br>读写分离(主从)"]
        OSS["阿里云 OSS / MinIO<br>文件存储"]
    end

    subgraph 外部系统
        E1["亚马逊 API"]
        E2["TikTok API"]
        E3["物流商 API"]
        E4["汇率 API"]
        E5["AI 大模型"]
    end

    U1 & U2 & U3 --> N --> GW
    GW <-->|"服务发现"| NACOS
    GW --> AUTH
    GW --> M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & M9
    M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & M9 <-->|"注册/配置"| NACOS
    M2 & M3 & M5 -->|"OpenFeign 服务调用"| M1
    M5 -->|"OpenFeign 服务调用"| M3
    M5 & M6 -->|"异步消息"| MQ
    MQ --> M3 & M7
    M1 & M2 & M3 & M4 & M5 & M6 & M7 --> R
    M1 & M2 & M3 & M4 & M5 & M6 & M7 --> DB
    M4 --> OSS
    J --> M2 & M3 & M5 & M6 & M7
    M5 --> E1 & E2
    M6 --> E3
    M7 --> E4
    M8 --> E5

7.2 后端项目模块划分

微服务架构下,每个业务模块是一个独立的 SpringBoot 应用,可独立编译、独立打包、独立部署。

flexchain/                              ← 项目根目录(Maven 聚合父工程)

├── flexchain-common/                   ← 公共模块(被所有服务引用,不可独立启动)
│   ├── common-core/                    ← 核心工具:统一响应/异常/工具类
│   ├── common-redis/                   ← Redis 配置和工具封装
│   ├── common-mybatis/                 ← MyBatis-Plus 配置和多租户插件
│   ├── common-security/                ← Sa-Token 权限配置
│   └── common-feign/                   ← OpenFeign 公共配置(超时/重试/日志)

├── flexchain-gateway/                  ← API 网关(Spring Cloud Gateway,独立部署)
│   ├── 路由转发配置
│   ├── Token 鉴权过滤器
│   └── 全局限流配置(Redis + Lua)

├── flexchain-auth/                     ← 认证服务(独立部署,端口 9200)
│   ├── 登录 / 登出接口
│   ├── Token 签发与刷新
│   └── 第三方 OAuth 对接

├── flexchain-system/                   ← 系统管理服务(独立部署,端口 9201)
│   ├── 用户管理
│   ├── 角色管理
│   ├── 菜单权限管理
│   └── 租户管理

├── flexchain-srm/                      ← 供应商服务(独立部署,端口 9202)
├── flexchain-pms/                      ← 采购服务(独立部署,端口 9203)
├── flexchain-wms/                      ← 仓储服务(独立部署,端口 9204)
├── flexchain-pim/                      ← 商品服务(独立部署,端口 9205)
├── flexchain-oms/                      ← 订单服务(独立部署,端口 9206)
├── flexchain-tms/                      ← 物流服务(独立部署,端口 9207)
├── flexchain-fms/                      ← 财务服务(独立部署,端口 9208)
└── flexchain-bi/                       ← 数据分析服务(独立部署,端口 9209)

理解要点

  • 每个业务服务(srm/pms/wms…)都有自己独立的 main 方法和 application.yml,可以单独启动
  • 服务间通信通过 OpenFeign 调用(同步)或 RocketMQ(异步),不再是同进程内的方法调用
  • 所有服务启动后向 Nacos 注册,Spring Cloud Gateway 根据 Nacos 的注册信息自动路由请求

7.3 前端项目结构

flexchain-web/                      ← 前端项目根目录
├── public/                         ← 静态资源
├── src/
│   ├── api/                        ← 接口请求(按模块分文件)
│   │   ├── srm/                    ← 供应商相关接口
│   │   ├── pms/                    ← 采购相关接口
│   │   └── ...
│   ├── views/                      ← 页面组件(按模块分目录)
│   │   ├── srm/                    ← 供应商页面
│   │   ├── pms/                    ← 采购页面
│   │   └── ...
│   ├── components/                 ← 公共组件
│   ├── router/                     ← 路由配置(含权限控制)
│   ├── stores/                     ← Pinia 状态管理
│   │   ├── useUserStore.js         ← 用户信息状态
│   │   └── usePermissionStore.js   ← 权限状态
│   ├── utils/                      ← 工具函数
│   │   ├── request.js              ← Axios 封装(含 Token 注入)
│   │   └── auth.js                 ← 认证工具
│   └── main.js                     ← 入口文件
├── package.json
└── vite.config.js

7.4 前后端交互规范

所有接口采用 RESTful 风格,统一 JSON 格式响应:

请求示例(查询供应商列表):

GET /api/srm/supplier/list?page=1&size=20&status=2&grade=A
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

响应格式规范(所有接口统一):

{
  "code": 200,
  "msg": "success",
  "data": {
    "total": 156,
    "pages": 8,
    "current": 1,
    "records": [...]
  },
  "timestamp": 1704067200000,
  "traceId": "abc123def456"
}

HTTP 状态码与业务码对应:

HTTP 状态码业务 code含义
200200成功
200400请求参数错误
200401Token 失效,需重新登录
200403无权限访问
20010001库存不足
20010002订单状态非法流转
500500系统内部错误

注意:HTTP 状态码统一返回 200,业务异常通过 code 字段区分,这是企业项目中常见的做法,前端根据 code 做统一拦截处理。


第八节 多租户架构设计

8.1 多租户的三种实现方案

面试中经常会被问到,需要深刻理解三种方案的区别。

方案隔离方式优点缺点适用场景
独立数据库每个租户一个数据库最强隔离性,互不影响维护成本极高,扩展困难超大型客户或金融场景
Schema 隔离共享数据库,每个租户独立 Schema(表空间)隔离性较好表结构变更复杂,连接池占用多中大型客户,对隔离有要求
字段隔离(行级隔离)共享库共享表,通过 tenant_id 字段区分维护成本低,扩展方便隔离性相对弱,需防止 SQL 漏写中小型 SaaS,初期推荐

我们采用第三种:字段隔离方案(行级隔离)

graph LR
    subgraph 同一张表 supplier
        R1["id=1 tenant_id=101<br>供应商:广州工厂A<br>(属于张三公司)"]
        R2["id=2 tenant_id=101<br>供应商:深圳贸易商B<br>(属于张三公司)"]
        R3["id=3 tenant_id=102<br>供应商:上海工厂C<br>(属于李四公司)"]
        R4["id=4 tenant_id=102<br>供应商:杭州供货商D<br>(属于李四公司)"]
    end

    A["张三的账号<br>tenant_id=101"] -->|"自动追加 WHERE tenant_id=101"| R1
    A --> R2
    B["李四的账号<br>tenant_id=102"] -->|"自动追加 WHERE tenant_id=102"| R3
    B --> R4
    A -.-x|"永远看不到"| R3
    A -.-x|"永远看不到"| R4

8.2 多租户的技术实现原理

整个租户上下文的传递流程:

flowchart LR
    A["前端发起请求<br>携带随机 Token"] --> B["Sa-Token 拦截器<br>校验 Redis 登录态"]
    B --> C["提取租户信息<br>tenant_id = 101<br>user_id = 501"]
    C --> D["存入 ThreadLocal<br>TenantContext.set(101)"]
    D --> E["业务代码执行<br>调用数据库查询"]
    E --> F["MyBatis-Plus<br>多租户插件拦截"]
    F --> G["自动追加条件<br>AND tenant_id = 101"]
    G --> H["MySQL 执行<br>只返回本租户数据"]
    H --> I["请求处理完毕<br>清除 ThreadLocal"]

重要:请求结束后必须清除 ThreadLocal,否则线程被复用时会出现租户信息串用的严重 Bug!

8.3 租户 ID 的生成策略

租户 ID 采用**雪花算法(Snowflake)**生成,确保全局唯一且趋势递增:

graph LR
    A["64位长整型 ID"] --> B["1位符号位<br>固定为0"]
    A --> C["41位时间戳<br>精确到毫秒<br>可用69年"]
    A --> D["10位机器ID<br>支持1024台机器"]
    A --> E["12位序列号<br>每毫秒4096个ID"]

雪花算法特点:

  • 不依赖数据库,由代码生成
  • 全局唯一,不会重复
  • 趋势递增,对数据库索引友好
  • 单台机器每秒可生成 400 万个 ID

第九节 数据库设计规范

这部分内容是整个项目开发的基础规范,所有同学必须严格遵守,否则后期协作会非常痛苦。

9.1 数据库命名规范

库名规范:

flexchain_dev        ← 开发环境
flexchain_test       ← 测试环境
flexchain_prod       ← 生产环境

表名规范:

  • 全部小写,下划线分词,禁止驼峰
  • 表名前缀代表所属模块
模块表名前缀示例
供应商管理supplier_supplier, supplier_cert
采购管理purchase_purchase_order, purchase_order_item
仓储管理warehouse_ / inventory_warehouse, inventory, inventory_log
商品管理product_product_spu, product_sku
订单管理order_order_main, order_item
物流管理logistics_logistics_carrier, logistics_waybill
财务管理finance_finance_bill, finance_statement
系统管理sys_sys_user, sys_role, sys_tenant

字段名规范:

  • 全部小写,下划线分词
  • 外键字段以 _id 结尾(如 supplier_idwarehouse_id
  • 状态字段统一命名为 status,用 TINYINT 类型,注释说明每个数值含义
  • 金额字段用 DECIMAL(12, 2),不能用 FLOAT(精度问题)
  • 时间字段用 DATETIME,不用 TIMESTAMP(2038年问题)

9.2 公共字段设计(所有表必须包含)

graph TD
    A["所有业务表<br>必须包含的公共字段"] --> B["id<br>BIGINT 主键<br>雪花算法生成"]
    A --> C["tenant_id<br>BIGINT 租户ID<br>多租户隔离核心字段"]
    A --> D["create_time<br>DATETIME 创建时间<br>自动填充,不可修改"]
    A --> E["update_time<br>DATETIME 更新时间<br>每次修改自动更新"]
    A --> F["create_by<br>BIGINT 创建人ID<br>记录谁创建的"]
    A --> G["update_by<br>BIGINT 最后操作人ID<br>记录谁最后修改的"]
    A --> H["is_deleted<br>TINYINT 逻辑删除标志<br>0=正常 1=已删除"]
    A --> I["version<br>INT 乐观锁版本号<br>防止并发修改冲突"]

9.3 逻辑删除 vs 物理删除

企业项目中,几乎所有业务数据都使用逻辑删除,而不是真正从数据库中删掉。

对比项物理删除(DELETE)逻辑删除(UPDATE is_deleted=1)
数据能否恢复❌ 不可恢复✅ 可以恢复
审计追溯❌ 无法追溯操作历史✅ 删除记录仍然存在
关联数据需要级联删除,复杂无需处理关联,更安全
数据量减少持续增加(需定期归档)
企业常见度极少使用✅ 标准做法

MyBatis-Plus 的逻辑删除工作原理:

  • 执行 remove() 方法时,实际执行 UPDATE ... SET is_deleted=1
  • 执行 list()/getById() 等查询时,自动追加 AND is_deleted=0
  • 完全透明,业务代码不需要手动处理

9.4 乐观锁设计

乐观锁解决什么问题?

场景:两个人同时打开同一个供应商的编辑页面,都做了修改,后一个人保存时,应该覆盖前一个人的修改,还是提示冲突?

企业标准做法:使用乐观锁,检测到冲突时提示用户重新操作。

sequenceDiagram
    participant A as 用户A
    participant B as 用户B
    participant DB as 数据库

    A->>DB: 查询供应商(version=1)
    B->>DB: 查询供应商(version=1)
    A->>DB: 更新:SET name='新名字' WHERE id=1 AND version=1
    DB->>A: 更新成功,version 变为 2
    B->>DB: 更新:SET name='另一个名字' WHERE id=1 AND version=1
    DB->>B: 更新失败!version 已变为 2,不匹配
    B->>B: 提示用户"数据已被他人修改,请刷新后重试"

9.5 数据库初始化脚本

以下是整个项目的数据库创建脚本,今天先创建基础库和系统管理相关的表。

9.5.1 创建数据库

-- 创建数据库
CREATE DATABASE IF NOT EXISTS `flexchain_dev`
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;

USE `flexchain_dev`;

9.5.2 系统用户表

把整个SaaS平台中所有的账号汇总到一起的数据表 SaaS平台自己的工作人员 一类 卖家的相关人员(卖家管理员 采购人员 仓储人员 财务人员) 一类 供应商 一类

-- 系统用户表
-- 存储所有登录系统的用户,包括:租户内部用户 + 供应商Portal用户 + 平台管理员
CREATE TABLE `sys_user`
(
    `id`          BIGINT UNSIGNED  NOT NULL COMMENT '主键ID,雪花算法生成',
    `tenant_id`   BIGINT           NOT NULL DEFAULT 0 COMMENT '租户ID,平台管理员为0',
    `username`    VARCHAR(64)      NOT NULL COMMENT '登录用户名,同一租户内唯一',
    `password`    VARCHAR(128)     NOT NULL COMMENT '密码,BCrypt加密存储',
    `real_name`   VARCHAR(64)      NULL     COMMENT '真实姓名',
    `avatar`      VARCHAR(512)     NULL     COMMENT '头像图片URL',
    `email`       VARCHAR(128)     NULL     COMMENT '邮箱地址,用于通知和找回密码',
    `phone`       VARCHAR(20)      NULL     COMMENT '手机号,加密存储',
    `user_type`   TINYINT          NOT NULL DEFAULT 1 COMMENT '用户类型:1=普通用户 2=租户管理员 3=供应商用户 9=超级管理员',
    `status`      TINYINT          NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常 2=锁定(多次密码错误)',
    `login_fail_count` TINYINT     NOT NULL DEFAULT 0 COMMENT '连续登录失败次数,超过5次锁定账号',
    `last_login_time`  DATETIME    NULL     COMMENT '最后登录时间',
    `last_login_ip`    VARCHAR(64) NULL     COMMENT '最后登录IP地址',
    `create_time` DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
    `create_by`   BIGINT           NULL     COMMENT '创建人用户ID',
    `update_by`   BIGINT           NULL     COMMENT '最后操作人用户ID',
    `is_deleted`  TINYINT(1)       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常 1=已删除',
    `version`     INT              NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_tenant_username` (`tenant_id`, `username`) COMMENT '同一租户内用户名唯一',
    KEY `idx_tenant_id` (`tenant_id`),
    KEY `idx_email` (`email`),
    KEY `idx_status` (`status`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '系统用户表';

9.5.3 租户表

租户已经注册登录成功后 要提交一些自身的信息

-- 租户表
-- 每一家注册使用我们系统的跨境卖家公司,就是一个租户
CREATE TABLE `sys_tenant`
(
    `id`              BIGINT UNSIGNED NOT NULL COMMENT '租户ID,雪花算法生成',
    `tenant_code`     VARCHAR(32)     NOT NULL COMMENT '租户编码,格式 TC-YYYYMMDD-XXXX,系统自动生成',
    `company_name`    VARCHAR(128)    NOT NULL COMMENT '公司名称',
    `contact_name`    VARCHAR(64)     NOT NULL COMMENT '负责人姓名',
    `contact_phone`   VARCHAR(20)     NOT NULL COMMENT '负责人手机号',
    `contact_email`   VARCHAR(128)    NOT NULL COMMENT '负责人邮箱,同时作为登录账号',
    `plan_type`       TINYINT         NOT NULL DEFAULT 1 COMMENT '套餐类型:1=基础版 2=专业版 3=企业版',
    `plan_start_time` DATETIME        NULL     COMMENT '套餐开始时间',
    `plan_end_time`   DATETIME        NULL     COMMENT '套餐到期时间,NULL表示永久有效',
    `trial_end_time`  DATETIME        NULL     COMMENT '试用期到期时间',
    `status`          TINYINT         NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常 2=试用中 3=已到期 4=已注销',
    `max_supplier`    INT             NOT NULL DEFAULT 20 COMMENT '套餐允许的最大供应商数量',
    `max_warehouse`   INT             NOT NULL DEFAULT 1 COMMENT '套餐允许的最大仓库数量',
    `max_monthly_order` INT           NOT NULL DEFAULT 500 COMMENT '套餐允许的月最大订单量',
    `max_platform`    INT             NOT NULL DEFAULT 1 COMMENT '套餐允许对接的最大平台数量',
    `admin_user_id`   BIGINT          NULL     COMMENT '租户管理员的用户ID',
    `remark`          VARCHAR(512)    NULL     COMMENT '备注信息',
    `create_time`     DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time`     DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
    `create_by`       BIGINT          NULL     COMMENT '创建人(平台管理员ID)',
    `update_by`       BIGINT          NULL     COMMENT '最后操作人',
    `is_deleted`      TINYINT(1)      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常 1=已注销',
    `version`         INT             NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_tenant_code` (`tenant_code`),
    UNIQUE KEY `uk_contact_email` (`contact_email`),
    KEY `idx_status` (`status`),
    KEY `idx_plan_end_time` (`plan_end_time`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '租户信息表';

9.5.4 角色表

把SaaS系统中的所有角色 也就是所有平台的各个角色都汇总到这个表中

-- 角色表
-- RBAC权限模型中的"角色",如:采购专员、仓储管理员、财务专员
CREATE TABLE `sys_role`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `tenant_id`   BIGINT          NOT NULL COMMENT '租户ID,0表示平台内置角色',
    `role_name`   VARCHAR(64)     NOT NULL COMMENT '角色名称,如:采购专员',
    `role_code`   VARCHAR(64)     NOT NULL COMMENT '角色编码,如:ROLE_PURCHASE,用于代码判断',
    `role_type`   TINYINT         NOT NULL DEFAULT 1 COMMENT '角色类型:1=自定义角色 2=系统内置角色(不可删除)',
    `data_scope`  TINYINT         NOT NULL DEFAULT 1 COMMENT '数据权限范围:1=全部数据 2=本部门数据 3=仅本人数据',
    `sort`        INT             NOT NULL DEFAULT 0 COMMENT '排序序号,数字越小越靠前',
    `status`      TINYINT         NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
    `remark`      VARCHAR(256)    NULL     COMMENT '角色说明',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `create_by`   BIGINT          NULL,
    `update_by`   BIGINT          NULL,
    `is_deleted`  TINYINT(1)      NOT NULL DEFAULT 0,
    `version`     INT             NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_tenant_role_code` (`tenant_id`, `role_code`),
    KEY `idx_tenant_id` (`tenant_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '角色表';

9.5.5 菜单权限表

不同的角色 在登录到后台管理系统时 看到的菜单是不一样的

-- 菜单权限表
-- 存储系统所有菜单项和操作按钮权限,树形结构
CREATE TABLE `sys_menu`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `parent_id`   BIGINT          NOT NULL DEFAULT 0 COMMENT '父菜单ID,0表示顶级菜单',
    `menu_name`   VARCHAR(64)     NOT NULL COMMENT '菜单/按钮名称',
    `menu_type`   TINYINT         NOT NULL COMMENT '类型:1=目录 2=菜单页面 3=操作按钮',
    `permission`  VARCHAR(128)    NULL     COMMENT '权限标识,如 srm:supplier:add,按钮级权限用此字段',
    `path`        VARCHAR(256)    NULL     COMMENT '前端路由路径,如 /srm/supplier',
    `component`   VARCHAR(256)    NULL     COMMENT '前端组件路径,如 srm/supplier/index',
    `icon`        VARCHAR(64)     NULL     COMMENT '菜单图标,使用Element Plus图标名称',
    `sort`        INT             NOT NULL DEFAULT 0 COMMENT '同级菜单排序',
    `is_visible`  TINYINT(1)      NOT NULL DEFAULT 1 COMMENT '是否在菜单中显示:1=显示 0=隐藏(路由仍有效)',
    `status`      TINYINT         NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `create_by`   BIGINT          NULL,
    `update_by`   BIGINT          NULL,
    `is_deleted`  TINYINT(1)      NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    KEY `idx_parent_id` (`parent_id`),
    KEY `idx_menu_type` (`menu_type`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';

9.5.6 用户-角色关联表

中间表的建表原则: 至少包含两个表的主键 可以有其他字段

-- 用户与角色的关联表(多对多关系)
-- 一个用户可以有多个角色,一个角色可以分配给多个用户
CREATE TABLE `sys_user_role`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `tenant_id`   BIGINT          NOT NULL COMMENT '租户ID',
    `user_id`     BIGINT          NOT NULL COMMENT '用户ID',
    `role_id`     BIGINT          NOT NULL COMMENT '角色ID',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '分配时间',
    `create_by`   BIGINT          NULL     COMMENT '分配人(管理员ID)',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_user_role` (`user_id`, `role_id`) COMMENT '防止重复分配同一角色',
    KEY `idx_user_id` (`user_id`),
    KEY `idx_role_id` (`role_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '用户角色关联表';

9.5.7 角色-菜单关联表

中间表的建表原则: 至少包含两个表的主键 可以有其他字段

-- 角色与菜单权限的关联表(多对多关系)
-- 定义每个角色能看哪些菜单、能做哪些操作
CREATE TABLE `sys_role_menu`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `tenant_id`   BIGINT          NOT NULL COMMENT '租户ID',
    `role_id`     BIGINT          NOT NULL COMMENT '角色ID',
    `menu_id`     BIGINT          NOT NULL COMMENT '菜单/按钮ID',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '分配时间',
    `create_by`   BIGINT          NULL     COMMENT '分配人',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`) COMMENT '防止重复分配',
    KEY `idx_role_id` (`role_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '角色菜单权限关联表';

9.5.8 操作审计日志表

在有权限校验的系统中 通常都会记录各个角色的各个操作 日志表 只能增 不能改 也不能删除 每个操作都需要进行日志的表的记录 这个功能如何实现 ?? AOP 增强

-- 操作审计日志表
-- 记录系统中所有写操作(新增/修改/删除),用于安全审计和问题追溯
-- 注意:此表只增不改不删,是系统的安全底线
CREATE TABLE `sys_audit_log`
(
    `id`              BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `tenant_id`       BIGINT          NOT NULL DEFAULT 0 COMMENT '租户ID',
    `user_id`         BIGINT          NOT NULL COMMENT '操作人用户ID',
    `username`        VARCHAR(64)     NOT NULL COMMENT '操作人用户名(冗余存储,防止用户删除后追溯失败)',
    `module`          VARCHAR(64)     NOT NULL COMMENT '操作模块,如:供应商管理、采购管理',
    `action`          VARCHAR(64)     NOT NULL COMMENT '操作动作,如:新增供应商、审核通过、删除采购单',
    `method`          VARCHAR(256)    NOT NULL COMMENT '请求方法(POST/PUT/DELETE)+ 接口路径',
    `request_params`  TEXT            NULL     COMMENT '请求参数JSON(敏感字段脱敏后记录)',
    `response_code`   INT             NULL     COMMENT '响应业务码',
    `ip_address`      VARCHAR(64)     NOT NULL COMMENT '操作来源IP地址',
    `user_agent`      VARCHAR(512)    NULL     COMMENT '浏览器User-Agent信息',
    `duration_ms`     INT             NULL     COMMENT '接口耗时(毫秒)',
    `status`          TINYINT         NOT NULL DEFAULT 1 COMMENT '操作结果:1=成功 0=失败',
    `error_msg`       VARCHAR(512)    NULL     COMMENT '操作失败时的错误信息',
    `operate_time`    DATETIME        NOT NULL COMMENT '操作发生时间',
    PRIMARY KEY (`id`),
    KEY `idx_tenant_time` (`tenant_id`, `operate_time`) COMMENT '按租户+时间查询审计日志',
    KEY `idx_user_id` (`user_id`),
    KEY `idx_module` (`module`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '操作审计日志表(只增不改不删)';

9.5.9 系统字典表

在这个SaaS系统中 会有很多的枚举值 这些值我们也需要存储到数据库中 一般这种有限个的数据 都会定义为 字典表

-- 数据字典主表
-- 用于管理系统中各种下拉选项的枚举值,避免硬编码
-- 例如:供应商类型、采购单状态、物流类型等
CREATE TABLE `sys_dict_type`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `dict_name`   VARCHAR(64)     NOT NULL COMMENT '字典名称,如:供应商类型',
    `dict_code`   VARCHAR(64)     NOT NULL COMMENT '字典编码,如:supplier_type,代码中用此值查询',
    `status`      TINYINT         NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
    `remark`      VARCHAR(256)    NULL     COMMENT '备注说明',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `create_by`   BIGINT          NULL,
    `update_by`   BIGINT          NULL,
    `is_deleted`  TINYINT(1)      NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_dict_code` (`dict_code`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '数据字典类型表';

-- 数据字典明细表
-- 存储每种字典类型下的具体枚举值
CREATE TABLE `sys_dict_item`
(
    `id`          BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
    `dict_type_id` BIGINT         NOT NULL COMMENT '关联字典类型ID',
    `dict_code`   VARCHAR(64)     NOT NULL COMMENT '字典编码(冗余,方便查询)',
    `item_value`  VARCHAR(64)     NOT NULL COMMENT '字典项的值,如:1',
    `item_label`  VARCHAR(128)    NOT NULL COMMENT '字典项的显示文字,如:工厂供应商',
    `item_label_en` VARCHAR(128)  NULL     COMMENT '英文显示文字(国际化)',
    `sort`        INT             NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
    `css_class`   VARCHAR(32)     NULL     COMMENT '样式类名,用于前端标签着色,如 success/warning/danger',
    `status`      TINYINT         NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
    `remark`      VARCHAR(256)    NULL     COMMENT '备注',
    `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `is_deleted`  TINYINT(1)      NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    KEY `idx_dict_code` (`dict_code`),
    KEY `idx_dict_type_id` (`dict_type_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT = '数据字典明细表';

9.5.10 初始化基础数据

和上面的建表的SQL对应的假数据

-- ============================================================
-- 初始化菜单数据
-- ============================================================
INSERT INTO `sys_menu` (`id`, `parent_id`, `menu_name`, `menu_type`, `permission`, `path`, `component`, `icon`, `sort`, `is_visible`, `status`)
VALUES
-- 一级菜单(目录)
(100, 0, '供应链管理', 1, NULL, '/supply', NULL, 'Box', 1, 1, 1),
(200, 0, '商品中心', 1, NULL, '/product', NULL, 'Goods', 2, 1, 1),
(300, 0, '订单中心', 1, NULL, '/order', NULL, 'List', 3, 1, 1),
(400, 0, '物流管理', 1, NULL, '/logistics', NULL, 'Van', 4, 1, 1),
(500, 0, '财务中心', 1, NULL, '/finance', NULL, 'Money', 5, 1, 1),
(600, 0, '数据分析', 1, NULL, '/bi', NULL, 'DataAnalysis', 6, 1, 1),
(900, 0, '系统设置', 1, NULL, '/system', NULL, 'Setting', 9, 1, 1),

-- 供应链管理 子菜单
(101, 100, '供应商管理', 2, NULL, '/supply/supplier', 'srm/supplier/index', NULL, 1, 1, 1),
(102, 100, '采购管理', 2, NULL, '/supply/purchase', 'pms/purchase/index', NULL, 2, 1, 1),
(103, 100, '仓库管理', 2, NULL, '/supply/warehouse', 'wms/warehouse/index', NULL, 3, 1, 1),
(104, 100, '库存管理', 2, NULL, '/supply/inventory', 'wms/inventory/index', NULL, 4, 1, 1),

-- 供应商管理 操作按钮
(10101, 101, '查看供应商', 3, 'srm:supplier:list', NULL, NULL, NULL, 1, 0, 1),
(10102, 101, '新增供应商', 3, 'srm:supplier:add', NULL, NULL, NULL, 2, 0, 1),
(10103, 101, '编辑供应商', 3, 'srm:supplier:edit', NULL, NULL, NULL, 3, 0, 1),
(10104, 101, '删除供应商', 3, 'srm:supplier:delete', NULL, NULL, NULL, 4, 0, 1),
(10105, 101, '审核供应商', 3, 'srm:supplier:audit', NULL, NULL, NULL, 5, 0, 1),

-- 系统设置 子菜单
(901, 900, '用户管理', 2, NULL, '/system/user', 'system/user/index', NULL, 1, 1, 1),
(902, 900, '角色管理', 2, NULL, '/system/role', 'system/role/index', NULL, 2, 1, 1),
(903, 900, '菜单管理', 2, NULL, '/system/menu', 'system/menu/index', NULL, 3, 1, 1),
(904, 900, '数据字典', 2, NULL, '/system/dict', 'system/dict/index', NULL, 4, 1, 1),
(905, 900, '审计日志', 2, NULL, '/system/audit', 'system/audit/index', NULL, 5, 1, 1);

-- ============================================================
-- 初始化角色数据(系统内置角色)
-- ============================================================
INSERT INTO `sys_role` (`id`, `tenant_id`, `role_name`, `role_code`, `role_type`, `data_scope`, `sort`, `status`, `remark`)
VALUES
(1, 0, '超级管理员', 'ROLE_SUPER_ADMIN', 2, 1, 0, 1, '平台超级管理员,拥有全部权限,不属于任何租户'),
(2, 0, '租户管理员', 'ROLE_TENANT_ADMIN', 2, 1, 1, 1, '租户内最高权限管理员,可管理本租户所有功能'),
(3, 0, '采购专员', 'ROLE_PURCHASE', 2, 3, 2, 1, '负责供应商管理和采购下单'),
(4, 0, '仓储管理员', 'ROLE_WAREHOUSE', 2, 1, 3, 1, '负责仓库日常运营,出入库操作'),
(5, 0, '运营专员', 'ROLE_OPERATION', 2, 3, 4, 1, '负责商品上架和订单处理'),
(6, 0, '物流专员', 'ROLE_LOGISTICS', 2, 3, 5, 1, '负责运单创建和物流跟踪'),
(7, 0, '财务专员', 'ROLE_FINANCE', 2, 1, 6, 1, '负责对账和利润核算'),
(8, 0, '供应商', 'ROLE_SUPPLIER', 2, 3, 7, 1, '供应商Portal专属角色,权限受严格限制');

-- ============================================================
-- 初始化数据字典
-- ============================================================
INSERT INTO `sys_dict_type` (`id`, `dict_name`, `dict_code`, `status`, `remark`)
VALUES
(1, '供应商类型', 'supplier_type', 1, '供应商的业务类型分类'),
(2, '供应商状态', 'supplier_status', 1, '供应商的审核和合作状态'),
(3, '供应商评级', 'supplier_grade', 1, '供应商的综合绩效评级'),
(4, '采购单状态', 'purchase_order_status', 1, '采购订单的全状态定义'),
(5, '仓库类型', 'warehouse_type', 1, '仓库的业务类型'),
(6, '库存流水类型', 'inventory_log_type', 1, '库存变动的操作类型'),
(7, '订单状态', 'order_status', 1, '销售订单的全状态定义'),
(8, '物流渠道类型', 'logistics_type', 1, '物流服务的类型分类'),
(9, '币种', 'currency', 1, '系统支持的结算货币');

INSERT INTO `sys_dict_item` (`id`, `dict_type_id`, `dict_code`, `item_value`, `item_label`, `item_label_en`, `sort`, `css_class`, `status`)
VALUES
-- 供应商类型
(101, 1, 'supplier_type', '1', '工厂供应商', 'Factory', 1, 'success', 1),
(102, 1, 'supplier_type', '2', '贸易商', 'Trader', 2, 'primary', 1),
(103, 1, 'supplier_type', '3', '物流服务商', 'Logistics Provider', 3, 'info', 1),
-- 供应商状态
(201, 2, 'supplier_status', '0', '草稿', 'Draft', 1, 'info', 1),
(202, 2, 'supplier_status', '1', '待审核', 'Pending', 2, 'warning', 1),
(203, 2, 'supplier_status', '2', '已通过', 'Approved', 3, 'success', 1),
(204, 2, 'supplier_status', '3', '已拒绝', 'Rejected', 4, 'danger', 1),
(205, 2, 'supplier_status', '4', '已停用', 'Disabled', 5, '', 1),
-- 供应商评级
(301, 3, 'supplier_grade', 'S', 'S级-优质', 'Premium', 1, 'success', 1),
(302, 3, 'supplier_grade', 'A', 'A级-良好', 'Good', 2, 'primary', 1),
(303, 3, 'supplier_grade', 'B', 'B级-一般', 'Fair', 3, 'warning', 1),
(304, 3, 'supplier_grade', 'C', 'C级-预警', 'Alert', 4, 'danger', 1),
-- 币种
(901, 9, 'currency', 'CNY', '人民币', 'Chinese Yuan', 1, '', 1),
(902, 9, 'currency', 'USD', '美元', 'US Dollar', 2, '', 1),
(903, 9, 'currency', 'EUR', '欧元', 'Euro', 3, '', 1),
(904, 9, 'currency', 'GBP', '英镑', 'British Pound', 4, '', 1),
(905, 9, 'currency', 'JPY', '日元', 'Japanese Yen', 5, '', 1);

第十节 项目工程初始化

10.1 开发环境准备

在开始写代码之前,需要确认以下工具都已安装:

项目的POM文件的编写 祖传 版本对应 分布式的很多组件 这些组件之间的版本号 需要进行对应的 不要随意的去修改POM文件 尤其是版本号 Maven 的本地仓库 如果某个依赖下载不了 这个依赖可能是你们的私服(公司内部搭建的Maven仓库) 也可能是因为你配置了国内的镜像(延迟 可能没有) 如果你到了公司 Clone了公司的代码 然后加载依赖的时候 某个依赖一直报错 复制这个依赖的 artifactId 去搜索 如果网上没有任何信息 说明私服 或者 groupId 是你们公司的域名 也能确定就是 私服 两种做法: 1. 找你们领导/同事 让他给你发一份maven的配置 导入到自己的Maven目录 不要忘记修改 本地仓库地址 2. 找你们同事 给你拷贝这个jar包 然后通过Maven命令 mvn .. 去安装这个依赖到本地仓库 mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging>

工具版本要求用途检查命令
JDK17 或 21(推荐 21)Java 运行环境java -version
Maven3.8+项目依赖管理mvn -version
MySQL8.0+数据库mysql --version
Redis7.x缓存服务redis-server --version
Node.js18+前端运行环境node --version
Git任意版本代码版本管理git --version
IntelliJ IDEA2023+ 或 2024后端 IDE
VS Code任意新版前端编辑器

10.2 Maven 父项目 pom.xml 核心配置

父 pom 统一管理所有子模块的依赖版本,确保版本一致性,避免版本冲突。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 项目坐标:groupId.artifactId 唯一标识一个项目 -->
    <groupId>com.flexchain</groupId>
    <artifactId>flexchain</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <!-- packaging 为 pom 表示这是父项目,不产生 jar 包 -->
    <packaging>pom</packaging>
    <name>FlexChain - 跨境出海柔性供应链SaaS平台</name>

    <!-- 声明所有子模块 -->
    <modules>
        <module>flexchain-common</module>
        <module>flexchain-gateway</module>
        <module>flexchain-auth</module>
        <module>flexchain-system</module>
        <module>flexchain-srm</module>
        <module>flexchain-pms</module>
        <module>flexchain-wms</module>
        <module>flexchain-pim</module>
        <module>flexchain-oms</module>
        <module>flexchain-tms</module>
        <module>flexchain-fms</module>
        <module>flexchain-bi</module>
    </modules>

    <!-- 统一管理版本号,所有子模块引用这里定义的版本 -->
    <properties>
        <java.version>21</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>3.2.0</spring-boot.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <sa-token.version>1.38.0</sa-token.version>
        <knife4j.version>4.4.0</knife4j.version>
        <hutool.version>5.8.25</hutool.version>
        <xxl-job.version>2.4.1</xxl-job.version>
        <aliyun-oss.version>3.17.4</aliyun-oss.version>
    </properties>

    <!--
        dependencyManagement:依赖管理区
        声明在这里的依赖,子模块引用时不需要写 version
        但只有子模块显式引入时才会真正加入依赖,不会自动传递
    -->
    <dependencyManagement>
        <dependencies>
            <!-- SpringBoot 依赖管理 BOM(Bill of Materials)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud 依赖管理 BOM -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba 依赖管理 BOM -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Nacos 服务注册与配置中心(各服务引入) -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>

            <!-- Spring Cloud Gateway:API 网关(gateway 模块引入) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>

            <!-- OpenFeign:声明式服务间调用(各服务引入) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>

            <!-- LoadBalancer:客户端负载均衡(配合 OpenFeign 使用) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-loadbalancer</artifactId>
            </dependency>

            <!-- Sentinel:限流、熔断、降级(各服务引入) -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>

            <!-- MyBatis-Plus:增强版ORM框架 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- Sa-Token:轻量级权限认证框架 -->
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-spring-boot3-starter</artifactId>
                <version>${sa-token.version}</version>
            </dependency>
            <!-- Sa-Token 整合 Redis(Token持久化) -->
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-redis-jackson</artifactId>
                <version>${sa-token.version}</version>
            </dependency>

            <!-- Knife4j:Swagger接口文档增强,比原版界面好用 -->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
                <version>${knife4j.version}</version>
            </dependency>

            <!-- Hutool:国产工具库,包含大量实用工具类 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- XXL-Job:分布式定时任务调度 -->
            <dependency>
                <groupId>com.xuxueli</groupId>
                <artifactId>xxl-job-core</artifactId>
                <version>${xxl-job.version}</version>
            </dependency>

            <!-- 阿里云OSS:文件存储 -->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-oss.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

10.3 SpringBoot 应用核心配置文件

生产环境的配置文件不能提交到 Git!数据库密码、Redis 密码等敏感信息必须通过环境变量注入。

# application.yml  —  公共配置(提交到 Git)
# 以 flexchain-oms(订单服务)为例,每个微服务有自己独立的 application.yml
spring:
  application:
    name: flexchain-oms    # 服务名,Nacos 注册时使用此名称
  profiles:
    active: dev

  # Nacos 服务注册与配置中心
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_ADDR:localhost:8848}   # Nacos 地址
        namespace: ${NACOS_NAMESPACE:dev}            # 命名空间(dev/test/prod 隔离)
        group: FLEXCHAIN_GROUP
      config:
        server-addr: ${NACOS_ADDR:localhost:8848}
        namespace: ${NACOS_NAMESPACE:dev}
        group: FLEXCHAIN_GROUP
        file-extension: yaml
        # 从 Nacos 拉取 flexchain-oms.yaml 配置
        prefix: ${spring.application.name}

# 服务端口(每个微服务端口不同)
server:
  port: 9206    # oms 服务端口,其他服务端口见模块结构说明

# MyBatis-Plus 配置
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: isDeleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# Sa-Token 权限框架配置(多服务共享 Redis,实现单点登录)
sa-token:
  token-name: Authorization
  timeout: 7200
  active-timeout: 604800
  is-concurrent: true
  token-style: random-64
  # 开启 Same-Token(服务间内部调用鉴权)
  is-share: false
# application-dev.yml  —  开发环境配置(不提交到 Git,加入 .gitignore)
# 敏感配置也可以放在 Nacos 配置中心,通过 namespace 隔离
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/flexchain_oms_dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: your_password_here
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      connection-timeout: 30000

  data:
    redis:
      host: localhost
      port: 6379
      password:
      database: 0
      timeout: 3000ms

  # RocketMQ 配置(服务间异步消息,Spring Cloud Alibaba)
  rocketmq:
    name-server: localhost:9876          # NameServer 地址
    producer:
      group: flexchain-producer-group
      send-message-timeout: 3000
    consumer:
      group: flexchain-consumer-group

# 文件上传配置
file:
  upload:
    type: local
    local:
      base-path: /tmp/flexchain/uploads

# Knife4j 接口文档(仅开发环境开启)
knife4j:
  enable: true
  setting:
    language: zh_cn

10.4 Git 仓库初始化与分支规范

实际开发中 都是先从 clone 开始 入职后 你们领导给你发放 GitLab 的地址 账号 密码(第一次登录要求修改密码) 修改密码后 立即 绑定你的 SSH 公私钥 找到你需要 clone 的地址 使用 git 开头的地址 进行 clone 然后就把代码荡到本地了

万一我把这个代码搞废了 无所谓 删除 重新 clone 只要不往远程推送 就没有问题

Git的使用的流程: 少量多次 每次修改完一个Bug / 实现一个业务 本地测试没有问题 立即推送 每天都要多次推送 不要等到下班的时候 一次推送 会导致 冲突的产生 立即推送 就算是遇到了冲突 涉及的方法 代码量也很少

每次推送之前要先拉取 如果这个时候有冲突 先解决冲突

本地版本每次完成一个小任务都要及时提交

# 1. 初始化本地 Git 仓库
git init

# 2. 创建 .gitignore 文件(排除不需要提交的文件)
# 内容见下方

# 3. 创建并切换到 main 分支(作为稳定主分支)
git checkout -b main

# 4. 添加远程仓库(替换为你的仓库地址)
git remote add origin https://gitlab.example.com/flexchain/flexchain.git

# 5. 初始提交
git add .
git commit -m "feat: 项目初始化,完成基础架构搭建"

# 6. 推送到远程
git push -u origin main

.gitignore 文件内容:

忽略提交 我们在公司做开发的时候 每个人的电脑的环境是不一样的 有的人使用Mac 有的人使用的是Windows 有的人使用Linux 这些操作系统他们的缓存文件格式都是不一样的 我们要确保 往远程推送的时候 只有 代码 + 配置 远程clone的时候会不会有这个文件 .gitignore 大概率是没有的 如果没有 你需要自己创建一个 .gitignore 并且把这个文件本身排除掉

# 编译产物
target/
*.class
*.jar
*.war

# Maven 本地仓库缓存
.mvn/

# IDE 配置文件(不同人 IDE 配置不同,不应共享)
.idea/
*.iml
.vscode/
.eclipse/

# 日志文件
logs/
*.log

# 环境配置文件(包含数据库密码等敏感信息,绝对不能提交!)
application-dev.yml
application-test.yml
application-prod.yml

# 操作系统生成文件
.DS_Store
Thumbs.db

# Node.js
node_modules/
dist/

Git 分支规范:

main/master 分支 非常纯洁 不允许去你玷污他 GIT 非常明确的权限 代码审核 你每次提交之前 都要先申请 你们的领导要审核 通过后 才能提交 创建分支 合并分支 都要有权限 你就算是想操作 误操作 也操作不了

gitGraph
    commit id: "初始化项目"
    branch develop
    checkout develop
    commit id: "feat: 公共模块搭建"
    branch feature/srm
    checkout feature/srm
    commit id: "feat: 供应商管理-供应商列表"
    commit id: "feat: 供应商管理-新增供应商"
    commit id: "feat: 供应商管理-审核流程"
    checkout develop
    merge feature/srm id: "merge: SRM模块完成"
    branch feature/pms
    checkout feature/pms
    commit id: "feat: 采购管理-采购订单"
    checkout develop
    merge feature/pms id: "merge: PMS模块完成"
    checkout main
    merge develop id: "release: v1.0.0"

分支说明:

分支用途规则
main生产稳定版只接受 develop 合并,不直接提交代码
develop开发集成分支汇聚所有功能分支的代码
feature/xxx功能开发分支每个功能模块一个分支,完成后合并回 develop
hotfix/xxx紧急修复分支main 拉出,修复后合并回 maindevelop

Git 提交信息规范(Conventional Commits):

feat:     新增功能
fix:      Bug 修复
docs:     文档更新
style:    代码格式调整(不影响功能)
refactor: 代码重构
test:     新增或修改测试
chore:    构建或辅助工具变动

示例:
git commit -m "feat(srm): 新增供应商审核流程"
git commit -m "fix(wms): 修复库存并发扣减出现负数的bug"
git commit -m "docs: 更新API文档"

第十一节 公共模块搭建

11.1 统一响应体设计

所有接口必须返回统一格式,前端才能写统一的拦截器处理错误。

统一响应类(R.java)的设计思路:

flowchart LR
    A["Controller 方法<br>return R.ok(data)"] --> B["R 对象<br>code=200<br>msg=success<br>data=业务数据"]
    C["Controller 方法<br>return R.fail(错误码, 错误信息)"] --> D["R 对象<br>code=错误码<br>msg=错误信息<br>data=null"]
    E["全局异常捕获器<br>catch Exception"] --> F["R 对象<br>code=500<br>msg=系统内部错误<br>data=null(生产环境不暴露堆栈)"]

设计要点说明(方法和字段含义):

字段/方法类型说明
codeInteger业务状态码,200=成功,非200=失败
msgString响应消息,成功时为”success”,失败时为错误描述
dataT(泛型)响应数据,失败时为 null
timestampLong服务器响应时间戳(毫秒),便于排查问题
traceIdString链路追踪ID,同一次请求的所有日志共享同一个 traceId
R.ok()静态方法快速构建成功响应,无数据时使用
R.ok(data)静态方法快速构建带数据的成功响应
R.fail(msg)静态方法快速构建失败响应
R.fail(code, msg)静态方法快速构建指定错误码的失败响应

11.2 全局异常处理器设计思路

flowchart TD
    A["请求进入"] --> B["正常业务逻辑执行"]
    B --> C{"是否抛出异常?"}
    C -- 否 --> D["正常响应 R.ok()"]
    C -- "业务异常<br>BusinessException" --> E["全局异常处理器捕获<br>返回 R.fail(业务错误码, 错误信息)"]
    C -- "参数校验异常<br>MethodArgumentNotValidException" --> F["捕获并提取字段错误信息<br>返回 R.fail(400, '邮箱格式不正确')"]
    C -- "权限异常<br>NotLoginException" --> G["返回 R.fail(401, '请先登录')"]
    C -- "系统异常<br>Exception" --> H["记录完整异常日志<br>返回 R.fail(500, '系统内部错误')<br>不暴露堆栈给前端"]
    E & F & G & H --> I["异常响应返回给前端"]

自定义业务异常类(BusinessException)的设计意义:

为什么不直接 throw new RuntimeException("供应商不存在")

因为全局异常处理器需要区分两种异常:

  • 业务异常(如”供应商不存在”、“库存不足”):是正常业务流程中预期可能发生的,应该返回明确的错误码和信息给前端
  • 系统异常(如数据库连接失败、空指针):是系统级错误,不能把技术细节暴露给用户,应该记录详细日志然后返回通用错误提示

11.3 多租户上下文设计

flowchart TD
    A["HTTP 请求到达<br>携带 Authorization: Bearer xxx"] --> B["TenantInterceptor<br>拦截所有请求"]
    B --> C["调用 Sa-Token 解析 Token<br>获取 userId 和 tenantId"]
    C --> D{"Token 有效?"}
    D -- 无效 --> E["返回 401 未授权"]
    D -- 有效 --> F["TenantContext.set(tenantId, userId)<br>存入 ThreadLocal"]
    F --> G["放行请求<br>继续执行 Controller"]
    G --> H["业务方法执行<br>需要租户ID时调用 TenantContext.getTenantId()"]
    H --> I["MyBatis-Plus 多租户插件<br>自动拼接 WHERE tenant_id = ?"]
    I --> J["请求处理完成"]
    J --> K["TenantInterceptor afterCompletion<br>TenantContext.clear() 清除 ThreadLocal"]

ThreadLocal 是什么:

ThreadLocal 是 Java 提供的一种机制,让每个线程都有自己独立的数据副本,线程之间互不影响。 比喻:每个人(线程)都有一个自己的抽屉(ThreadLocal),存放自己的钥匙(tenant_id)。 使用完必须清空抽屉(remove()),否则下次同一个人(线程)打开抽屉,里面还放着上次的钥匙。

11.4 公共配置的 MyBatis-Plus 插件链

graph LR
    A["SQL执行请求"] --> B["分页插件<br>自动处理 LIMIT 和 COUNT"]
    B --> C["多租户插件<br>自动追加 tenant_id 条件"]
    C --> D["乐观锁插件<br>自动处理 version 字段"]
    D --> E["SQL最终执行<br>发送给 MySQL"]

插件执行顺序的重要性:

  • 分页插件需要先于多租户插件执行,否则 COUNT 语句不会包含租户条件,分页总数会算错
  • 乐观锁插件在最后,只在 UPDATE 语句中追加 AND version = ?

今日总结与作业

今日知识点回顾

业务层面:

  • 跨境出海电商的业务模式:中国商品卖给全球消费者,需要多平台运营和复杂的供应链管理
  • 柔性供应链 vs 刚性供应链:柔性 = 快速响应市场变化,动态调整产能/库存/物流
  • SaaS 多租户架构:一套系统服务多个企业客户,数据完全隔离,订阅制收费
  • 五流合一:商品流/订单流/资金流/信息流/物流,五个维度打通后才实现供应链真正数字化

技术层面:

  • 项目技术栈:SpringBoot 3.x + Spring Cloud Alibaba + MyBatis-Plus + MySQL + Redis + RocketMQ + Vue3
  • 系统分层架构:前端 → Nginx → Spring Cloud Gateway → 各微服务 → 中间件 → 数据存储 → 外部系统
  • 服务注册与配置:所有微服务向 Nacos 注册,配置统一在 Nacos 管理,支持多环境隔离
  • 多租户实现方案:字段隔离(行级隔离)+ ThreadLocal 上下文传递 + MP 多租户插件
  • 数据库设计规范:所有表必须包含公共字段(id/tenant_id/create_time/update_time/create_by/update_by/is_deleted/version)
  • 逻辑删除:企业标准做法,不真正删除数据,通过 is_deleted=1 标记
  • 雪花算法:生成全局唯一、趋势递增的 ID,不依赖数据库

今日作业

作业 1:业务理解题(必做)

回答以下问题(用自己的话,不超过 100 字/题):

  1. 如果你是一个跨境卖家,手里有 3 款商品分别在亚马逊和 TikTok 上卖,一天有 200 个订单,不使用供应链管理系统的话,你会遇到哪些具体的问题? 不用系统会出现订单分散、库存不准、人工统计慢的问题。亚马逊和 TikTok 订单要手工汇总,容易漏单、重复发货; 多个平台共用库存时容易超卖; 采购、发货、物流、利润都靠 Excel 记录,数据不同步,出问题也难追踪。
  2. “柔性供应链”中的”柔性”体现在哪些方面?举一个真实的业务场景说明。 柔性体现在供应商、库存、采购、仓储、物流都能快速调整。 比如某款商品在 TikTok 突然爆单,系统发现库存低于安全库存后,自动提醒补货,并优先选择交期更短的备用供应商和更快的物流渠道,避免断货。
  3. 如果不设计多租户隔离,A 公司的员工有没有可能看到 B 公司的供应商信息?用技术语言解释为什么会发生这种情况。 有可能。 ==如果所有公司的数据都存在同一批表里,但查询时没有按 tenant_id 过滤,SQL 可能查出全表数据。比如查询供应商列表只写 select * from supplier,没有 where tenant_id = 当前租户ID,A 公司就可能看到 B 公司的供应商。==

作业 2:数据库执行(必做)

  1. 在 MySQL 中创建 flexchain_dev 数据库
  2. 执行本节课所有的建表 SQL 脚本(sys_user / sys_tenant / sys_role / sys_menu / sys_user_role / sys_role_menu / sys_audit_log / sys_dict_type / sys_dict_item)
  3. 执行初始化数据脚本(菜单数据 / 角色数据 / 字典数据)
  4. 在 MySQL 客户端(Navicat 或 DataGrip)中,用以下 SQL 验证数据正确插入:
-- 验证脚本
-- 1. 检查所有表是否创建成功
SELECT TABLE_NAME, TABLE_COMMENT 
FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_SCHEMA = 'flexchain_dev'
ORDER BY TABLE_NAME;

-- 2. 检查菜单数据是否正确(应返回15条记录)
SELECT id, parent_id, menu_name, menu_type FROM sys_menu ORDER BY id;

-- 3. 检查角色数据是否正确(应返回8条记录)
SELECT id, role_name, role_code, data_scope FROM sys_role ORDER BY sort;

-- 4. 检查字典数据(查询供应商状态字典)
SELECT dt.dict_name, di.item_value, di.item_label, di.css_class
FROM sys_dict_item di
JOIN sys_dict_type dt ON di.dict_type_id = dt.id
WHERE di.dict_code = 'supplier_status'
ORDER BY di.sort;

作业 3:项目初始化(必做)

  1. 在 IntelliJ IDEA 中创建 Maven 父项目,并创建以下子模块:
    • flexchain-common(公共模块)
    • flexchain-gateway(API 网关模块)
    • flexchain-auth(认证服务模块)
    • flexchain-system(系统管理服务模块)
  2. 按照本节课的 pom.xml 配置,完成父 pom 和各子模块 pom 的依赖配置(注意引入 Spring Cloud Alibaba BOM)
  3. 本地启动 Nacos(下载 Nacos 2.x,执行 startup.cmd -m standalone),确认 Nacos 控制台可访问(默认 http://localhost:8848/nacos)
  4. flexchain-system 创建 application.yml,配置 Nacos 注册地址,启动后在 Nacos 控制台「服务管理」中能看到该服务注册成功
  5. 初始化 Git 仓库,完成第一次提交(确认 .gitignore 已排除 application-dev.yml

作业 4:拓展思考(选做)

查阅资料,思考以下问题:

  1. 雪花算法在分布式环境下,如何保证不同机器生成的 ID 不重复?(提示:机器ID的作用) 雪花算法的 ID 一般由时间戳、机器 ID、序列号组成。 不同机器会分配不同的机器 ID,即使同一毫秒内生成 ID,机器 ID 不同,最终拼出来的 ID 也不同。 单机内再通过毫秒内自增序列号避免重复。
  2. 多租户的字段隔离方案和独立数据库方案,分别在什么场景下更合适? 字段隔离适合中小型 SaaS,一套库表通过 tenant_id 区分租户,成本低、扩展快、运维简单。 独立数据库适合大客户、金融、强合规场景,每个租户单独数据库,隔离性强,但部署、升级、备份和运维成本更高。

明日预告

第二天:供应商管理系统(SRM)完整实现

明天我们将完整实现供应商管理系统,包括:

  • 供应商信息的 CRUD(增删改查)接口开发
  • 供应商审核工作流(状态机设计与实现)
  • 文件上传功能(对接阿里云 OSS)
  • 供应商绩效评分定时任务
  • 供应商 Portal 账号创建
  • 前端页面:供应商列表、新增/编辑表单、审核操作

预习建议:

  • 了解 MyBatis-Plus 的基础 CRUD 用法(BaseMapperIService
  • 了解状态机(State Machine)的概念
  • 了解 RESTful API 的设计规范(GET/POST/PUT/DELETE 的使用场景)

重要提醒:每天课后务必整理当天的笔记,并把遇到的问题记录在笔记中。项目开发中遇到问题是正常的,关键是要记录下来问题是什么、原因是什么、怎么解决的。这个习惯在面试中非常有用,面试官最喜欢问”你在项目中遇到过什么技术难题,是怎么解决的”。