前端Docker多平台构建自动化实践

本文介绍如何使用 Node.js + pnpm + Docker Buildx 构建一个功能完善的多平台镜像构建工具,实现自动版本管理、镜像仓库集成等企业级功能。

背景与挑战

业务背景

在现代前端工程化实践中,容器化部署已成为标准配置。然而,随着 Apple Silicon(ARM64 架构)的普及和云原生技术的发展,我们面临着新的挑战:

  1. 多架构支持:需要同时支持 x86_64 (amd64) 和 ARM64 架构
  2. 版本管理混乱:手动管理版本号容易出错,与镜像仓库不同步
  3. 构建流程复杂:前端构建 → Docker 构建 → 推送仓库,步骤繁琐
  4. CI/CD 集成困难:不同环境配置不一致,难以标准化

痛点分析

传统的 Docker 构建流程存在以下问题:

bash 复制代码
# 传统流程 - 步骤繁琐,容易出错
pnpm install
pnpm run build:pro
docker build -t my-app:3.14.0 .
docker tag my-app:3.14.0 registry.example.com/my-app:3.14.0
docker push registry.example.com/my-app:3.14.0
docker tag my-app:3.14.0 registry.example.com/my-app:latest
docker push registry.example.com/my-app:latest

# 多平台构建更加复杂
docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/my-app:3.14.0 \
  --push .

主要痛点:

  • ❌ 版本号需要手动维护,容易遗忘更新
  • ❌ 多平台构建命令冗长,参数复杂
  • ❌ 需要手动登录 Docker 仓库
  • ❌ 构建失败时难以定位问题
  • ❌ 无法自动检测远程版本,可能覆盖已有镜像

技术选型

核心技术栈

技术 版本 用途
Node.js 20.19.0+ 运行时环境
pnpm 10.23.0+ 包管理器
Docker 19.03+ 容器引擎
Docker Buildx Latest 多平台构建
consola 3.4.2 日志输出

为什么选择 Node.js?

  1. 与前端项目无缝集成 :可以直接读取 package.json,调用 pnpm 命令
  2. 跨平台支持:在 macOS、Linux、Windows 上都能运行
  3. 生态丰富:有大量成熟的工具库
  4. 团队熟悉:前端团队无需学习新语言

为什么选择 Docker Buildx?

Docker Buildx 是 Docker 官方提供的多平台构建工具,具有以下优势:

  • ✅ 原生支持多架构构建
  • ✅ 自动处理交叉编译
  • ✅ 支持构建缓存,提升速度
  • ✅ 可以直接推送到镜像仓库

系统架构设计

整体架构

markdown 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        用户交互层                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ 命令行参数    │  │  环境变量     │  │  配置文件     │          │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘          │
│         └─────────────────┼─────────────────┘                   │
└───────────────────────────┼─────────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                      主控制器 (DockerBuilder)                    │
│  - 流程编排                                                      │
│  - 错误处理                                                      │
│  - 日志输出                                                      │
└───────────────────────────┬─────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│  配置管理      │  │  版本管理      │  │  仓库管理      │
│ BuildConfig   │  │VersionManager │  │DockerRegistry │
└───────────────┘  └───────────────┘  └───────────────┘

模块职责

1. 主控制器 (DockerBuilder)

负责整个构建流程的编排和控制:

javascript 复制代码
class DockerBuilder {
  async build() {
    // 1. 环境检查
    if (!this.checkDocker()) return false
    if (!this.checkNodeEnv()) return false
    
    // 2. 前端构建
    if (!await this.buildFrontend()) return false
    
    // 3. Docker 登录
    if (!await this.dockerLogin()) return false
    
    // 4. 版本管理
    const version = await this.getVersion()
    
    // 5. 镜像构建
    await this.buildMultiPlatform(version)
    
    return true
  }
}

2. 配置管理 (BuildConfig)

实现多源配置合并,优先级清晰:

javascript 复制代码
class BuildConfig {
  constructor(options = {}) {
    // 配置优先级: 命令行参数 > 环境变量 > 配置文件 > 默认值
    this.registry = options.registry
      || process.env.DOCKER_REGISTRY
      || fileConfig.registry
      || 'registry.cn-shanghai.aliyuncs.com'
  }
}

3. 版本管理 (VersionManager)

实现智能版本号管理:

javascript 复制代码
class VersionManager {
  incrementVersion(currentVersion, remoteVersion) {
    // 比较当前版本和远程版本,取较大值
    const baseVersion = this.compareVersion(currentVersion, remoteVersion) >= 0
      ? currentVersion
      : remoteVersion
    
    // 自动递增 patch 版本
    const ver = this.parseVersion(baseVersion)
    ver.patch += 1
    
    return `${ver.major}.${ver.minor}.${ver.patch}`
  }
}

4. 仓库管理 (DockerRegistry)

处理 Docker 仓库的登录和查询:

javascript 复制代码
class DockerRegistry {
  async login() {
    // 使用 --password-stdin 保证安全
    const command = `echo "${this.config.password}" | docker login -u "${this.config.username}" --password-stdin "${this.config.registry}"`
    this.exec(command, { silent: true })
  }
  
  async getLatestVersion() {
    // 查询镜像仓库最新版本
    const versions = await this.getVersionsFromManifest(imageName)
    return this.findLatestVersion(versions)
  }
}

工作流程

go 复制代码
开始
  │
  ▼
解析命令行参数 ─────► 加载配置文件 ─────► 合并环境变量
  │
  ▼
检查 Docker 环境 ────────────────┐
  │                              │ 失败
  ▼                              ▼
检查 Node.js 环境 ──────────────► 输出错误 ──► 退出
  │                              ▲
  ▼                              │
构建前端项目 (pnpm)  ─────────────┤
  │                              │
  ▼                              │
Docker 登录 ──────────────────────┤
  │                              │
  ▼                              │
获取版本号                         │
  ├─ 读取 package.json           │
  ├─ 查询远程版本                 │
  ├─ 比较版本                     │
  ├─ 自动递增                     │
  └─ 更新 package.json           │
  │                              │
  ▼                              │
构建多平台镜像 ────────────────────┤
  │                              │
  ▼                              │
推送到仓库 ────────────────────────┤
  │                              │
  ▼                              │
显示结果                           │
  │                              │
  ▼                              │
成功 ◄─────────────────────────────┘

核心功能实现

1. 多平台构建

使用 Docker Buildx 实现多平台构建:

javascript 复制代码
async buildMultiPlatform(version) {
  const fullImageName = `${this.config.registry}/${this.config.namespace}/${this.config.imageName}:${version}`
  
  const args = [
    'buildx',
    'build',
    '--platform',
    this.config.platforms,  // linux/amd64,linux/arm64
    '--tag',
    fullImageName,
  ]
  
  // 添加 latest 标签
  if (this.config.pushImage) {
    args.push('--tag', `${this.config.registry}/${this.config.namespace}/${this.config.imageName}:latest`)
    args.push('--push')
  }
  
  args.push('--file', 'Dockerfile')
  args.push('.')
  
  await this.execStream('docker', args)
}

关键点:

  • 使用 --platform 参数指定多个平台
  • 同时打上版本号和 latest 标签
  • 使用 --push 直接推送到仓库(多平台镜像无法加载到本地)

2. 自动版本管理

实现智能版本号管理的核心逻辑:

javascript 复制代码
async getVersion() {
  // 1. 获取 package.json 版本
  const packageVersion = this.versionManager.getPackageVersion()
  consola.info(`package.json 版本: ${packageVersion}`)
  
  // 2. 获取远程版本
  if (this.config.autoIncrement) {
    const remoteVersion = await this.dockerRegistry.getLatestVersion()
    if (remoteVersion) {
      consola.info(`镜像仓库最新版本: ${remoteVersion}`)
      
      // 3. 比较并自增版本
      const newVersion = this.versionManager.incrementVersion(
        packageVersion,
        remoteVersion
      )
      consola.success(`新版本号: ${newVersion}`)
      
      // 4. 更新 package.json
      if (this.config.updatePackageJson) {
        this.versionManager.updatePackageVersion(newVersion)
      }
      
      return newVersion
    }
  }
  
  // 5. 使用 package.json 版本
  return this.config.version || packageVersion
}

版本比较算法:

javascript 复制代码
compareVersion(v1, v2) {
  const ver1 = this.parseVersion(v1)  // { major: 3, minor: 14, patch: 0 }
  const ver2 = this.parseVersion(v2)  // { major: 3, minor: 14, patch: 5 }
  
  // 比较顺序: major > minor > patch
  if (ver1.major !== ver2.major) {
    return ver1.major > ver2.major ? 1 : -1
  }
  if (ver1.minor !== ver2.minor) {
    return ver1.minor > ver2.minor ? 1 : -1
  }
  if (ver1.patch !== ver2.patch) {
    return ver1.patch > ver2.patch ? 1 : -1
  }
  return 0
}

版本递增策略:

javascript 复制代码
incrementVersion(currentVersion, remoteVersion, type = 'patch') {
  // 取较大的版本作为基准
  const baseVersion = this.compareVersion(currentVersion, remoteVersion) >= 0
    ? currentVersion
    : remoteVersion
  
  const ver = this.parseVersion(baseVersion)
  
  switch (type) {
    case 'major':
      ver.major += 1
      ver.minor = 0
      ver.patch = 0
      break
    case 'minor':
      ver.minor += 1
      ver.patch = 0
      break
    case 'patch':
    default:
      ver.patch += 1
      break
  }
  
  return `${ver.major}.${ver.minor}.${ver.patch}`
}

3. 前端项目自动构建

集成前端构建流程:

javascript 复制代码
async buildFrontend() {
  if (this.config.skipBuild) {
    consola.info('跳过前端构建 (SKIP_BUILD=true)')
    return true
  }
  
  consola.start('开始构建前端项目...')
  
  try {
    // 1. 清理旧的构建产物
    const distPath = join(this.config.projectRoot, 'dist')
    if (existsSync(distPath)) {
      consola.info('清理旧的构建产物...')
      this.exec(`rm -rf ${distPath}`)
    }
    
    // 2. 安装依赖
    consola.info('安装依赖...')
    await this.execStream('pnpm', ['install'])
    
    // 3. 构建生产版本
    consola.info('构建生产版本...')
    await this.execStream('pnpm', ['run', 'build:pro'])
    
    // 4. 检查构建产物
    if (!existsSync(distPath)) {
      throw new Error('构建失败: dist 目录不存在')
    }
    
    consola.success('前端构建完成')
    return true
  }
  catch (error) {
    consola.error('前端构建失败:', error.message)
    return false
  }
}

4. Docker 仓库自动登录

安全地处理 Docker 登录:

javascript 复制代码
async login() {
  consola.start(`正在登录 Docker 仓库: ${this.config.registry}`)
  
  if (!this.config.username || !this.config.password) {
    consola.error('Docker 用户名或密码未配置')
    return false
  }
  
  try {
    // 使用 --password-stdin 避免密码出现在命令行历史中
    const command = `echo "${this.config.password}" | docker login -u "${this.config.username}" --password-stdin "${this.config.registry}"`
    this.exec(command, { silent: true })
    
    consola.success(`Docker 仓库登录成功: ${this.config.registry}`)
    return true
  }
  catch (error) {
    consola.error(`Docker 仓库登录失败: ${this.config.registry}`)
    return false
  }
}

安全要点:

  • ✅ 使用 --password-stdin 传递密码
  • ✅ 密码不出现在命令行历史
  • ✅ 支持从环境变量读取
  • ✅ 日志中不显示敏感信息

5. 配置系统设计

实现灵活的多源配置:

javascript 复制代码
class BuildConfig {
  constructor(options = {}) {
    // 加载配置文件
    const fileConfig = this.loadConfigFile()
    
    // 合并配置 (优先级: 命令行 > 环境变量 > 配置文件 > 默认值)
    this.registry = options.registry
      || process.env.DOCKER_REGISTRY
      || fileConfig.registry
      || 'registry.cn-shanghai.aliyuncs.com'
    
    this.namespace = options.namespace
      || process.env.DOCKER_NAMESPACE
      || fileConfig.namespace
      || 'zhangjian_sh'
    
    // 布尔值配置需要特殊处理
    this.pushImage = this.parseBoolean(
      options.pushImage,
      process.env.PUSH_IMAGE,
      fileConfig.pushImage,
      false  // 默认值
    )
  }
  
  // 解析布尔值
  parseBoolean(...values) {
    for (const value of values) {
      if (value === true || value === false) {
        return value
      }
      if (typeof value === 'string') {
        const lower = value.toLowerCase()
        if (lower === 'true' || lower === '1' || lower === 'yes') {
          return true
        }
        if (lower === 'false' || lower === '0' || lower === 'no') {
          return false
        }
      }
    }
    return false
  }
}

配置文件示例:

json 复制代码
{
  "registry": "rregistry.example.com",
  "namespace": "your-namespace",
  "imageName": "your-imageName",
  "version": null,
  "platforms": "linux/amd64,linux/arm64",
  "pushImage": false,
  "skipBuild": false,
  "autoIncrement": true,
  "updatePackageJson": true,
  "autoLogin": true
}

6. 日志系统

使用 consola 实现美观的日志输出:

javascript 复制代码
import consola from 'consola'

// 不同级别的日志
consola.start('检查 Docker 环境...')      // ⏳ 开始执行
consola.success('Docker 检查通过')        // ✓ 成功信息
consola.info('Node.js 版本: v20.19.0')   // ℹ 提示信息
consola.warn('无法获取远程版本')          // ⚠ 警告信息
consola.error('Docker 未运行')           // ✖ 错误信息

// 显示结果框
consola.box({
  title: '🎉 构建完成',
  message: [
    `镜像名称: ${imageInfo.fullImageName}`,
    `版本号: ${imageInfo.version}`,
    `支持平台: ${this.config.platforms}`,
    '',
    '✅ 镜像已推送到仓库'
  ].join('\n'),
  style: {
    borderColor: 'green',
    borderStyle: 'round'
  }
})

输出效果:

csharp 复制代码
[INFO] 🚀 开始 Docker 多平台构建...

[START] 检查 Docker 环境...
[SUCCESS] Docker 检查通过

[START] 获取版本信息...
[INFO] package.json 版本: 3.14.0
[INFO] 镜像仓库最新版本: 3.14.5
[SUCCESS] 新版本号: 3.14.6

╭─────────────────────────────────────────╮
│  🎉 构建完成                             │
│                                         │
│  镜像名称: registry.cn-shanghai...      │
│  版本号: 3.14.6                         │
│  支持平台: linux/amd64,linux/arm64      │
│                                         │
│  ✅ 镜像已推送到仓库                     │
╰─────────────────────────────────────────╯

[SUCCESS] ✨ 构建完成!耗时: 45.23s

最佳实践

1. 使用方式

基础用法

bash 复制代码
# 构建镜像(不推送)
node buildx/index.js

# 构建并推送
node buildx/index.js --push

# 跳过前端构建
node buildx/index.js --skip-build --push

集成到 package.json

json 复制代码
{
  "scripts": {
    "docker:build": "node buildx/index.js",
    "docker:build:push": "node buildx/index.js --push",
    "docker:build:skip": "node buildx/index.js --skip-build --push"
  }
}

然后使用:

bash 复制代码
pnpm run docker:build:push

2. 版本管理策略

策略 1: 完全自动(推荐)

bash 复制代码
# 自动查询远程版本,自动递增,自动更新 package.json
pnpm run docker:build:push

适用场景: 日常开发,小版本迭代

策略 2: 手动指定版本

bash 复制代码
# 发布大版本时手动指定
node buildx/index.js --version 4.0.0 --no-auto-increment --push

适用场景: 重大版本发布

策略 3: 禁用更新 package.json

bash 复制代码
# 自动递增但不更新 package.json
node buildx/index.js --no-update-package --push

适用场景: 测试环境,不想修改源码

3. CI/CD 集成

GitLab CI/CD

yaml 复制代码
# .gitlab-ci.yml
stages:
  - build

build-docker:
  stage: build
  image: node:20
  services:
    - docker:dind
  before_script:
    - npm install -g pnpm
    - pnpm install
  script:
    - export DOCKER_USERNAME=$CI_REGISTRY_USER
    - export DOCKER_PASSWORD=$CI_REGISTRY_PASSWORD
    - export PUSH_IMAGE=true
    - pnpm run docker:build:push
  only:
    - main
    - tags

GitHub Actions

yaml 复制代码
# .github/workflows/docker-build.yml
name: Docker Build and Push

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
      
      - name: Install pnpm
        run: npm install -g pnpm
      
      - name: Build and push
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          PUSH_IMAGE: true
        run: pnpm run docker:build:push

4. 安全建议

不要硬编码密码

javascript 复制代码
// ❌ 错误做法
const password = 'my-password'

// ✅ 正确做法
const password = process.env.DOCKER_PASSWORD

使用 .gitignore

bash 复制代码
# .gitignore
buildx/config.json
.env

使用环境变量

bash 复制代码
# 开发环境
export DOCKER_USERNAME="your-username"
export DOCKER_PASSWORD="your-password"

# 或使用 .env 文件
echo "DOCKER_USERNAME=your-username" >> .env
echo "DOCKER_PASSWORD=your-password" >> .env

5. 故障排查

问题 1: Docker Buildx 不可用

bash 复制代码
# 检查 Docker 版本
docker version

# 创建 builder
docker buildx create --name multi-platform-builder --use
docker buildx inspect --bootstrap

问题 2: 前端构建失败

bash 复制代码
# 清理依赖重新安装
rm -rf node_modules pnpm-lock.yaml
pnpm install

# 手动构建测试
pnpm run build:pro

问题 3: 无法推送镜像

bash 复制代码
# 手动登录测试
docker login registry.cn-shanghai.aliyuncs.com

# 检查镜像名称
docker images | grep your-image

性能优化

1. 构建缓存

Docker Buildx 自动利用层缓存:

dockerfile 复制代码
# Dockerfile 优化示例
FROM node:20-alpine AS builder

# 先复制依赖文件(变化少)
COPY package.json pnpm-lock.yaml ./
RUN pnpm install

# 再复制源码(变化多)
COPY . .
RUN pnpm run build:pro

# 生产镜像
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

优化效果:

  • 首次构建:~60 秒
  • 依赖未变化:~10 秒
  • 代码变化:~20 秒

2. 跳过前端构建

已构建时可跳过:

bash 复制代码
# 完整构建
pnpm run docker:build:push  # ~60 秒

# 跳过前端构建
pnpm run docker:build:skip  # ~20 秒

3. 单平台构建

开发环境只构建单平台:

bash 复制代码
# 仅构建 amd64(更快)
node buildx/index.js --platform linux/amd64 --push  # ~30 秒

# 生产环境构建多平台
node buildx/index.js --platform linux/amd64,linux/arm64 --push  # ~50 秒

4. 并行构建

Docker Buildx 自动并行构建多平台:

bash 复制代码
linux/amd64 ─┐
             ├──► 并行执行
linux/arm64 ─┘

性能对比:

  • 单平台:~30 秒
  • 双平台串行:~60 秒
  • 双平台并行:~40 秒(节省 33%)

项目结构

bash 复制代码
buildx/
├── index.js                    # 主入口文件
├── lib/
│   ├── config.js              # 配置管理
│   ├── version-manager.js     # 版本管理
│   └── docker-registry.js     # 仓库管理
├── config.example.json        # 配置文件示例
├── package.json               # 依赖配置
├── test.js                    # 测试脚本
├── README.md                  # 使用文档
├── QUICKSTART.md              # 快速开始
├── ARCHITECTURE.md            # 架构设计
├── FEATURES.md                # 功能特性
├── EXAMPLES.md                # 使用示例
└── CHANGELOG.md               # 更新日志

技术亮点

1. 模块化设计

采用面向对象设计,职责清晰:

scss 复制代码
DockerBuilder (主控制器)
  ├── BuildConfig (配置管理)
  ├── VersionManager (版本管理)
  └── DockerRegistry (仓库管理)

每个模块独立可测试,易于维护和扩展。

2. 智能版本管理

自动对比本地和远程版本,避免版本冲突:

makefile 复制代码
package.json: 3.14.0
远程仓库: 3.14.5
─────────────────
取较大值: 3.14.5
自动递增: 3.14.6

3. 多源配置

支持命令行、环境变量、配置文件,优先级清晰:

复制代码
命令行参数 > 环境变量 > 配置文件 > 默认值

4. 完善的错误处理

每个步骤都有错误检测和友好提示:

javascript 复制代码
try {
  await this.buildFrontend()
}
catch (error) {
  consola.error('前端构建失败:', error.message)
  consola.info('请检查: pnpm install && pnpm run build:pro')
  return false
}

5. 流式输出

使用 spawn 实现实时日志输出:

javascript 复制代码
async execStream(command, args = []) {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      stdio: 'inherit',  // 实时输出
      shell: true
    })
    
    child.on('close', (code) => {
      code === 0 ? resolve() : reject()
    })
  })
}

实际效果

构建时间对比

场景 传统方式 自动化工具 提升
首次构建 ~90 秒 ~60 秒 33% ↑
增量构建 ~60 秒 ~20 秒 67% ↑
跳过前端 ~30 秒 ~15 秒 50% ↑

操作步骤对比

操作 传统方式 自动化工具
命令数量 8+ 条 1 条
版本管理 手动 自动
登录仓库 手动 自动
错误处理 无 完善

团队效率提升

  • ✅ 减少 80% 的手动操作
  • ✅ 避免 100% 的版本冲突
  • ✅ 降低 90% 的构建错误
  • ✅ 提升 50% 的构建速度

总结与展望

核心价值

  1. 自动化:一条命令完成所有操作
  2. 智能化:自动版本管理,避免冲突
  3. 标准化:统一构建流程,降低门槛
  4. 可靠性:完善的错误处理和日志
  5. 高效率:利用缓存,提升构建速度

适用场景

  • ✅ 前端项目容器化部署
  • ✅ 多架构支持(x86 + ARM)
  • ✅ CI/CD 自动化构建
  • ✅ 多环境部署管理
  • ✅ 团队协作开发

未来展望

短期计划

  1. 支持更多镜像仓库

    • Harbor
    • Docker Hub
    • 腾讯云 TCR
    • AWS ECR
  2. 增强版本管理

    • 支持 alpha、beta、rc 版本
    • 支持 Git Tag 自动生成版本
    • 支持版本回滚
  3. 构建通知

    • 钉钉通知
    • 企业微信通知
    • 邮件通知

长期规划

  1. 插件系统

    javascript 复制代码
    builder.use(new NotificationPlugin())
    builder.use(new ReportPlugin())
    builder.use(new MetricsPlugin())
  2. Web 控制台

    • 可视化构建管理
    • 实时日志查看
    • 构建历史记录
  3. 分布式构建

    • 支持多节点并行构建
    • 构建任务队列
    • 资源调度优化

参考资料


💡 提示:本文介绍的工具已在生产环境稳定运行,处理了数千次构建任务。如果你也在寻找一个高效的 Docker 构建解决方案,不妨试试这个工具!

相关推荐
云-缩影1 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
Dream-Y.ocean1 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
考虑考虑2 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
Csvn2 小时前
性能监控
前端
Highcharts.js2 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
无奈何杨3 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
Csvn3 小时前
构建工具
前端
FRing033 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Patrick_Wilson4 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术4 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端