企业级微前端大型应用运维部署方案书

日期: 2026-02-10
版本: v1.0
适用范围: 基于 Wujie + Vue 3 + Vite + pnpm Monorepo 的微前端架构
目标读者: 前端架构师、DevOps 工程师、技术负责人


目录


一、项目架构概览

1.1 Monorepo 仓库结构

cmclink-web-main/                    # pnpm Monorepo 根目录
├── apps/
│   └── main/                        # 主应用(@cmclink/main)— Wujie Host
├── packages/
│   ├── api/                         # @cmclink/api — 统一 API 层
│   ├── biz/                         # @cmclink/biz — 业务组件
│   ├── eslint-config/               # @cmclink/eslint-config — ESLint 规范
│   ├── hooks/                       # @cmclink/hooks — 通用 Composables
│   ├── micro-bridge/                # @cmclink/micro-bridge — 微前端通信桥
│   ├── micro-bootstrap/             # @cmclink/micro-bootstrap — 子应用启动器
│   ├── tsconfig/                    # @cmclink/tsconfig — TS 配置基线
│   ├── types/                       # @cmclink/types — 类型定义
│   ├── ui/                          # @cmclink/ui — UI 组件库
│   ├── utils/                       # @cmclink/utils — 工具函数
│   └── vite-config/                 # @cmclink/vite-config — Vite 配置工厂
├── turbo.json                       # Turborepo 任务编排
├── pnpm-workspace.yaml              # pnpm 工作区
└── package.json

1.2 微前端应用拓扑

                    ┌─────────────────────────────────────────┐
                    │              Nginx 网关                   │
                    │         (反向代理 + 静态资源)              │
                    └──────┬──────┬──────┬──────┬──────┬───────┘
                           │      │      │      │      │
                    ┌──────▼──┐ ┌─▼────┐ ┌▼─────┐ ┌───▼───┐ ┌──▼──┐
                    │ /micro- │ │/doc/ │ │/mkt/ │ │/ibs-  │ │/api │
                    │ main/   │ │      │ │      │ │manage/│ │     │
                    │ 主应用   │ │单证   │ │营销   │ │IBS    │ │后端  │
                    │ (Wujie  │ │子应用 │ │子应用 │ │管理   │ │服务  │
                    │  Host)  │ │      │ │      │ │子应用  │ │     │
                    └─────────┘ └──────┘ └──────┘ └───────┘ └─────┘

1.3 子应用注册表

子应用name路由前缀开发端口构建产物目录状态
主应用main/micro-main/3000micro-main/✅ 已迁入
单证doc/doc/3003doc/✅ 已迁入
IBS 管理ibs-manage/ibs-manage/3007ibs-manage/✅ 已迁入
营销mkt/mkt/3001mkt/🔜 待迁入
商务财务commerce-finance/commerce-finance/3002commerce-finance/🔜 待迁入
操作operation/operation/3004operation/🔜 待迁入
通用general/general/3005general/🔜 待迁入
公共common/common/3006common/🔜 待迁入

1.4 技术栈

层面技术选型
微前端框架Wujie(腾讯开源,iframe + WebComponent 沙箱)
前端框架Vue 3.5+ (Composition API + <script setup>)
构建工具Vite 7.0+
语言TypeScript 5.8+
Monorepopnpm 10 + Turborepo 2.5
UI 组件库Element Plus 2.9+
CSSTailwind CSS 4.1+
状态管理Pinia 3.0+

二、构建产物设计

2.1 当前问题分析

主应用构建存在双产物目录问题:

apps/main/
├── dist/                    # Visualizer 插件输出 stats.html
└── micro-main/              # 实际构建产物(VITE_BASE_NAME=micro-main)
    ├── index.html
    ├── css/
    ├── js/
    ├── img/
    └── favicon.svg

根因: build.outDirVITE_BASE_NAME 控制(值为 micro-main),而 Visualizer 插件 filename 默认输出到 dist/stats.html,导致同时存在两个目录。

建议修复: 将 Visualizer 输出路径改为 micro-main/stats.html,或在 CI 中统一收集时忽略 dist/

2.2 产物目录规范

核心原则: 构建产物目录名 = Vite base 路径 = Nginx location 前缀 = 子应用 entry,四者必须一致。

VITE_BASE_PATH=/micro-main/     →  Vite base: '/micro-main/'
VITE_BASE_NAME=micro-main       →  Vite outDir: 'micro-main'
Nginx: location /micro-main/    →  alias .../micro-main/
Registry: entry: '/micro-main/' →  Wujie startApp url

2.3 CI 统一收集后的部署产物

deploy-artifacts/                # CI 归档目录
├── micro-main/                  # 主应用
│   ├── index.html
│   ├── css/
│   ├── js/
│   └── img/
├── doc/                         # 单证子应用
├── ibs-manage/                  # IBS 管理子应用
├── mkt/                         # 营销子应用(待迁入)
└── build-info.json              # 构建元信息

2.4 构建命令矩阵

环境命令环境文件NODE_ENVsourcemapgzip
DEVpnpm dev.env.devdevelopment
SITpnpm build:sit.env.sitdevelopment
UATpnpm build:uat.env.uatproduction
PRODpnpm build:prod.env.prodproduction

Turborepo 全量/增量构建:

# 全量构建
pnpm build:prod

# 仅构建受 Git 变更影响的包(增量)
pnpm build:affected

三、代码分层与 Hook 复用规范

微前端架构下,代码复用需要严格分层,避免子应用间耦合。

3.1 三层 Hook 体系

┌─────────────────────────────────────────────────────────────┐
│  L1  @vueuse/core                                           │
│      社区通用 Composables(优先使用,不重复造轮子)              │
│      useMouse, useStorage, useDark, useClipboard ...        │
├─────────────────────────────────────────────────────────────┤
│  L2  @cmclink/hooks                                         │
│      公司级通用 Composables(跨子应用共享的业务无关逻辑)        │
│      useTable, useForm, useCache, usePermission,            │
│      useValidator, useExport, useWatermark                  │
├─────────────────────────────────────────────────────────────┤
│  L3  apps/<app>/src/hooks/                                  │
│      子应用自定义 Hook(强关联当前子应用业务的定制逻辑)          │
│      主应用: useMessage, useDesign, useBpmApproval ...      │
│      子应用: useDocForm, useShipmentTrack ...                │
└─────────────────────────────────────────────────────────────┘

3.2 选型决策流程

需要一个 Composable
    │
    ├─ @vueuse/core 有现成的? ──── 是 ──→ 直接用 @vueuse/core
    │                                       import { useXxx } from '@vueuse/core'
    │
    ├─ 多个子应用都需要? ────────── 是 ──→ 放入 @cmclink/hooks
    │   且与具体业务无关                     import { useXxx } from '@cmclink/hooks'
    │
    └─ 仅当前子应用需要? ────────── 是 ──→ 放入 apps/<app>/src/hooks/
        或强关联当前业务                     import { useXxx } from '@/hooks/xxx'

3.3 各层职责与示例

L1 — @vueuse/core(社区层)

// ✅ 直接使用 VueUse,不要自己封装
import { useStorage, useDark, useClipboard, useEventListener } from '@vueuse/core'

判断标准: VueUse 官方文档中已有的功能,直接使用,不二次封装。

L2 — @cmclink/hooks(公司基础层)

// packages/hooks/src/ 下的 Composables
import { useTable, useForm, useCache, usePermission } from '@cmclink/hooks'

当前已有:

Hook职责
useTable表格数据管理(分页、排序、筛选)
useForm表单状态管理(校验、提交、重置)
useCache缓存管理(localStorage/sessionStorage 封装)
useValidator通用校验规则(手机号、邮箱、身份证等)
usePermission权限判断(按钮级权限控制)
useExport数据导出(Excel 下载)
useWatermark页面水印

准入标准: 至少 2 个子应用需要使用,且不依赖特定子应用的业务逻辑。

L3 — 子应用自定义 Hook(应用层)

apps/main/src/hooks/
├── business/                    # 业务 Hook
│   ├── useBpmApproval.ts        # BPM 审批流程(主应用特有)
│   └── useI18nLabels.ts         # 国际化标签(主应用特有)
├── validation/                  # 校验 Hook
│   ├── useRules.ts              # 表单校验规则
│   └── useValidation.ts         # 校验逻辑
└── web/                         # 通用 Web Hook
    ├── useCache.ts              # 缓存(主应用定制版)
    ├── useCountdown.ts          # 倒计时
    ├── useDesign.ts             # 设计系统
    ├── useForm.ts               # 表单(主应用定制版)
    ├── useI18n.ts               # 国际化
    ├── useLocale.ts             # 语言切换
    ├── useMessage.ts            # 消息提示
    ├── useTitle.ts              # 页面标题
    └── useValidator.ts          # 校验器(主应用定制版)

注意: 主应用 hooks/web/useCache.ts@cmclink/hooks/useCache 存在同名,应逐步将通用部分下沉到 L2,应用层仅保留定制扩展。

3.4 packages 分层全景

┌─────────────────────────────────────────────────────────────┐
│                        apps/ (应用层)                        │
│  main / doc / ibs-manage / mkt / ...                        │
│  各子应用独立的业务代码、路由、Store、自定义 Hook              │
├─────────────────────────────────────────────────────────────┤
│                     packages/ (基础设施层)                    │
│                                                             │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐       │
│  │  hooks   │ │   api    │ │   biz    │ │    ui    │       │
│  │ 通用组合  │ │ 统一API  │ │ 业务组件  │ │ UI组件库 │       │
│  │ 式函数    │ │ 层       │ │          │ │          │       │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘       │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐       │
│  │  utils   │ │  types   │ │  micro-  │ │  micro-  │       │
│  │ 工具函数  │ │ 类型定义  │ │  bridge  │ │bootstrap │       │
│  │          │ │          │ │ 微前端桥  │ │ 子应用    │       │
│  │          │ │          │ │          │ │ 启动器    │       │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘       │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                    │
│  │ vite-    │ │ eslint-  │ │ tsconfig │                    │
│  │ config   │ │ config   │ │ 基线     │                    │
│  │ 构建工厂  │ │ 规范     │ │          │                    │
│  └──────────┘ └──────────┘ └──────────┘                    │
└─────────────────────────────────────────────────────────────┘

四、环境体系规划

4.1 四环境架构

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│   DEV    │───▶│   SIT    │───▶│   UAT    │───▶│   PROD   │
│  开发环境  │    │  测试环境  │    │  验收环境  │    │  生产环境  │
│ 本地联调   │    │ 集成测试   │    │ 用户验收   │    │ 正式上线   │
│ 热更新     │    │ 自动部署   │    │ 手动审批   │    │ 蓝绿/灰度  │
└──────────┘    └──────────┘    └──────────┘    └──────────┘

4.2 环境配置对照表

配置项DEVSITUATPROD
域名localhost:3000cmclink-sit:33080cmclink-uatcmclink
NODE_ENVdevelopmentdevelopmentproductionproduction
sourcemap
console.log保留保留移除移除
debugger保留保留移除移除
gzip 压缩

4.3 环境变量管理

.env.{mode}          → 提交到 Git(非敏感配置)
.env.{mode}.local    → 不提交(敏感配置、个人覆盖)

敏感信息: API 密钥、证书等通过 Jenkins Credentials 或 K8s Secrets 注入,禁止硬编码。


五、Nginx 网关层设计

5.1 架构角色

Nginx 在微前端架构中承担核心网关

  1. 静态资源服务 — 托管所有应用构建产物
  2. 反向代理 — API 请求转发到后端
  3. 路由分发 — URL 前缀路由到对应子应用
  4. SPA 回退 — 每个子应用非静态请求回退到各自 index.html
  5. 安全防护 — CORS、CSP、HSTS 等安全头
  6. 性能优化 — gzip/brotli、缓存策略、HTTP/2

5.2 核心配置

# /etc/nginx/conf.d/cmclink.conf

upstream backend_api {
    least_conn;
    server backend-1:8080 weight=5;
    server backend-2:8080 weight=5;
}

server {
    listen       80;
    listen       443 ssl http2;
    server_name  cmclink.sinolines.com.cn;

    # ─── SSL ───
    ssl_certificate      /etc/nginx/ssl/cmclink.crt;
    ssl_certificate_key  /etc/nginx/ssl/cmclink.key;
    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_ciphers          HIGH:!aNULL:!MD5;

    if ($scheme = http) {
        return 301 https://$host$request_uri;
    }

    # ─── 安全头 ───
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # ─── gzip ───
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/javascript
               application/javascript application/json
               application/xml image/svg+xml;

    root /usr/share/nginx/html;

    # ─── 根路径 → 主应用 ───
    location = / {
        return 302 /micro-main/;
    }

    # ─── 主应用 ───
    location /micro-main/ {
        alias /usr/share/nginx/html/micro-main/;
        try_files $uri $uri/ /micro-main/index.html;

        # 带 hash 的静态资源:强缓存 1 年
        location ~* /micro-main/(?:js|css|img|fonts)/.*\.[a-f0-9]{8}\. {
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }
        # index.html:不缓存
        location = /micro-main/index.html {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
    }

    # ─── 子应用:单证 ───
    location /doc/ {
        alias /usr/share/nginx/html/doc/;
        try_files $uri $uri/ /doc/index.html;
        location ~* /doc/(?:js|css|img|fonts)/.*\.[a-f0-9]{8}\. {
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }
        location = /doc/index.html {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
    }

    # ─── 子应用:IBS 管理 ───
    location /ibs-manage/ {
        alias /usr/share/nginx/html/ibs-manage/;
        try_files $uri $uri/ /ibs-manage/index.html;
        location ~* /ibs-manage/(?:js|css|img|fonts)/.*\.[a-f0-9]{8}\. {
            expires 1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }
        location = /ibs-manage/index.html {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
    }

    # ─── 新增子应用模板(复制此块) ───
    # location /<app-name>/ {
    #     alias /usr/share/nginx/html/<app-name>/;
    #     try_files $uri $uri/ /<app-name>/index.html;
    # }

    # ─── API 反向代理 ───
    location /admin-api/ {
        proxy_pass http://backend_api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_connect_timeout 60s;
        proxy_send_timeout 120s;
        proxy_read_timeout 120s;
    }

    # ─── 健康检查 ───
    location /health {
        access_log off;
        return 200 '{"status":"ok"}';
        add_header Content-Type application/json;
    }

    # ─── 禁止访问隐藏文件 ───
    location ~ /\. {
        deny all;
    }
}

5.3 缓存策略

资源类型Cache-Control说明
index.htmlno-cache, no-store, must-revalidate每次获取最新版本
js/[name]-[hash].jspublic, immutable, max-age=31536000强缓存 1 年
css/[name]-[hash].csspublic, immutable, max-age=31536000强缓存 1 年
img/[name]-[hash].extpublic, immutable, max-age=31536000强缓存 1 年
API 响应no-store动态数据不缓存

六、Jenkins CI/CD 流水线

6.1 流水线架构

Git Push / MR
    │
    ▼
┌──────────────────────────────────────────────────────┐
│                    Jenkins Pipeline                    │
├──────────┬──────────┬──────────┬──────────┬──────────┤
│ Prepare  │ Install  │ Quality  │  Build   │  Deploy  │
│ 环境准备  │ 依赖安装  │ 质量门禁  │ 构建打包  │ 部署上线  │
│          │          │          │          │          │
│ pnpm     │ frozen   │ type-    │ turbo    │ rsync /  │
│ corepack │ lockfile │ check    │ build    │ docker / │
│          │          │ + lint   │          │ k8s      │
└──────────┴──────────┴──────────┴──────────┴──────────┘

6.2 Jenkinsfile

pipeline {
    agent {
        docker {
            image 'node:22-alpine'
            args '-v pnpm-store:/root/.local/share/pnpm/store'
        }
    }

    environment {
        BUILD_ENV = "${params.BUILD_ENV ?: 'sit'}"
        DOCKER_REGISTRY = 'harbor.sinolines.com.cn'
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/cmclink/web-frontend"
    }

    parameters {
        choice(name: 'BUILD_ENV', choices: ['sit', 'uat', 'prod'], description: '目标环境')
        booleanParam(name: 'SKIP_LINT', defaultValue: false, description: '跳过 Lint(仅紧急修复)')
        booleanParam(name: 'FULL_BUILD', defaultValue: false, description: '强制全量构建')
    }

    options {
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '20'))
        timestamps()
    }

    stages {
        stage('Prepare') {
            steps {
                sh 'corepack enable && corepack prepare pnpm@10.15.0 --activate'
                sh 'node -v && pnpm -v'
            }
        }

        stage('Install') {
            steps {
                sh 'pnpm install --frozen-lockfile'
            }
        }

        stage('Quality Gate') {
            when { expression { !params.SKIP_LINT } }
            parallel {
                stage('Type Check') { steps { sh 'pnpm type-check' } }
                stage('Lint')       { steps { sh 'pnpm lint' } }
            }
        }

        stage('Build') {
            steps {
                script {
                    def cmd = params.FULL_BUILD
                        ? "pnpm build:${BUILD_ENV}"
                        : "pnpm build:affected"
                    sh cmd
                }
            }
        }

        stage('Archive') {
            steps {
                sh '''
                    mkdir -p deploy-artifacts
                    [ -d apps/main/micro-main ] && cp -r apps/main/micro-main deploy-artifacts/
                    # 子应用产物(按需添加)
                    # [ -d apps/doc/doc ] && cp -r apps/doc/doc deploy-artifacts/
                    # [ -d apps/ibs-manage/ibs-manage ] && cp -r apps/ibs-manage/ibs-manage deploy-artifacts/

                    cat > deploy-artifacts/build-info.json << EOF
{
  "version": "${BUILD_NUMBER}",
  "env": "${BUILD_ENV}",
  "branch": "${GIT_BRANCH}",
  "commit": "${GIT_COMMIT}",
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
                '''
                archiveArtifacts artifacts: 'deploy-artifacts/**', fingerprint: true
            }
        }

        stage('Docker Build') {
            when { expression { BUILD_ENV in ['uat', 'prod'] } }
            steps {
                script {
                    def tag = "${BUILD_ENV}-${BUILD_NUMBER}"
                    sh """
                        docker build \
                            --build-arg BUILD_ENV=${BUILD_ENV} \
                            -t ${DOCKER_IMAGE}:${tag} \
                            -t ${DOCKER_IMAGE}:${BUILD_ENV}-latest \
                            -f deploy/Dockerfile .
                        docker push ${DOCKER_IMAGE}:${tag}
                        docker push ${DOCKER_IMAGE}:${BUILD_ENV}-latest
                    """
                }
            }
        }

        stage('Deploy') {
            steps {
                script {
                    switch (BUILD_ENV) {
                        case 'sit':
                            // SIT:rsync 直接部署
                            sh """
                                rsync -avz --delete deploy-artifacts/ \
                                    ${DEPLOY_SERVER}:/data/nginx/html/
                                ssh ${DEPLOY_SERVER} 'nginx -t && nginx -s reload'
                            """
                            break
                        case 'uat':
                            input message: "确认部署到 UAT?", ok: "部署"
                            deployDocker('uat')
                            break
                        case 'prod':
                            input message: "确认部署到生产?(需技术负责人审批)", ok: "部署"
                            deployBlueGreen()
                            break
                    }
                }
            }
        }

        stage('Health Check') {
            steps {
                retry(3) {
                    sleep 10
                    sh "curl -sf ${getTargetUrl(BUILD_ENV)}/health || exit 1"
                }
            }
        }
    }

    post {
        success { sendNotification('✅ 部署成功') }
        failure { sendNotification('❌ 部署失败') }
        always  { cleanWs() }
    }
}

// ─── 辅助函数 ───

def deployDocker(env) {
    def tag = "${env}-${BUILD_NUMBER}"
    sh """
        ssh ${DEPLOY_SERVER} '
            docker pull ${DOCKER_IMAGE}:${tag}
            docker stop cmclink-web-${env} || true
            docker rm cmclink-web-${env} || true
            docker run -d --name cmclink-web-${env} \
                --restart unless-stopped \
                -p 80:80 -p 443:443 \
                -v /data/ssl:/etc/nginx/ssl:ro \
                ${DOCKER_IMAGE}:${tag}
        '
    """
}

def deployBlueGreen() {
    def tag = "prod-${BUILD_NUMBER}"
    sh """
        ssh ${DEPLOY_SERVER} '
            ACTIVE=\$(docker ps --filter name=cmclink-web-prod \
                --format "{{.Names}}" | grep -o "blue\\|green" || echo "none")
            TARGET=\$([ "\$ACTIVE" = "blue" ] && echo "green" || echo "blue")
            PORT=\$([ "\$TARGET" = "blue" ] && echo "8081" || echo "8082")

            docker pull ${DOCKER_IMAGE}:${tag}
            docker stop cmclink-web-prod-\$TARGET || true
            docker rm cmclink-web-prod-\$TARGET || true
            docker run -d --name cmclink-web-prod-\$TARGET \
                --restart unless-stopped -p \$PORT:80 \
                ${DOCKER_IMAGE}:${tag}

            sleep 10
            curl -sf http://localhost:\$PORT/health || exit 1

            # 切换 upstream
            sed -i "s/server 127.0.0.1:.*/server 127.0.0.1:\$PORT;/" \
                /etc/nginx/conf.d/upstream.conf
            nginx -t && nginx -s reload
        '
    """
}

def getTargetUrl(env) {
    [sit: 'https://cmclink-sit.sinolines.com.cn:33080',
     uat: 'https://cmclink-uat.sinolines.com.cn',
     prod: 'https://cmclink.sinolines.com.cn'][env]
}

def sendNotification(msg) {
    // 企业微信 Webhook
    sh """
        curl -s -X POST "\${WECHAT_WEBHOOK}" \
            -H 'Content-Type: application/json' \
            -d '{"msgtype":"markdown","markdown":{"content":"${msg} #${BUILD_NUMBER}\\n> 环境: ${BUILD_ENV}\\n> 分支: ${GIT_BRANCH}"}}'
    """
}

6.3 增量构建

Turborepo --affected 自动分析依赖图:

packages/utils 变更  → 所有依赖它的 apps 重新构建
packages/hooks 变更  → 所有依赖它的 apps 重新构建
apps/main/src 变更   → 仅 main 重新构建
apps/doc/src 变更    → 仅 doc 重新构建

七、Docker 容器化方案

7.1 多阶段构建 Dockerfile

# deploy/Dockerfile

# ─── 阶段 1:构建 ───
FROM node:22-alpine AS builder
RUN corepack enable && corepack prepare pnpm@10.15.0 --activate
WORKDIR /app

# 先复制依赖描述(利用 Docker 层缓存)
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY apps/main/package.json apps/main/
COPY packages/api/package.json packages/api/
COPY packages/biz/package.json packages/biz/
COPY packages/eslint-config/package.json packages/eslint-config/
COPY packages/hooks/package.json packages/hooks/
COPY packages/micro-bridge/package.json packages/micro-bridge/
COPY packages/micro-bootstrap/package.json packages/micro-bootstrap/
COPY packages/tsconfig/package.json packages/tsconfig/
COPY packages/types/package.json packages/types/
COPY packages/ui/package.json packages/ui/
COPY packages/utils/package.json packages/utils/
COPY packages/vite-config/package.json packages/vite-config/

RUN pnpm install --frozen-lockfile

COPY . .

ARG BUILD_ENV=prod
RUN pnpm build:${BUILD_ENV}

# ─── 阶段 2:运行 ───
FROM nginx:1.27-alpine AS runtime
RUN apk add --no-cache curl

COPY deploy/nginx/nginx.conf /etc/nginx/nginx.conf
COPY deploy/nginx/conf.d/ /etc/nginx/conf.d/

# 复制构建产物
COPY --from=builder /app/apps/main/micro-main /usr/share/nginx/html/micro-main
# COPY --from=builder /app/apps/doc/doc /usr/share/nginx/html/doc
# COPY --from=builder /app/apps/ibs-manage/ibs-manage /usr/share/nginx/html/ibs-manage

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -sf http://localhost/health || exit 1

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

7.2 Docker Compose(SIT 环境)

# deploy/docker-compose.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: deploy/Dockerfile
      args:
        BUILD_ENV: ${BUILD_ENV:-sit}
    container_name: cmclink-web
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./deploy/nginx/conf.d:/etc/nginx/conf.d:ro
      - ./deploy/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 256M

  # 日志收集(可选)
  filebeat:
    image: elastic/filebeat:8.12.0
    volumes:
      - nginx-logs:/var/log/nginx:ro
    depends_on:
      - web

volumes:
  nginx-logs:

7.3 镜像优化

优化项措施效果
多阶段构建builder → runtime~1.5GB → ~30MB
Alpine 基础镜像nginx:1.27-alpine基础镜像 ~7MB
层缓存先 COPY package.json依赖不变时跳过安装
.dockerignore排除 node_modules/.git减少构建上下文
# deploy/.dockerignore
node_modules
.git
*.md
.env*.local
.turbo

八、Kubernetes 编排方案(进阶)

8.1 Deployment

# deploy/k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cmclink-web
  namespace: cmclink
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: cmclink-web
  template:
    metadata:
      labels:
        app: cmclink-web
    spec:
      containers:
        - name: web
          image: harbor.sinolines.com.cn/cmclink/web-frontend:prod-latest
          ports:
            - containerPort: 80
          resources:
            requests: { cpu: 100m, memory: 64Mi }
            limits:   { cpu: 500m, memory: 256Mi }
          livenessProbe:
            httpGet: { path: /health, port: 80 }
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet: { path: /health, port: 80 }
            initialDelaySeconds: 5
            periodSeconds: 10
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - { key: app, operator: In, values: [cmclink-web] }
                topologyKey: kubernetes.io/hostname

8.2 Service + Ingress

apiVersion: v1
kind: Service
metadata:
  name: cmclink-web-svc
  namespace: cmclink
spec:
  selector: { app: cmclink-web }
  ports: [{ port: 80, targetPort: 80 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cmclink-web-ingress
  namespace: cmclink
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts: [cmclink.sinolines.com.cn]
      secretName: cmclink-tls
  rules:
    - host: cmclink.sinolines.com.cn
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: cmclink-web-svc, port: { number: 80 } }

8.3 HPA 自动扩缩

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: cmclink-web-hpa
  namespace: cmclink
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: cmclink-web
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

九、容灾与高可用

9.1 容灾架构

                    ┌─────────────────────────┐
                    │       DNS (智能解析)       │
                    └─────┬───────────┬────────┘
                          │           │
                 ┌────────▼──┐  ┌────▼────────┐
                 │  主机房     │  │  灾备机房     │
                 │  (Active)  │  │  (Standby)  │
                 │  Web x 3   │  │  Web x 2    │
                 └────────────┘  └─────────────┘

9.2 多层容灾策略

层级策略RTO
应用层多副本 (replicas ≥ 3)秒级
节点层Pod 反亲和(分散到不同节点)秒级
机房层双机房热备 + DNS 切换分钟级
区域层异地灾备 + OSS 备份小时级

9.3 静态资源备份

#!/bin/bash
# deploy/scripts/sync-to-oss.sh
# 构建产物同步到 OSS 作为灾备

OSS_BUCKET="cmclink-web-backup"
DEPLOY_DIR="/usr/share/nginx/html"
DATE=$(date +%Y%m%d_%H%M%S)

# 同步当前版本
ossutil sync ${DEPLOY_DIR}/ oss://${OSS_BUCKET}/current/ --delete
# 归档历史版本
ossutil cp oss://${OSS_BUCKET}/current/ oss://${OSS_BUCKET}/archive/${DATE}/ -r

十、灰度发布与回滚

10.1 发布策略对比

策略风险回滚速度资源开销适用场景
滚动更新分钟级日常发布
蓝绿部署秒级高(双倍)重大版本
灰度/金丝雀最低秒级高风险变更

10.2 Nginx 灰度配置

# 基于百分比的灰度
split_clients "${remote_addr}" $upstream_group {
    10%  canary;    # 10% 流量走灰度版
    *    stable;
}

upstream stable { server 127.0.0.1:8081; }
upstream canary { server 127.0.0.1:8082; }

server {
    location / {
        proxy_pass http://$upstream_group;
    }
}

10.3 回滚方案

# Docker 回滚
docker stop cmclink-web-prod && docker rm cmclink-web-prod
docker run -d --name cmclink-web-prod -p 80:80 \
    harbor.sinolines.com.cn/cmclink/web-frontend:prod-42  # 指定历史版本号

# K8s 回滚
kubectl rollout undo deployment/cmclink-web -n cmclink           # 上一版本
kubectl rollout undo deployment/cmclink-web --to-revision=3      # 指定版本

# 静态文件回滚(从 OSS 恢复)
ossutil sync oss://cmclink-web-backup/archive/20260210_143000/ \
    /usr/share/nginx/html/ -r
nginx -s reload

十一、监控与告警

11.1 监控体系

┌─────────────────────────────────────────────────────────┐
│                      Grafana 大盘                         │
├──────────┬──────────┬──────────┬──────────┬──────────────┤
│ 基础设施   │ 应用性能   │ 业务指标   │ 日志分析   │ 告警         │
│ CPU/内存  │ FCP/LCP  │ PV/UV    │ 错误日志   │ 企业微信     │
│ 容器状态   │ TTFB/CLS │ 子应用加载 │ 访问日志   │ 邮件/短信    │
└──────────┴──────────┴──────────┴──────────┴──────────────┘
  Prometheus    Sentry       ELK Stack    Alertmanager

11.2 关键告警规则

指标阈值级别
5xx 错误率> 5% 持续 5minCritical
P99 响应时间> 3s 持续 10minWarning
Pod 频繁重启> 0 次/15minCritical
磁盘空间< 10%Warning

十二、安全加固

12.1 安全防护矩阵

层面措施优先级
传输层HTTPS + HSTS + TLS 1.2+P0
应用层CSP + X-Frame-Options + X-Content-Type-OptionsP0
认证层JWT + HttpOnly Cookie + CSRF TokenP0
网络层WAF + DDoS 防护P1
构建层依赖审计 (pnpm audit) + 镜像扫描 (Trivy)P1
运维层最小权限 + 审计日志 + 密钥轮转P2

12.2 CSP 配置(微前端特殊处理)

add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'unsafe-inline' 'unsafe-eval';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: blob: https:;
    font-src 'self' data:;
    connect-src 'self' https://cmclink*.sinolines.com.cn;
    frame-src 'self';
" always;

⚠️ Wujie 使用 iframe 沙箱,frame-src 'self' 必须;子应用 JS 在闭包中执行需要 'unsafe-eval'


十三、性能优化

13.1 构建产物优化

优化项措施效果
代码分割manualChunks 拆分 vendor首屏 JS -60%
Tree ShakingESM + sideEffects移除未使用代码
gzip 压缩vite-plugin-compression (≥20KB)体积 -70%
CSS 优化Tailwind CSS purgeCSS -90%

13.2 运行时优化

优化项措施效果
子应用预加载分级 preloadApp(高频立即、低频延迟 3s)首次打开 < 1s
预执行 + 保活exec: true + alive: true瞬间打开
fiber 模式fiber: true(默认)不阻塞主应用渲染
路由懒加载() => import(...)按需加载页面

13.3 Nginx 性能调优

worker_processes auto;
worker_rlimit_nofile 65535;
worker_connections 4096;

sendfile on;
tcp_nopush on;
tcp_nodelay on;

open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;

十四、运维 SOP 手册

14.1 日常发布 SOP

1. MR → Code Review → 合并 develop
2. Jenkins 自动触发 SIT 构建部署
3. QA 在 SIT 验证
4. 合并 release → 触发 UAT 构建 → 人工审批
5. 合并 main → 触发 PROD 构建 → 蓝绿/灰度部署
6. 健康检查 → 监控观察 30 分钟
7. 清理旧版本

14.2 紧急回滚 SOP

1. 确认是前端问题
2. 通知技术负责人
3. 执行回滚(K8s rollout undo / Docker 切换镜像 / OSS 恢复)
4. 验证回滚成功
5. 事后复盘

14.3 子应用独立发布 SOP

微前端核心优势 — 子应用可独立发布,不影响其他应用

# 1. 仅构建该子应用
turbo run build:prod --filter=@cmclink/doc

# 2. 仅替换该子应用静态资源
rsync -avz apps/doc/doc/ server:/usr/share/nginx/html/doc/

# 3. 无需重启 Nginx(静态文件替换即生效)
# 4. 主应用和其他子应用完全不受影响

14.4 新增子应用 Checklist

  • apps/<name>/ 创建子应用目录
  • vite.config.ts 使用 @cmclink/vite-config 工厂
  • .env.* 配置 VITE_BASE_PATH / VITE_BASE_NAME
  • micro-bridge/registry.ts 注册子应用
  • Nginx 添加 location /<name>/
  • Dockerfile 添加 COPY --from=builder 指令
  • Jenkinsfile 添加产物收集
  • turbo.json outputs 添加产物目录
  • 测试:独立运行 + 微前端模式

附录 A:配置文件模板

A.1 完整部署目录结构

deploy/                              # 新增部署配置目录
├── Dockerfile
├── .dockerignore
├── docker-compose.yml
├── nginx/
│   ├── nginx.conf
│   └── conf.d/
│       └── cmclink.conf
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── hpa.yaml
└── scripts/
    ├── sync-to-oss.sh
    ├── health-check.sh
    └── rollback.sh

A.2 Nginx 容器主配置

# deploy/nginx/nginx.conf
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # JSON 日志格式(便于 ELK 采集)
    log_format json escape=json
        '{"time":"$time_iso8601",'
         '"addr":"$remote_addr",'
         '"req":"$request",'
         '"status":$status,'
         '"size":$body_bytes_sent,'
         '"rt":$request_time,'
         '"ua":"$http_user_agent"}';

    access_log /var/log/nginx/access.log json;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    client_max_body_size 50m;

    gzip on;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/javascript
               application/javascript application/json image/svg+xml;

    include /etc/nginx/conf.d/*.conf;
}

附录 B:故障排查 Checklist

B.1 页面白屏

  • Nginx 运行状态:nginx -t && curl localhost/health
  • index.html 存在:ls /usr/share/nginx/html/micro-main/index.html
  • JS/CSS 资源 404:DevTools → Network
  • base 路径一致:VITE_BASE_PATH = Nginx location = 子应用 entry

B.2 子应用加载失败

  • 子应用资源可访问:curl https://domain/doc/index.html
  • CORS 头正确:Access-Control-Allow-Origin: *
  • 注册表 entry 与部署路径一致
  • 浏览器控制台沙箱错误

B.3 部署后缓存未更新

  • index.html 的 Cache-Controlno-cache
  • CDN 已刷新缓存
  • 无 Service Worker 缓存
  • 强制刷新:Ctrl + Shift + R

B.4 容器启动失败

  • 容器日志:docker logs cmclink-web
  • Nginx 配置:docker exec cmclink-web nginx -t
  • 端口占用:netstat -tlnp | grep 80
  • 磁盘空间:df -h

文档维护: 本方案书应随架构演进持续更新。每次重大变更(新增子应用、变更部署方式、升级基础设施)后,请同步更新对应章节。