校享云 — 高校智慧后勤平台微服务架构设计与实践

作者:CoderDongHuang
项目地址https://github.com/CoderDongHuang/Campus-Cloud
难度等级:⭐⭐⭐⭐(中高级全栈项目)
阅读时间:约 45 分钟


📖 目录


一、🎯 项目背景与痛点分析

1.1 行业现状与挑战

高校后勤服务长期面临"三难"困局:

痛点传统模式影响
报修响应慢电话/纸质工单,人工派单学生等 2-3 天,投诉率高
服务不可见没有评价和追踪机制服务质量无法量化,师傅积极性低
数据割裂各宿舍楼独立管理,无统一平台校方无法做全局决策,资源浪费

以某高校真实数据为例:日均报修 200+ 单,保洁服务 150+ 次,配送订单 300+ 笔——没有数字化系统根本无法高效运转。

1.2 解决方案价值

校享云通过 学生端 + 师傅端 + 校方后台 三端协同,实现校园生活服务全链路数字化:

                    ┌─────────────────────────────────────────────┐
                    │            校享云 · 三端协同平台              │
                    │                                             │
     ┌──────────────┼──────────────────────┬──────────────────────┤
     │              │                      │                      │
     ▼              ▼                      ▼                      ▼
 ┌───────┐     ┌───────┐            ┌───────────┐          ┌───────┐
 │ 📱学生 │     │ 🔧师傅 │            │ 🖥️ 校方    │          │ 📊数据 │
 │ 端     │     │ 端     │            │ 管理后台   │          │ 大屏   │
 ├───────┤     ├───────┤            ├───────────┤          ├───────┤
 │浏览服务│     │接单拒单│            │商品管理    │          │GMV统计 │
 │下单支付│     │工单管理│            │订单管理    │          │订单趋势│
 │在线沟通│     │收益查看│            │用户管理    │          │转化分析│
 │评价打星│     │提现申请│            │优惠券管理  │          │师傅排行│
 │领优惠券│     │实名认证│            │结算审核    │          │服务热榜│
 └───────┘     └───────┘            └───────────┘          └───────┘

核心价值

  • 🚀 效率提升:报修响应从 2 天缩短到 30 分钟内
  • 📈 数据驱动:Dashboard 实时大盘,GMV、订单量、转化率一目了然
  • 🔒 安全可控:每笔交易全链路追踪,资金流向透明
  • 💰 商业闭环:订单→支付→结算→提现,完整资金链路

1.3 平台效果展示

仪表盘主界面:校方登录后首先看到数据大盘,包含今日 GMV、订单量、支付转化率、在线师傅数,以及热门服务 TOP5 排行榜和师傅接单排行。

设备/服务管理:支持在线添加商品服务、设置价格和库存、配置服务区域,所有操作即时生效。

订单实时监控:订单状态实时流转(待支付→待接单→服务中→已完成),超时自动取消,退款流程可视化。

报警与通知:异常订单自动标记,后台通过 RabbitMQ 异步推送通知(短信、App 推送、站内信)。

数据查询与导出:支持按时间范围、服务类型、校区筛选历史订单,一键导出 Excel 报表。


二、🏗️ 技术架构与选型决策

2.1 整体架构设计

采用 微服务 + 前后端分离 架构,Gateway 统一入口、Nacos 服务治理、MQ 异步解耦、Canal 数据同步:

                        ┌───────────────────────────────────────────┐
                        │              客户端层                      │
    ┌───────────────────┤  Admin (:3000)    │   H5 (:3001)           │
    │                   │  Element Plus     │   Vant 4              │
    │                   └─────────┬─────────┴──────┬─────────────────┘
    │                             │     /api       │
    │                 Vite Proxy  │    代理转发     │  Vite Proxy
    │                             ▼                 ▼
    │                   ┌──────────────────────────────────────────────┐
    │                   │           API 网关层  (:8080)                │
    │                   │    Spring Cloud Gateway (Reactive Netty)     │
    │  ①全局鉴权        │  ┌─────────────────────────────────────────┐ │
    │  ②路由转发        │  │  AuthGlobalFilter (Order: -100)         │ │
    │  ③CORS跨域        │  │  白名单放行 · JWT校验 · 用户信息注入    │ │
    │  ④Sentinel限流    │  └─────────────────────────────────────────┘ │
    │                   └───────────────┬──────────────────────────────┘
    │                                   │  lb://campus-xxx
    │                                   │  客户端负载均衡
    │                                   ▼
    │                   ┌──────────────────────────────────────────────┐
    │                   │      服务注册 & 配置中心  (:8848)            │
    │                   │              Nacos 2.3                       │
    │                   │  ┌──────────────┐  ┌─────────────────────┐  │
    │                   │  │ 服务注册列表  │  │ 配置管理(namespace) │  │
    │                   │  │ 健康检查/心跳 │  │ campus-common.yaml  │  │
    │                   │  └──────────────┘  └─────────────────────┘  │
    │                   └──────────────────────────────────────────────┘
    │                                   │
    │        ┌──────────┬──────────┬────┼────┬──────────┬──────────────┐
    │        ▼          ▼          ▼    │    ▼          ▼              ▼
    │  ┌──────────┐ ┌────────┐ ┌──────┐│┌────────┐ ┌────────┐ ┌────────────┐
    │  │  auth    │ │  user  │ │product│││ order  │ │payment │ │  coupon    │
    │  │  :8081   │ │ :8082  │ │:8083  │││ :8084  │ │ :8085  │ │   ...      │
    │  │ 认证授权 │ │ 用户   │ │商品   │││ 订单   │ │ 支付   │ │  优惠券    │
    │  └────┬─────┘ └───┬────┘ └───┬───┘│└───┬────┘ └───┬────┘ └──────┬─────┘
    │       │           │         │     │    │          │             │
    └───────┼───────────┼─────────┼─────┼────┼──────────┼─────────────┼──────┘
            │           │         │     │    │          │             │
            ▼           ▼         ▼     ▼    ▼          ▼             ▼
    ┌───────────────────────────────────────────────────────────────────┐
    │                        中间件层 (Docker)                          │
    │                                                                   │
    │  ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────────┐              │
    │  │  MySQL   │ │ Redis  │ │ RabbitMQ │ │Elasticsearch│            │
    │  │ 8.0 :3307│ │7.2:6379│ │3.12 :5672│ │ 8.11 :9200 │             │
    │  │ 主数据库 │ │缓存/锁 │ │ 消息队列 │ │ 全文搜索  │              │
    │  └──────────┘ └────────┘ └──────────┘ └──────────┘              │
    │                                                                   │
    │  ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────────────────────┐  │
    │  │ MongoDB  │ │ MinIO  │ │ XXL-JOB  │ │  Canal + Logstash    │  │
    │  │6.0 :27017│ │ :9000  │ │ :18080   │ │  :11111     :5044    │  │
    │  │ IM消息  │ │ 文件存储│ │ 定时任务 │ │  数据同步  日志收集  │  │
    │  └──────────┘ └────────┘ └──────────┘ └──────────────────────┘  │
    └───────────────────────────────────────────────────────────────────┘

2.2 后端技术栈

技术版本用途选型理由
JDK17 LTS运行环境长期支持,GC 优化
Spring Boot3.2.5微服务基座生态成熟,自动配置
Spring Cloud2023.0.1微服务治理Gateway + LoadBalancer
Spring Cloud Alibaba2023.0.1.0服务注册/配置Nacos 深度集成
Nacos2.3.1注册中心+配置中心一体化方案,减少运维组件
MyBatis-Plus3.5.5ORM 框架分页插件、逻辑删除、租户拦截
ShardingSphere5.4.1分库分表order 服务 4 库 16 表
Druid1.2.20连接池SQL 监控、Wall 防火墙
Redisson3.30.0分布式锁看门狗机制,自动续期
jjwt0.12.5JWT 签发校验轻量,API 简洁
RabbitMQ3.12消息队列延迟队列、死信机制
Canal1.1.7binlog 订阅零侵入数据同步
XXL-JOB2.4.0分布式定时任务可视化调度、失败重试
Knife4j4.5.0API 文档Gateway 聚合模式
Jasypt3.0.5配置加密敏感配置密文存储
MapStruct1.5.5对象映射编译期生成,零反射
Hutool5.8.25工具集国产良心,开箱即用

2.3 前端技术栈

技术用途选型理由
Vue 3.5 + Composition API核心框架<script setup> 语法糖,代码更简洁
TypeScript 5.x类型安全编译期拦截 90% 的低级错误
Vite 6构建工具HMR < 500ms,原生 ESM
Element Plus 2.9Admin UI 组件库企业级组件,暗色主题
Vant 4H5 移动端 UI轻量,适配移动端
ECharts 5数据可视化Dashboard 图表引擎
UnoCSS原子化 CSS按需生成,零运行时
Pinia 2状态管理Vue 3 官方推荐,TS 友好
AxiosHTTP 客户端拦截器链,请求/响应统一处理
pnpm + Turborepo包管理+构建节省磁盘空间,增量构建

2.4 技术组件选型对比

几个关键选型做过详细对比:

决策点选择方案对比方案选择原因
注册中心Nacos 2.3Eureka / Consul兼顾注册+配置,与 SCA 深度集成
API 网关Spring Cloud GatewayZuul 1.x / KongReactive 非阻塞,性能优于 Zuul
分布式事务MQ 最终一致性Seata AT性能损耗小,MQ 天然解耦
搜索同步Canal 旁路监听业务双写零业务侵入,binlog 天然有序
前端 UIElement PlusAnt Design Vue社区更活跃,暗色主题成熟
对象存储MinIO阿里云 OSS开发环境自托管,兼容 S3 API
令牌方案JWT 双 TokenSession + Redis无状态,适合微服务横向扩展

2.5 前后端分离架构优势

传统单体架构                         校享云微服务架构
┌──────────────┐                    ┌──────────────────────────────┐
│  单体应用    │                    │  前端 (Vue 3 + Vite)          │
│  Spring MVC  │                    │  Admin :3000  │  H5 :3001    │
│  + JSP/Thym  │                    └───────┬───────┴──────┬───────┘
│  所有逻辑    │                            │   REST API   │
│  耦合在一起  │                    ┌───────┴──────────────┴───────┐
│              │                    │  Gateway (:8080) 统一入口     │
│              │                    │  鉴权 + 路由 + 限流 + CORS   │
│              │                    └───────┬──────────────────────┘
│              │                            │
│  难以扩展    │            ┌───────────────┼───────────────┐
│  技术栈锁定  │            ▼               ▼               ▼
│  部署风险高  │      ┌──────────┐  ┌──────────┐  ┌──────────┐
└──────────────┘      │  auth    │  │  order   │  │  payment │
                      │  独立部署 │  │  独立部署 │  │  独立部署 │
                      └──────────┘  └──────────┘  └──────────┘
                      可独立扩缩容   技术栈灵活    故障隔离

2.6 项目结构设计

Campus-Cloud/                              # 根目录
│
├── 📁 campus-common/                      # 公共模块聚合
│   ├── common-core/                       # 统一返回体Result、BizException、工具类
│   ├── common-security/                   # JWT工具、Gateway鉴权、UserContext
│   ├── common-mybatis/                    # MyBatis-Plus配置、租户拦截器
│   ├── common-mq/                         # RabbitMQ交换机/队列/绑定声明
│   └── common-es/                         # Elasticsearch Java Client配置
│
├── 📁 campus-gateway/          (:8080)    # API网关 — 统一鉴权、路由转发
├── 📁 campus-auth/             (:8081)    # 认证服务 — 登录、JWT、RBAC
├── 📁 campus-user/             (:8082)    # 用户服务 — 个人信息、地址
├── 📁 campus-product/          (:8083)    # 商品服务 — SPU/SKU、分类
├── 📁 campus-order/            (:8084)    # 订单服务 — 状态机、分库分表
├── 📁 campus-payment/          (:8085)    # 支付服务 — 流水、退款
├── 📁 campus-coupon/           (···)     # 优惠券 — 秒杀(Redis+Lua)
├── 📁 campus-settlement/       (···)     # 结算服务 — 分账、提现
├── 📁 campus-review/           (···)     # 评价服务 — 评分、投诉
├── 📁 campus-search/           (···)     # 搜索服务 — ES全文检索
├── 📁 campus-im/               (···)     # 即时通讯 — WebSocket+MongoDB
├── 📁 campus-file/             (···)     # 文件服务 — MinIO对象存储
├── 📁 campus-data/             (···)     # 数据服务 — Dashboard大盘
├── 📁 campus-tenant/           (···)     # 租户服务 — 多校区隔离
├── 📁 campus-notify/           (···)     # 通知服务 — SMS/App/站内信
│
├── 📁 campus-frontend/                   # 前端 Monorepo (pnpm workspace)
│   ├── packages/
│   │   ├── admin/             (:3000)    # 校方管理后台 (Element Plus)
│   │   └── h5/                (:3001)    # 移动端 (Vant)
│   └── shared/                           # 共享类型、工具、常量
│
├── 📁 campus-scripts/                    # 运维脚本
│   ├── sql/                              # 数据库初始化脚本
│   ├── ci/                               # GitHub Actions CI/CD
│   ├── canal/                            # Canal 配置
│   ├── k8s/                              # Kubernetes 部署模板
│   └── elk/                              # ELK 日志收集配置
│
├── 📄 docker-compose.yml                 # 中间件一键部署
├── 📄 .env.example                       # 环境变量模板(可安全提交)
├── 📄 pom.xml                            # Maven 父 POM(15 模块)
└── 📄 README.md

三、💡 核心功能模块详解

3.1 前端架构设计与用户体验优化

3.1.1 响应式布局设计

Admin 管理后台采用 深色侧边栏 + 白色主内容区 经典布局,通过 Element Plus 的 el-container + el-aside 实现:

<!-- DefaultLayout.vue — 管理后台布局骨架 -->
<template>
  <el-container style="height: 100vh">
    <!-- 深色侧边栏:可折叠,hover 展开 -->
    <el-aside :width="isCollapse ? '64px' : '220px'" class="aside">
      <div class="logo">
        <span class="logo-mark">
          <span class="lg-a">X</span>
          <span class="lg-b">C</span>
          <span class="lg-c">Y</span>
        </span>
        <span v-if="!isCollapse" class="logo-text">校享云</span>
      </div>

      <el-menu
        :default-active="route.path"
        :collapse="isCollapse"
        background-color="#0F172A"
        text-color="#94A3B8"
        active-text-color="#4F6EF7"
        router
      >
        <el-menu-item v-for="item in menuItems"
          :key="item.path" :index="item.path">
          <el-icon><component :is="item.icon" /></el-icon>
          <span>{{ item.title }}</span>
        </el-menu-item>
      </el-menu>
    </el-aside>

    <!-- 主内容区 -->
    <el-container>
      <el-header class="header">
        <el-breadcrumb>...</el-breadcrumb>
        <el-dropdown>👤 管理员 · 退出登录</el-dropdown>
      </el-header>
      <el-main>
        <router-view />   <!-- 页面内容渲染区 -->
      </el-main>
    </el-container>
  </el-container>
</template>

响应式断点设计:

屏幕宽度侧边栏内容区表格列数
≥ 1440px220px 展开自适应全部显示
1024~1440px64px 折叠自适应隐藏次要列
< 1024px隐藏(抽屉)100%仅关键列
3.1.2 数据可视化组件设计

Dashboard 使用 ECharts 5 实现数据大屏,核心指标卡片 + 图表组合:

<!-- dashboard/index.vue — 数据大盘 -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'

const stats = ref({ todayGMV: 0, todayOrders: 0 })
const topServices = ref<Array<{name:string, salesCount:number}>>([])
const workerRanking = ref<Array<{worker_id:number, order_count:number}>>([])

onMounted(async () => {
  try {
    // 并发请求,Promise.all 减少等待时间
    const [overview, top, ranking] = await Promise.all([
      axios.get('/api/v1/data/dashboard/overview'),
      axios.get('/api/v1/product/spu/top?n=5'),
      axios.get('/api/v1/order/stats/worker-ranking'),
    ])
    stats.value = overview.data.data
    topServices.value = top.data.data
    workerRanking.value = ranking.data.data
  } catch { /* 后端未启动时静默降级 */ }
})
</script>

统计卡片设计要点

  • 4 个核心指标并排(GMV / 订单量 / 转化率 / 在线师傅)
  • 每张卡片包含趋势箭头(↑ 涨绿色 / ↓ 跌红色 / 持平灰色)
  • 数据使用 toLocaleString() 格式化(大数字加千分位逗号)
  • 左侧热门服务 TOP5 表格 + 右侧师傅接单排行
3.1.3 状态管理设计

项目使用 Pinia 管理全局状态,核心 store:

stores/
├── user.ts        # 用户信息 (token、userType、nickname)
├── permission.ts  # 权限路由 (动态菜单)
└── app.ts         # 全局设置 (侧边栏折叠、主题色)

Token 管理策略:

localStorage
├── accessToken     # 短期 Token(2小时),每次请求携带
├── refreshToken    # 长期 Token(7天),用于无感刷新
└── userType        # STUDENT / WORKER / ADMIN

Pinia Store (内存)
└── userStore       # 响应式用户状态,映射到 localStorage
3.1.4 设备管理模块(商品服务管理)

校方管理员可通过后台在线添加/编辑服务商品:

商品管理页面功能树
│
├── 商品列表
│   ├── 分页表格(图片、名称、价格、库存、状态)
│   ├── 搜索筛选(按名称、分类、状态)
│   ├── 批量操作(上架/下架/删除)
│   └── 排序(按销量、价格、创建时间)
│
├── 添加商品
│   ├── 基本信息(名称、描述、分类)
│   ├── SKU 管理(规格、价格、库存)
│   ├── 图片上传(MinIO 预签名直传)
│   └── 服务区域配置(校区、宿舍楼)
│
└── 商品详情
    ├── 实时数据(今日销量、累计销量、好评率)
    ├── 历史数据图表(7日/30日趋势)
    └── 操作日志(谁、何时、做了什么)
3.1.5 实时数据监控模块

订单状态通过 WebSocket + MQ 实现实时推送:

订单生命周期实时监控:

  创建订单          支付成功          师傅接单          服务完成
  ┌─────────┐     ┌─────────┐      ┌─────────┐      ┌─────────┐
  │PENDING  │ ──▶ │PENDING  │ ──▶  │IN       │ ──▶  │COMPLETED│
  │_PAY     │     │_ACCEPT  │      │_PROGRESS│      │         │
  └─────────┘     └─────────┘      └─────────┘      └─────────┘
       │               │                                  │
       │   15分钟超时   │    师傅拒单                       │ 评价打星
       ▼               ▼                                  ▼
  ┌─────────┐     ┌─────────┐                      ┌─────────┐
  │CANCELED │     │退回待接单│                      │ REVIEW  │
  └─────────┘     └─────────┘                      │_DONE    │
                                                   └─────────┘

核心代码实现 — 订单状态机(State Pattern)

// OrderState.java — 状态接口
public interface OrderState {
    void pay(OrderContext ctx);          // 支付
    void accept(OrderContext ctx);       // 接单
    void complete(OrderContext ctx);     // 完成服务
    void cancel(OrderContext ctx);       // 取消
    void applyRefund(OrderContext ctx);  // 申请退款
}

// PendingPayState.java — 待支付状态
public class PendingPayState implements OrderState {
    @Override
    public void pay(OrderContext ctx) {
        // 1. 校验支付金额
        // 2. 扣款(调用 payment 服务)
        // 3. 状态迁移
        ctx.setState(new PendingAcceptState());
        ctx.save();
        // 4. 发送 MQ 消息(通知师傅、更新搜索索引...)
        mqSender.send("order.paid", new OrderPaidEvent(ctx.getOrder()));
    }

    @Override
    public void cancel(OrderContext ctx) {
        // 超时取消:归还库存、释放优惠券
        ctx.setState(new CanceledState());
        ctx.save();
        mqSender.send("order.canceled", new OrderCanceledEvent(ctx.getOrder()));
    }

    @Override
    public void accept(OrderContext ctx) {
        throw new BizException(ErrorCode.ORDER_NOT_PAID);  // 未支付不可接单
    }
    // ...
}

为什么用状态模式而不是 if-else?

// ❌ 传统 if-else 写法(难维护)
public void handleOrder(Order order, String action) {
    if (order.getStatus() == PENDING_PAY) {
        if (action.equals("pay")) { /* ... */ }
        else if (action.equals("cancel")) { /* ... */ }
    } else if (order.getStatus() == PENDING_ACCEPT) {
        if (action.equals("accept")) { /* ... */ }
        else if (action.equals("cancel")) { /* ... */ }
    }
    // 7 个状态 × 5 种操作 = 35 个分支... 难以维护!
}

// ✅ 状态模式(清晰可扩展)
orderContext.getState().pay(orderContext);   // 状态自己决定行为
// 新增状态只需新增一个 State 类,符合开闭原则

3.2 认证与权限控制

3.2.1 JWT 双 Token 认证流程
    Client                  Gateway                    Auth Service            MySQL         Redis
      │                        │                           │                    │             │
      │──POST /login──────────▶│ (白名单放行)               │                    │             │
      │  {phone, password}     │──────────────────────────▶│                    │             │
      │                        │                           │──查询用户──────────▶│             │
      │                        │                           │◀──SysUser PO───────│             │
      │                        │                           │                    │             │
      │                        │                           │──BCrypt.matches()  │             │
      │                        │                           │──buildTokens()     │             │
      │                        │                           │  accessToken(2h)  │             │
      │                        │                           │  refreshToken(7d) │             │
      │                        │                           │──更新登录时间──────▶│             │
      │                        │◀──Result.success(tokens)──│                    │             │
      │◀──{accessToken,        │                           │                    │             │
      │    refreshToken,        │                           │                    │             │
      │    userType,            │                           │                    │             │
      │    userId, nickname}    │                           │                    │             │
      │                        │                           │                    │             │
      │  localStorage.set()    │                           │                    │             │
      │                        │                           │                    │             │
      │──GET /order/list──────▶│                           │                    │             │
      │  Authorization:        │                           │                    │             │
      │    Bearer eyJhbGc...   │──JWT校验───────────────────│                    │             │
      │                        │──注入Header(X-User-Id等)──▶│                    │             │
      │                        │                           │──查询订单──────────▶│             │
      │                        │◀────Result.success(list)──│                    │             │
      │◀──订单列表 JSON────────│                           │                    │             │
3.2.2 JWT Token 签发核心代码
// JwtTokenProvider.java — Token 工具类
@Component
public class JwtTokenProvider {

    private SecretKey secretKey;

    // AccessToken:2 小时,含完整用户信息
    public String createAccessToken(Long userId, Long tenantId,
                                     String userType, List<String> roles) {
        Date now = new Date();
        return Jwts.builder()
                .subject(String.valueOf(userId))        // sub = userId
                .claim("tid", tenantId)                  // 租户ID
                .claim("typ", userType)                  // STUDENT/WORKER/ADMIN
                .claim("rol", String.join(",", roles))   // 角色列表
                .id(UUID.randomUUID().toString())        // jti(黑名单用)
                .issuedAt(now)
                .expiration(new Date(now.getTime() + 7200_000))  // 2 小时
                .signWith(secretKey)
                .compact();
    }

    // RefreshToken:7 天,只含 userId + tenantId
    public String createRefreshToken(Long userId, Long tenantId) {
        Date now = new Date();
        return Jwts.builder()
                .subject(String.valueOf(userId))
                .claim("tid", tenantId)
                .issuedAt(now)
                .expiration(new Date(now.getTime() + 604800_000))  // 7 天
                .signWith(secretKey)
                .compact();
    }

    // Token 校验
    public boolean validateToken(String token) {
        try {
            Jwts.parser().verifyWith(secretKey).build()
                .parseSignedClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}
3.2.3 三层安全防御
攻击面分析:

  互联网 ───▶ Gateway (:8080) ───▶ 微服务 (:8081~)
                 ▲                        ▲
                 │ 第①层                   │ 第②层+第③层
                 │                        │
  认证边界 ──────┘             内部信任边界 ─┘

  第①层:Gateway AuthGlobalFilter
         · 白名单路径放行
         · 非白名单 → JWT 校验
         · 校验通过 → 注入 X-User-Id、X-Gateway-Key 等 Header

  第②层:下游服务 GatewayAuthFilter
         · 校验 X-Gateway-Key 秘钥
         · 拒绝所有非 Gateway 来源的直连请求
         · 效果:curl localhost:8081/api/user → 401 "请通过网关访问"

  第③层:UserContext ThreadLocal + 租户 SQL 拦截
         · 解析 Header → 写入 ThreadLocal
         · MyBatis-Plus 自动追加 WHERE tenant_id = ?
         · 业务代码零感知的多租户隔离

直接访问微服务会被拒绝:

# ❌ 绕过网关直连(第②层拦截)
$ curl http://localhost:8081/api/v1/user/info
{"code":401,"message":"请通过网关访问"}

3.3 消息通信与异步解耦

3.3.1 RabbitMQ 延迟队列 — 订单超时取消

不使用定时任务轮询数据库,而是用 RabbitMQ 的 TTL + Dead Letter 机制:

                    ┌────────────────────────────────────┐
                    │         order.delay.exchange       │
                    │           (Topic Exchange)          │
                    └──────────┬─────────────────────────┘
                               │ routingKey: order.delay
                               ▼
                    ┌────────────────────────────────────┐
                    │         order.delay.queue          │
                    │         TTL = 15 分钟               │
                    │         x-dead-letter-exchange      │
                    │           = order.dlx              │
                    │         x-dead-letter-routing-key   │
                    │           = order.cancel            │
                    └──────────┬─────────────────────────┘
                               │ 15分钟后自动过期
                               ▼
                    ┌────────────────────────────────────┐
                    │          order.dlx (Fanout)         │
                    └──────────┬─────────────────────────┘
                               │ routingKey: order.cancel
                               ▼
                    ┌────────────────────────────────────┐
                    │        order.cancel.queue          │
                    │  → OrderEventConsumer 消费         │
                    │  → 检查订单状态是否为 PENDING_PAY   │
                    │  → 取消订单 + 归还库存 + 退优惠券   │
                    └────────────────────────────────────┘
// OrderEventConsumer.java — 延迟超时取消消费者
@RabbitListener(queues = "order.cancel.queue")
public void handleOrderTimeout(OrderTimeoutMessage msg) {
    Order order = orderMapper.selectById(msg.getOrderId());
    // 双重检查:只有待支付状态的订单才取消
    if (order != null && order.getStatus() == OrderStatus.PENDING_PAY) {
        orderContext.setState(order, new CanceledState());
        orderService.updateById(order);

        // 恢复库存
        productService.restoreStock(order.getSkuId(), order.getQuantity());
        // 归还优惠券
        if (order.getCouponId() != null) {
            couponService.returnCoupon(order.getCouponId(), order.getUserId());
        }
        log.info("订单超时自动取消: orderId={}", msg.getOrderId());
    }
}
3.3.2 Canal 数据同步管道 (CQRS)

搜索场景采用 CQRS 模式:写走 MySQL,读走 Elasticsearch,通过 Canal 监听 binlog 实现准实时同步:

  ┌──────────┐   INSERT/UPDATE   ┌──────────┐   binlog事件    ┌──────────┐
  │  MySQL   │ ───────────────▶  │  Canal   │ ─────────────▶ │ RabbitMQ │
  │ product  │      /DELETE      │  Server  │    投递消息     │  Queue   │
  └──────────┘                   └──────────┘                └────┬─────┘
                                                                  │
                                                  消费 binlog 事件 │
                                                                  ▼
                                                         ┌──────────────┐
                                                         │ search-service│
                                                         │  (消费者)     │
                                                         └──────┬───────┘
                                                                │ Bulk API
                                                                ▼
                                                         ┌──────────────┐
                                                         │Elasticsearch │
                                                         │campus_product │
                                                         │   索引       │
                                                         └──────────────┘

  兜底机制:XXL-JOB 每 30 分钟全量同步 modified_time > last_sync_time 的记录

四、🗄️ 数据库设计与优化

4.1 核心表结构设计

用户认证表 (sys_user)
CREATE TABLE sys_user (
    id          BIGINT PRIMARY KEY COMMENT 'Snowflake ID',
    tenant_id   BIGINT NOT NULL DEFAULT 0 COMMENT '租户ID(校区)',
    phone       VARCHAR(20) NOT NULL COMMENT '手机号',
    password    VARCHAR(255) NOT NULL COMMENT 'BCrypt密文',
    nickname    VARCHAR(50) COMMENT '昵称',
    avatar      VARCHAR(500) COMMENT '头像URL',
    user_type   VARCHAR(20) NOT NULL DEFAULT 'STUDENT' COMMENT 'STUDENT/WORKER/ADMIN',
    status      TINYINT DEFAULT 1 COMMENT '1=正常 0=禁用',
    last_login_time DATETIME COMMENT '最后登录时间',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE,
    is_deleted  TINYINT DEFAULT 0 COMMENT '逻辑删除',
    UNIQUE KEY uk_phone (phone),
    INDEX idx_tenant_type (tenant_id, user_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
订单表 (orders) — 分库分表设计
-- order_db0 ~ order_db3,每库 16 张表 (orders_0 ~ orders_15)
-- 分片键:user_id % 64
-- 总共 4×16 = 64 张物理表

CREATE TABLE orders_0 (
    id              BIGINT PRIMARY KEY COMMENT 'Snowflake ID',
    user_id         BIGINT NOT NULL COMMENT '用户ID(分片键)',
    tenant_id       BIGINT NOT NULL COMMENT '校区ID',
    order_no        VARCHAR(32) NOT NULL COMMENT '订单号(唯一)',
    status          VARCHAR(20) NOT NULL DEFAULT 'PENDING_PAY'
                    COMMENT 'PENDING_PAY/PENDING_ACCEPT/IN_PROGRESS/
                            COMPLETED/CANCELED/REFUNDING/REFUNDED',
    total_amount    DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
    pay_amount      DECIMAL(10,2) NOT NULL COMMENT '实付金额',
    sku_id          BIGINT NOT NULL COMMENT 'SKU ID',
    quantity        INT NOT NULL DEFAULT 1 COMMENT '数量',
    coupon_id       BIGINT COMMENT '优惠券ID',
    address_snapshot JSON COMMENT '地址快照',
    order_snapshot  JSON NOT NULL COMMENT '订单快照(完整订单详情JSON)',
    pay_time        DATETIME COMMENT '支付时间',
    accept_time     DATETIME COMMENT '接单时间',
    complete_time   DATETIME COMMENT '完成时间',
    create_time     DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time     DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE,
    is_deleted      TINYINT DEFAULT 0,
    UNIQUE KEY uk_order_no (order_no),
    INDEX idx_user_status (user_id, status),
    INDEX idx_tenant_time (tenant_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

订单快照字段 (order_snapshot JSON) 是核心设计亮点:

{
  "skuName": "空调清洗(挂机)",
  "skuPrice": 88.00,
  "categoryName": "家电维修",
  "address": "东校区·12号楼·305室",
  "workerId": 1692345678901234567,
  "workerName": "张师傅",
  "couponDiscount": 10.00,
  "createTime": "2026-07-14T10:30:00"
}

快照的好处:即使后续商品改名、调价、师傅离职,历史订单数据也不受影响。

4.2 数据库性能优化

4.2.1 索引策略
-- 1. 高频查询索引
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time DESC);

-- 2. 覆盖索引(避免回表)
ALTER TABLE orders ADD INDEX idx_cover_list (tenant_id, status, create_time, id, order_no, total_amount);

-- 3. 校方管理后台的全局搜索(LEFT 前缀匹配)
ALTER TABLE orders ADD INDEX idx_order_no_prefix (order_no(16));

-- 4. 时间范围查询(分区裁剪的前提)
ALTER TABLE orders ADD INDEX idx_create_time (create_time);
4.2.2 分库分表策略
# ShardingSphere-JDBC 配置
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1,ds2,ds3
      ds0: # order_db0
      ds1: # order_db1
      ds2: # order_db2
      ds3: # order_db3
    rules:
      sharding:
        tables:
          orders:
            actual-data-nodes: ds$->{0..3}.orders_$->{0..15}  # 64 张物理表
            database-strategy:
              standard:
                sharding-column: user_id
                algorithm-expression: ds$->{user_id % 4}       # 4 库
            table-strategy:
              standard:
                sharding-column: user_id
                algorithm-expression: orders_$->{user_id % 16} # 16 表

五、📨 消息通信与异步解耦

5.1 RabbitMQ 整体拓扑

                         ┌──────────────────────────────────────────┐
                         │          RabbitMQ 交换机/队列拓扑          │
                         │                                          │
  ┌──────────────┐      │  ┌─────────────────────────────────────┐  │
  │ order-service │─────▶│  │  order.event.exchange (Topic)      │  │
  └──────────────┘      │  │  ├─ order.created → order.created.q │  │
                         │  │  ├─ order.paid    → order.paid.q   │  │
  ┌──────────────┐      │  │  └─ order.canceled→ order.canceled.q│  │
  │payment-service│────▶│  └─────────────────────────────────────┘  │
  └──────────────┘      │                                          │
                         │  ┌─────────────────────────────────────┐  │
  ┌──────────────┐      │  │  coupon.exchange (Topic)            │  │
  │ coupon-server│─────▶│  │  ├─ coupon.grab.success             │  │
  └──────────────┘      │  │  └─ coupon.used / coupon.expired    │  │
                         │  └─────────────────────────────────────┘  │
                         │                                          │
                         │  ┌─────────────────────────────────────┐  │
                         │  │  notify.exchange (Topic)            │  │
                         │  │  ├─ notify.sms / notify.app        │  │
                         │  │  └─ notify.inbox                   │  │
                         │  └─────────────────────────────────────┘  │
                         │                                          │
                         │  ┌─────────────────────────────────────┐  │
                         │  │  db.sync.exchange (Topic)           │  │
                         │  │  └─ db.sync.product_spu → ES       │  │
                         │  └─────────────────────────────────────┘  │
                         └──────────────────────────────────────────┘

5.2 WebSocket 即时通讯

IM 服务使用 Netty + WebSocket 实现实时消息:

  Student App                    IM Server (:8090)               Worker App
      │                                │                              │
      │──WS CONNECT /ws───────────────▶│◀──────WS CONNECT /ws────────│
      │──{"type":"ping"}──────────────▶│◀─────{"type":"pong"}───────│ (30s心跳)
      │                                │                              │
      │──{"type":"text",──────────────▶│                              │
      │    "content":"师傅几点到?"}      │──存储MongoDB────────────────▶│
      │                                │──推送给Worker────────────────▶│
      │                                │                              │
      │                    (Worker离线时)                             │
      │                                │──存入Redis List(离线消息)     │
      │                                │  (重连后批量拉取)             │

消息存储双写策略:

存储用途过期策略
MongoDB历史消息永久存储TTL 90 天自动清理
Redis List离线消息暂存拉取后清除
Redis Hash未读计数HINCRBY im:unread:{userId} {sessionId}

六、🔐 安全认证与权限控制

6.1 前端安全 — Axios 拦截器

// shared/utils/request.ts — Token 无感刷新核心逻辑
let isRefreshing = false
let refreshQueue: Array<(token: string) => void> = []

http.interceptors.response.use(
  (res) => res,
  async (error) => {
    if (error.response?.status === 401 && !error.config._retry) {
      error.config._retry = true

      // 并发 401 排队机制(关键!)
      if (!isRefreshing) {
        isRefreshing = true
        try {
          const refreshToken = localStorage.getItem('refreshToken')
          const res = await axios.post('/api/v1/auth/refresh', { refreshToken })
          const newToken = res.data.data.accessToken
          localStorage.setItem('accessToken', newToken)

          // 唤醒所有排队请求
          refreshQueue.forEach(cb => cb(newToken))
          refreshQueue = []
          return http(error.config)
        } catch {
          localStorage.clear()
          window.location.href = '/login'
        } finally {
          isRefreshing = false
        }
      }

      // 排队等待
      return new Promise((resolve) => {
        refreshQueue.push((token: string) => {
          error.config.headers.Authorization = `Bearer ${token}`
          resolve(http(error.config))
        })
      })
    }
    return Promise.reject(error)
  }
)

6.2 后端安全 — 密码加密与限流

// 登录失败限流(Redis)
// 同一个手机号 5 分钟内失败 5 次 → 锁定 30 分钟
public void checkLoginRateLimit(String phone) {
    String key = "login:fail:" + phone;
    Long failCount = redisTemplate.opsForValue().increment(key);
    if (failCount == 1) {
        redisTemplate.expire(key, 5, TimeUnit.MINUTES);  // 窗口 5 分钟
    }
    if (failCount > 5) {
        redisTemplate.expire(key, 30, TimeUnit.MINUTES);  // 锁 30 分钟
        throw new BizException(ErrorCode.LOGIN_LOCKED, "登录过于频繁,请30分钟后重试");
    }
}

// Token 登出黑名单
public void logout(String token) {
    String jti = jwtTokenProvider.getTokenId(token);
    Date expiration = jwtTokenProvider.getExpiration(token);
    long ttl = expiration.getTime() - System.currentTimeMillis();
    if (ttl > 0) {
        redisTemplate.opsForValue().set(
            "token:blacklist:" + jti, "1", Duration.ofMillis(ttl));
    }
}

七、🚀 性能优化实战经验

7.1 前端性能优化

策略实现效果
路由懒加载() => import('@/views/dashboard/index.vue')首屏 JS 减少 60%
组件按需引入unplugin-vue-components + Element Plus resolverTree-shaking 自动移除未使用组件
图表优化ECharts notMerge: true + 销毁前 dispose()避免内存泄漏
请求缓存Dashboard 数据 30s 内不重复请求减少无效网络请求
图片懒加载Vant Lazyload 指令移动端加载速度提升 40%

7.2 后端性能优化

优惠券秒杀 — Redis + Lua 原子化
-- scripts/grab_coupon.lua
-- 单次 EVAL 完成:查库存 + 查重复 + 扣减 + 标记
local stockKey = KEYS[1]    -- coupon:stock:{id}
local grabbedKey = KEYS[2]  -- coupon:grabbed:{id}
local userId = ARGV[1]

if redis.call('SISMEMBER', grabbedKey, userId) == 1 then
    return {0, 'ALREADY_GRABBED'}
end

local stock = tonumber(redis.call('GET', stockKey) or '0')
if stock <= 0 then
    return {0, 'SOLD_OUT'}
end

redis.call('DECRBY', stockKey, 1)
redis.call('SADD', grabbedKey, userId)
return {1, 'SUCCESS', stock - 1}

性能对比

方案QPS复杂度超卖风险
synchronized~1K分布式下失效
Redisson 分布式锁~5K锁超时可能超卖
Redis Lua 原子操作~50K零风险

7.3 数据缓存策略

缓存层次结构:

  第 0 层:浏览器缓存 (localStorage/IndexedDB)
     ↓ MISS
  第 1 层:CDN 缓存 (静态资源 .js/.css/.png)
     ↓ MISS
  第 2 层:Redis 缓存 (热点数据)
     ├── 商品详情:TTL 30 分钟
     ├── 用户信息:TTL 1 小时
     ├── Dashboard 统计:TTL 5 分钟
     ├── 热搜词排行:SortedSet
     └── Token 黑名单:TTL = 剩余有效期
     ↓ MISS
  第 3 层:MySQL / Elasticsearch (持久存储)

八、🔧 深度踩坑与解决方案

8.1 前端绕过 Gateway 直连微服务 ⭐⭐⭐⭐⭐

问题描述:前端能登录但 Dashboard 数据不加载,Gateway 日志中没有任何请求。排查发现 Vite proxy 的 target 配置成了微服务直连端口而非 Gateway 端口。

现象

  • 登录接口 /api/v1/auth/login 正常(返回 Token)
  • 但登录后的数据请求无响应
  • Gateway 日志为空
  • 浏览器 Network 面板看不到 /api/v1/data/dashboard/overview 请求(因为根本没发到 Gateway)

根因

// ❌ 错误配置 — 指向 auth 微服务直连端口
proxy: { '/api': { target: 'http://localhost:8081' } }

// ✅ 正确配置 — 指向 Gateway 统一入口
proxy: { '/api': { target: 'http://localhost:8080' } }

教训:前端 proxy 是安全链路的关键一环,配置错一个端口号,Gateway 的所有鉴权逻辑全部被绕过。

8.2 Docker Desktop IPv6 导致 MySQL 连接失败 ⭐⭐⭐⭐

问题描述:Spring Boot 启动时 Druid 连接池持续报错 Communications link failure,报错信息为 Can not read response from server. Expected to read 4 bytes, read 0 bytes

排查过程

  1. docker ps → MySQL 容器状态 healthy
  2. docker exec mysql mysql -e 'SELECT 1' → 正常 ✅
  3. 宿主机 telnet localhost 3307 → TCP 连接成功 ✅
  4. Java 应用 JDBC 连接 → ❌ Communications link failure

根因:Windows 上 localhost 解析为 IPv6 地址 ::1,而 Docker Desktop 的端口转发在 IPv6 上存在 bug——TCP 三次握手能完成,但 MySQL 协议握手包(Server Greeting)无法正确送达客户端。

解决方案

# ✅ 强制使用 IPv4 回环地址
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3307/campus_user?useSSL=false&...

8.3 Token 刷新并发雪崩 ⭐⭐⭐⭐⭐

问题描述:AccessToken 过期瞬间,页面上 5 个并发 API 请求同时触发 401,导致 5 次并发刷新 Token。第二次刷新使第一次拿到的 Token 立即失效,后续请求再次 401 → 被踢回登录页。

时间线

T+0ms   请求A/B/C/D/E 同时发出(Token 刚好过期)
T+50ms  5 个请求同时返回 401
T+51ms  5 个请求同时调用 /auth/refresh
T+100ms 第一次 refresh 成功 → newToken_v1
T+110ms 第二次 refresh 成功 → newToken_v2(v1 失效!)
T+150ms 第三次 refresh 成功 → newToken_v3(v2 失效!)
T+200ms 请求A 用 v3 重试 → ✅ OK
T+210ms 请求B/C/D 用 v1/v2 重试 → 401
T+300ms 被踢回登录页 😱

解决方案:实现请求队列机制——第一个 401 请求持有刷新锁,其他 401 请求排队等待,Token 刷新完成后统一唤醒重试(见 §6.1 代码)。

8.4 端口被旧进程占用 ⭐⭐⭐

问题:重启服务时报 Port 8081 was already in use

快速排查

# Windows
netstat -ano | findstr :8081          # 找到 PID
taskkill -F -PID <PID>                 # 杀掉进程

# Linux / Mac
lsof -i :8081                          # 找到 PID
kill -9 <PID>

8.5 图表组件内存泄漏 ⭐⭐⭐

问题:Dashboard 页面切换后,浏览器内存持续增长,最终标签页崩溃。

根因:ECharts 实例没有在组件卸载时销毁。

修复

<script setup>
import { onBeforeUnmount, ref } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref<HTMLDivElement>()
let chartInstance: echarts.ECharts | null = null

onMounted(() => {
  chartInstance = echarts.init(chartRef.value!)
  chartInstance.setOption({ /* ... */ })
})

// ⚠️ 关键:组件卸载时销毁 ECharts 实例
onBeforeUnmount(() => {
  chartInstance?.dispose()
  chartInstance = null
})
</script>

九、💎 总结与未来展望

9.1 项目价值总结

维度实现情况
微服务拆分14 个服务,独立数据库,独立部署
网关安全三层纵深防御(Gateway → 下游 Filter → 租户拦截)
高并发处理Redis + Lua 秒杀 QPS 50K,分库分表 64 片
数据同步Canal + MQ + ES 准实时搜索,零业务侵入
前后端工程化TypeScript + Monorepo + 请求队列 + 三端统一登录
运维部署Docker Compose 一键启动 12 个中间件,健康检查全覆盖

9.2 技术亮点

  1. 状态模式驱动订单流转 — 7 种状态 × 5 种操作,无 if-else 嵌套
  2. Redis + Lua 原子化秒杀 — 单次 EVAL 完成查库存+查重复+扣减+标记
  3. CQRS 搜索管道 — Canal 监听 binlog → MQ → ES,读写分离
  4. 延迟队列超时取消 — RabbitMQ TTL + Dead Letter,无需轮询数据库
  5. 并发 401 排队刷新 — 前端 Token 无感刷新,避免雪崩
  6. 三层安全防御 — Gateway → GatewayAuthFilter → ThreadLocal 层层把关
  7. 订单快照设计 — 下单时 JSON 序列化完整快照,历史数据永不丢失
  8. 多租户 SQL 拦截 — MyBatis-Plus 自动追加 WHERE tenant_id = ?

9.3 未来规划

短期(1-2 月)                  中期(3-6 月)              长期(6-12 月)
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ · 微信小程序端   │    │ · 智能派单算法    │    │ · 多校区 SaaS 化  │
│ · 支付宝支付集成 │    │ · AI 客服机器人   │    │ · 数据分析中台    │
│ · 单元测试覆盖   │    │ · 灰度发布        │    │ · 国际化支持      │
│ · CI/CD 流水线   │    │ · 全链路监控      │    │ · IoT 设备接入    │
└──────────────────┘    └──────────────────┘    └──────────────────┘

📚 参考资料


CoderDongHuang · 编程星辰海
2026 年 7 月 · 校享云 Campus-Cloud

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程星辰海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值