Jenkins自动化部署实战:Spring Boot+Vue项目CI/CD流水线搭建
1. 项目概述为什么我们需要自动化部署流水线如果你经历过手动部署的“痛苦三连”——本地打包、上传服务器、重启服务然后发现一个字母打错了再重复一遍——那你一定能理解自动化部署的价值。我最早接触 Jenkins 是在一个中型电商项目当时每次上线都像打仗开发、测试、运维围在一起手动执行脚本一个环节出错就全盘重来加班到深夜是常态。后来引入 Jenkins 搭建了一套自动化流水线上线时间从几小时缩短到几分钟团队终于能准时下班了。这个项目标题“一文弄懂自动打包部署(前后台)”的核心就是通过 Jenkins 这条“流水线”把代码从提交到上线的全过程自动化覆盖前端如 Vue/React和后端如 Spring Boot/Node.js应用实现一键式、可重复、可靠的部署。简单说它解决的核心问题是部署流程的标准化与效率提升。在没有自动化之前部署依赖个人经验容易出错且耗时。Jenkins 作为一款开源的持续集成/持续部署CI/CD工具扮演了“自动化工程师”的角色。它能监听代码仓库如 GitLab、GitHub的变动自动拉取最新代码执行你预设好的“剧本”打包、测试、构建镜像、部署最终将应用发布到目标环境服务器、Docker 或 Kubernetes。对于前后台分离的现代应用这套流程需要兼顾前端静态资源的构建优化和后端服务的可靠发布。这篇文章我会以一个典型的“Spring Boot 后端 Vue 前端”项目为例带你从零开始搭建一条完整的 Jenkins 自动化部署流水线。无论你是刚接触 DevOps 的开发者还是想优化现有流程的运维都能找到可直接复现的步骤和踩坑经验。我们将重点关注 Jenkins 的核心配置逻辑、与 Docker 的集成以及如何为前后端应用设计不同的构建策略。2. 环境准备与 Jenkins 核心安装在开始搭建流水线之前我们需要一个稳定的基础环境。我强烈建议使用一台干净的 Linux 服务器如 Ubuntu 22.04 LTS 或 CentOS 7.9作为 Jenkins 的主机这能避免很多因环境冲突带来的诡异问题。2.1 基础环境与依赖安装首先我们需要安装 Java因为 Jenkins 本身是基于 Java 开发的。现在 Jenkins 新版本通常要求 Java 11 或 17。# 以 Ubuntu 为例安装 OpenJDK 11 sudo apt update sudo apt install -y openjdk-11-jdk # 验证安装 java -version接下来是安装 Jenkins 本身。最稳妥的方式是通过其官方提供的包仓库安装这样可以方便后续升级。# 1. 添加 Jenkins 仓库密钥和源 curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \ /usr/share/keyrings/jenkins-keyring.asc /dev/null echo deb [signed-by/usr/share/keyrings/jenkins-keyring.asc] \ https://pkg.jenkins.io/debian-stable binary/ | sudo tee \ /etc/apt/sources.list.d/jenkins.list /dev/null # 2. 更新并安装 Jenkins sudo apt update sudo apt install -y jenkins # 3. 启动并设置开机自启 sudo systemctl start jenkins sudo systemctl enable jenkins # 4. 检查运行状态 sudo systemctl status jenkins安装完成后Jenkins 会默认在 8080 端口启动。你需要打开浏览器访问http://你的服务器IP:8080。首次访问会要求输入初始管理员密码该密码存储在服务器上的一个文件中。# 获取初始密码 sudo cat /var/lib/jenkins/secrets/initialAdminPassword将输出的密码粘贴到网页中即可进入初始化向导。注意很多教程会建议你关闭防火墙或直接开放 8080 端口。在生产环境中更安全的做法是配置反向代理如 Nginx并为 Jenkins 绑定域名、配置 HTTPS。或者至少使用ufw或firewalld精细控制访问来源 IP。2.2 初始配置与关键插件安装初始化时我建议选择“安装推荐的插件”。这会安装最常用的 Git、Pipeline 等插件节省大量时间。创建完管理员账户后我们就进入了 Jenkins 主界面。此时还有几项关键配置必须做配置全局工具Global Tool Configuration这是 Jenkins 知道去哪里找各种构建工具如 JDK、Maven、Node.js的地方。虽然 Jenkins 可以自动安装但在内网或追求稳定性的环境下我更倾向于指定服务器上已安装的路径。JDK取消“自动安装”在JAVA_HOME处填写/usr/lib/jvm/java-11-openjdk-amd64路径根据你的实际安装调整。Maven同样可以取消自动安装指定/usr/share/maven路径。Node.js对于前端构建需要 Node.js。你可以在这里配置自动安装某个 LTS 版本非常方便。安装必备插件除了推荐的我们还需要几个核心插件来完善流水线功能。进入“系统管理” - “插件管理” - “可选插件”Docker Pipeline用于在 Pipeline 脚本中与 Docker 交互。GitLab Plugin或Gitee Plugin根据你的代码仓库选择用于配置 Webhook 触发构建。Publish Over SSH如果你需要将构建产物推送到远程服务器这个插件必不可少。Blue Ocean提供更直观、现代化的流水线可视化界面对新手友好。安装完插件后记得重启 Jenkins 使插件生效。2.3 配置凭据Credentials这是安全连接外部资源的关键。我们需要为 Jenkins 配置访问代码仓库和服务器所需的“钥匙”。Git 仓库凭据进入“系统管理” - “凭据管理” - “全局凭据”。点击“添加凭据”。类型选择“Username with password”。在用户名和密码处填写你的 Git 仓库如 GitLab、Gitee账号密码。如果使用 SSH 私钥则选择“SSH Username with private key”。给这个凭据起一个易记的 ID如gitlab-account。后续在任务中通过这个 ID 引用。服务器 SSH 凭据如果需要部署到远程服务器同样在凭据页面类型选择“SSH Username with private key”。在 Private Key 栏中粘贴部署服务器用户的私钥内容通常是~/.ssh/id_rsa文件的内容。这实现了 Jenkins 到目标服务器的免密登录是自动化部署的基石。实操心得凭据 ID 一定要起得规范、易懂比如prod-server-deploy-key。当项目多了以后一堆id_123会让你在配置时非常头疼。另外对于生产环境可以考虑使用 Jenkins 的“凭据绑定”功能将凭据以环境变量的方式注入流水线避免在脚本中硬编码。3. 前后台项目结构与流水线设计思路在动手写 Jenkins 任务之前我们必须先理清要部署的应用结构。一个典型的前后台分离项目代码仓库的组织方式通常有两种单体仓库Mono-repo前端和后端代码放在同一个 Git 仓库的不同目录下例如my-project/ ├── backend/ # Spring Boot 项目 │ ├── src/ │ ├── pom.xml │ └── Dockerfile ├── frontend/ # Vue/React 项目 │ ├── src/ │ ├── package.json │ └── Dockerfile └── docker-compose.yml # 可选的用于本地或测试环境编排多仓库Multi-repo前端和后端分别是独立的 Git 仓库。本文将以更常见的单体仓库为例因为它简化了依赖管理和版本一致性。我们的流水线设计目标如下触发当代码推送到 Git 仓库的特定分支如main或develop时自动触发构建。构建后端使用 Maven 或 Gradle 进行编译、打包生成可执行的 JAR 文件。前端使用 Node.js 和 npm/yarn/pnpm 安装依赖执行构建命令如npm run build生成静态资源文件通常在dist目录。打包为前后端分别构建 Docker 镜像。镜像标签Tag最好包含 Git 提交哈希或构建编号便于追踪。部署将构建好的 Docker 镜像推送到私有镜像仓库如 Harbor、Nexus然后在目标服务器上拉取新镜像并重启容器。整个流程的核心是Jenkins Pipeline。Pipeline 将整个构建部署过程定义为代码Jenkinsfile存储在项目根目录与源代码一起进行版本控制。这种方式比在 Web 界面点击配置更强大、更可维护。一个基础的 Jenkinsfile 结构如下pipeline { agent any // 指定在哪个 Jenkins 节点上运行 stages { stage(拉取代码) { steps { // 从 Git 拉取代码 } } stage(后端构建) { steps { // 编译打包 Spring Boot } } stage(前端构建) { steps { // 构建 Vue/React 静态资源 } } stage(构建 Docker 镜像) { steps { // 分别为前后端构建镜像 } } stage(推送镜像) { steps { // 推送到镜像仓库 } } stage(部署) { steps { // 在目标服务器上更新容器 } } } post { // 构建后的操作如成功/失败通知 } }4. 核心环节实现编写 Jenkinsfile 与 Dockerfile理论清晰后我们进入最关键的实操部分编写定义流水线的Jenkinsfile和定义运行环境的Dockerfile。4.1 后端 Spring Boot 的 Dockerfile在backend/目录下创建Dockerfile。这里采用多阶段构建以减小最终镜像体积。# 第一阶段构建阶段 FROM maven:3.8.6-openjdk-11-slim AS builder WORKDIR /app COPY pom.xml . # 利用 Docker 层缓存先只复制 pom 文件下载依赖 RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行阶段 FROM openjdk:11-jre-slim WORKDIR /app # 从构建阶段复制打好的 jar 包 COPY --frombuilder /app/target/*.jar app.jar # 设置时区按需 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 声明运行时暴露的端口 EXPOSE 8080 # 使用 exec 形式启动使 Java 进程能接收 SIGTERM 信号 ENTRYPOINT [java, -jar, -Dspring.profiles.activeprod, /app/app.jar]注意事项-DskipTests在构建镜像时跳过测试可以加快速度但完整的 CI 流程中应该有一个独立的测试阶段。生产镜像务必使用-jre-slim这类精简版本能比完整 JDK 镜像小几百兆。ENTRYPOINT使用数组格式exec 形式是 Docker 的最佳实践。4.2 前端 Vue/React 的 Dockerfile在frontend/目录下创建Dockerfile。前端静态资源通常由 Nginx 提供服务。# 第一阶段构建阶段 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction --registryhttps://registry.npmmirror.com # 使用国内镜像加速 COPY . . RUN npm run build # 第二阶段运行阶段 FROM nginx:alpine WORKDIR /usr/share/nginx/html # 从构建阶段复制构建好的静态文件 COPY --frombuilder /app/dist ./ # 复制自定义的 Nginx 配置如果需要 # COPY --frombuilder /app/nginx.conf /etc/nginx/conf.d/default.conf # 暴露 80 端口 EXPOSE 80 CMD [nginx, -g, daemon off;]实操心得npm ci比npm install更适合自动化环境它严格根据package-lock.json安装能确保依赖一致性。使用alpine版本镜像能极大减小体积。如果前端路由使用了history模式务必在 Nginx 配置中添加try_files $uri $uri/ /index.html;这条规则避免刷新页面 404。4.3 项目根目录的 Jenkinsfile这是流水线的“总指挥”。我们将其放在项目根目录。pipeline { agent any // 可以在任何有标签的代理上运行 environment { // 定义全局环境变量 DOCKER_REGISTRY your-registry.com:5000 // 你的私有镜像仓库地址 BACKEND_IMAGE_NAME ${DOCKER_REGISTRY}/myapp-backend FRONTEND_IMAGE_NAME ${DOCKER_REGISTRY}/myapp-frontend // 从 Git 提交中获取短哈希作为镜像标签的一部分 GIT_COMMIT_SHORT sh(script: git rev-parse --short HEAD, returnStdout: true).trim() BUILD_TAG ${env.BUILD_NUMBER}-${GIT_COMMIT_SHORT} } stages { stage(拉取代码) { steps { checkout scmGit( branches: [[name: */main]], // 监听 main 分支 extensions: [], userRemoteConfigs: [[ credentialsId: gitlab-account, // 之前配置的凭据 ID url: http://your-gitlab.com/your-group/your-project.git ]] ) } } stage(单元测试) { steps { dir(backend) { sh mvn clean test // 执行后端测试 } // 前端测试如果需要也可以在这里添加 // dir(frontend) { sh npm run test:unit } } post { always { junit backend/target/surefire-reports/*.xml // 收集测试报告 } } } stage(后端构建与打包) { steps { dir(backend) { sh mvn clean package -DskipTests // 跳过测试因为上一步已执行 } } } stage(前端构建) { steps { dir(frontend) { sh npm ci // 安装依赖 sh npm run build // 构建生产环境静态资源 } } } stage(构建 Docker 镜像) { steps { script { // 构建后端镜像 docker.build(${BACKEND_IMAGE_NAME}:${BUILD_TAG}, ./backend) // 构建前端镜像 docker.build(${FRONTEND_IMAGE_NAME}:${BUILD_TAG}, ./frontend) } } } stage(推送镜像到仓库) { steps { script { // 假设已通过 docker login 配置了仓库认证 docker.withRegistry(https://${DOCKER_REGISTRY}) { docker.image(${BACKEND_IMAGE_NAME}:${BUILD_TAG}).push() docker.image(${FRONTEND_IMAGE_NAME}:${BUILD_TAG}).push() // 同时打一个 latest 标签谨慎用于生产 docker.image(${BACKEND_IMAGE_NAME}:${BUILD_TAG}).push(latest) docker.image(${FRONTEND_IMAGE_NAME}:${BUILD_TAG}).push(latest) } } } } stage(部署到服务器) { steps { script { // 使用 SSH 插件在远程服务器上执行命令 sshPublisher( publishers: [ sshPublisherDesc( configName: prod-server, // 在 Jenkins 系统配置中定义的 SSH Server 名称 transfers: [ sshTransfer( execCommand: # 拉取最新镜像 docker pull ${BACKEND_IMAGE_NAME}:${BUILD_TAG} docker pull ${FRONTEND_IMAGE_NAME}:${BUILD_TAG} # 停止并移除旧容器 docker stop myapp-backend || true docker rm myapp-backend || true docker stop myapp-frontend || true docker rm myapp-frontend || true # 启动新容器使用 Docker Compose 更佳 docker run -d --name myapp-backend --network myapp-net -p 8080:8080 ${BACKEND_IMAGE_NAME}:${BUILD_TAG} docker run -d --name myapp-frontend --network myapp-net -p 80:80 ${FRONTEND_IMAGE_NAME}:${BUILD_TAG} ) ], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true ) ] ) } } } } post { success { echo 流水线执行成功 // 可以在这里集成邮件、钉钉、企业微信等通知 // emailext body: 项目构建部署成功, subject: Jenkins构建通知, to: teamexample.com } failure { echo ❌ 流水线执行失败 } always { echo 本次构建标签: ${BUILD_TAG} cleanWs() // 清理工作空间 } } }这个Jenkinsfile定义了一个完整的六阶段流水线。其中environment块定义了全局变量stages块包含了从拉取代码到部署的各个步骤post块用于构建后的处理。重要提示直接使用docker run命令部署过于简单。对于生产环境强烈建议使用docker-compose up -d或编写 Shell 脚本来管理容器这样可以更优雅地处理网络、卷挂载、环境变量等配置。上述示例中的execCommand部分应替换为执行一个预置在服务器上的部署脚本如deploy.sh该脚本负责更复杂的更新逻辑。5. 配置 Jenkins 任务与 Git Webhook 自动触发有了Jenkinsfile我们还需要在 Jenkins 上创建一个任务Job来执行它并配置 Git 仓库的 Webhook 来实现代码推送自动构建。5.1 创建 Pipeline 任务在 Jenkins 首页点击“新建任务”。输入任务名称例如myapp-full-ci-cd选择“流水线”Pipeline点击确定。在任务配置页面找到“流水线”Pipeline部分。在“定义”处选择“Pipeline script from SCM”。这告诉 Jenkins 从源代码管理系统中获取Jenkinsfile。在“SCM”处选择“Git”。填入你的仓库 URL并选择之前配置的凭据gitlab-account。在“分支指定符”中填写*/main或其他你想要监听的分支。在“脚本路径”中保持默认的Jenkinsfile。如果你的Jenkinsfile不在根目录则需要修改为相应路径。点击保存。现在你可以手动点击“立即构建”来触发第一次流水线运行。如果一切配置正确你应该能看到各个阶段依次执行并在“阶段视图”中看到进度。5.2 配置 GitLab Webhook 实现自动触发手动构建显然不是我们想要的。我们需要实现当开发者向main分支推送代码时GitLab 自动通知 Jenkins 开始构建。在 Jenkins 中生成身份验证令牌进入 Jenkins 用户设置点击右上角用户名 - 设置。在“API Token”区域点击“添加新 Token”生成一个 Token 并复制保存好只显示一次。安装并配置 GitLab 插件确保已安装GitLab Plugin。进入 Jenkins 系统管理 - 系统配置找到“GitLab”部分。点击“添加 GitLab 连接”。连接名称填gitlabGitLab 主机 URL 填你的 GitLab 地址如http://your-gitlab.com。在“凭据”处添加一个新的凭据类型选择“GitLab API token”将你在 GitLab 上生成的 Personal Access Token 粘贴进去需要在 GitLab 账号设置中生成需api权限。点击“测试连接”确保成功。在 Jenkins 任务中启用 GitLab 触发回到你的流水线任务配置页面。找到“构建触发器”部分勾选“Build when a change is pushed to GitLab”。会显示一个 Webhook URL如http://jenkins.your-server.com/project/myapp-full-ci-cd。复制这个 URL。在 GitLab 项目中配置 Webhook进入你的 GitLab 项目页面 - 设置 - Webhooks。将复制的 Jenkins Webhook URL 粘贴到“URL”字段。在“触发事件”中至少勾选“Push events”和“Merge request events”。为了安全可以填写一个“Secret Token”可选但建议。然后在 Jenkins 任务构建触发器的“高级”设置中填入同样的 Token。取消勾选“Enable SSL verification”如果 Jenkins 使用的是 HTTP仅限测试环境生产务必用 HTTPS。点击“添加 Webhook”。添加后可以点击“测试” - “Push events”如果返回 200则表示配置成功。完成以上步骤后当你下次向main分支推送代码时GitLab 会向 Jenkins 发送一个 POST 请求Jenkins 接收到后就会自动触发流水线构建。常见问题Webhook 测试失败常见原因是 Jenkins 服务器防火墙未开放端口或者 Jenkins 地址是内网地址而 GitLab 无法访问。生产环境建议 Jenkins 使用域名并通过 Nginx 反向代理同时配置好防火墙规则。另外确保 Jenkins 的“匿名用户”具有“读取”权限或者为 GitLab 的 Webhook 调用创建一个专用 Jenkins 用户并赋予相应权限。6. 高级优化与生产环境考量基础的流水线跑通后我们可以从稳定性、效率和安全性方面进行优化使其更适合生产环境。6.1 使用 Docker Compose 进行服务编排在部署阶段直接使用多个docker run命令难以管理依赖和网络。更好的做法是使用 Docker Compose。在项目根目录或服务器上创建docker-compose.prod.ymlversion: 3.8 services: backend: image: your-registry.com:5000/myapp-backend:${TAG:-latest} container_name: myapp-backend restart: always networks: - myapp-net environment: - SPRING_PROFILES_ACTIVEprod - DB_HOSTdatabase # depends_on: # - database # 健康检查 healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] interval: 30s timeout: 10s retries: 3 frontend: image: your-registry.com:5000/myapp-frontend:${TAG:-latest} container_name: myapp-frontend restart: always networks: - myapp-net ports: - 80:80 # 可以在此添加数据库、Redis等服务 # database: # image: postgres:14-alpine # environment: ... networks: myapp-net: driver: bridge然后在 Jenkins 的部署阶段远程执行的命令可以简化为# 在服务器上 export TAG本次构建的标签 docker-compose -f docker-compose.prod.yml pull docker-compose -f docker-compose.prod.yml up -d # 清理旧的 dangling 镜像 docker image prune -f6.2 实现“蓝绿部署”或“滚动更新”以降低风险直接停止旧容器启动新容器会导致服务短暂中断。更高级的策略是蓝绿部署准备两套完全相同的环境蓝组和绿组。当前流量指向蓝组v1版本。新版本部署到绿组v2版本并进行内部测试。测试通过后将流量切换至绿组。蓝组作为回滚备用或升级为下一个版本的部署环境。在 Docker 环境下可以通过标签和 Nginx 反向代理动态 upstream 来实现简单的蓝绿部署。或者直接使用 Kubernetes 的 Deployment 策略它能原生支持滚动更新。6.3 敏感信息管理与安全不要在 Jenkinsfile 或 Dockerfile 中硬编码密码、密钥。使用 Jenkins 的“凭据绑定”功能在environment块或withCredentials步骤中注入。stage(部署) { environment { SSH_PRIVATE_KEY credentials(prod-server-ssh-key) // 引用SSH私钥凭据 } steps { sh echo $SSH_PRIVATE_KEY /tmp/deploy_key chmod 600 /tmp/deploy_key ssh -i /tmp/deploy_key userserver deploy.sh } }后端应用的配置使用环境变量、外部配置文件通过卷挂载或配置中心如 Spring Cloud Config来管理。镜像仓库认证在 Jenkins 服务器上执行docker login your-registry.com凭证会保存在~/.docker/config.json。也可以在 Pipeline 中使用withRegistry和docker.withRegistry配合凭据。6.4 构建性能优化使用 Jenkins Agent 标签将前端构建这类需要 Node.js 的任务分配到安装了 Node 环境的特定 Agent 上执行。利用 Docker 层缓存在Dockerfile中把不经常变动的操作如安装依赖放在前面经常变动的操作如复制源代码放在后面。使用 Jenkins 的缓存机制对于 Maven可以配置本地仓库缓存对于 npm可以使用npm cache或第三方缓存工具。并行执行阶段如果前后端构建没有依赖关系可以在Jenkinsfile中使用parallel指令让它们同时进行。stage(并行构建) { parallel { stage(构建后端) { steps { ... } } stage(构建前端) { steps { ... } } } }7. 常见问题排查与调试技巧即使按照步骤操作也难免会遇到问题。这里记录几个我踩过的坑和排查思路。7.1 Jenkins 构建日志分析与调试“Cannot run program “mvn””说明 Jenkins 节点上没有安装 Maven或者全局工具配置中指定的 Maven 路径不对。检查“系统管理” - “全局工具配置”。“npm: command not found”同理需要确保 Node.js 已正确配置。可以在流水线开始加一个sh node --version npm --version的步骤来验证。Docker 命令执行失败 “Got permission denied”运行 Docker 命令的用户通常是jenkins没有加入docker用户组。执行sudo usermod -aG docker jenkins然后重启 Jenkins 服务。Webhook 触发失败返回 403通常是 Jenkins 的 CSRF 保护“防止跨站点请求伪造”导致的。进入“系统管理” - “全局安全配置”在“CSRF Protection”下勾选“允许没有crumb的请求访问 /project 和 /build 端点”或者确保你的 GitLab 插件配置了正确的 Secret Token。7.2 Docker 构建与部署问题镜像构建缓慢检查网络特别是拉取基础镜像时。可以为 Docker Daemon 配置国内镜像加速器。在/etc/docker/daemon.json中添加{ registry-mirrors: [https://your-mirror.mirror.aliyuncs.com] }推送镜像到私有仓库失败首先确保已在 Jenkins 服务器上执行过docker login your-registry.com。如果使用自签名证书的仓库需要在 Docker Daemon 的配置中信任该证书。部署时端口冲突如果旧容器没有成功停止或移除新容器启动时会因为端口被占用而失败。在部署脚本中确保先停止再移除容器并且可以加入|| true来忽略不存在的容器导致的错误。7.3 流水线脚本编写技巧使用script块当需要在steps中写复杂的 Groovy 逻辑时将其包裹在script { ... }块中。善用post块除了success和failure还有always、changed、unstable等条件可以用于在不同状态下发送通知、清理资源。环境变量传递在environment块定义的变量可以在整个流水线中使用。在 Shell 脚本中引用 Jenkins 环境变量要用${env.VAR_NAME}或$VAR_NAME在双引号字符串中。调试输出在关键步骤使用echo打印变量值如echo 当前镜像标签: ${BUILD_TAG}这对排查问题非常有帮助。搭建自动化部署流水线是一个迭代的过程不要期望一蹴而就。先从最简单的“代码推送到特定分支即触发构建”开始然后逐步加入测试、镜像构建、部署等环节。每增加一个环节都意味着交付速度的提升和人为错误的减少。当你看到每次代码提交后几分钟内就能自动完成测试并部署到测试环境时那种解放双手的成就感就是 DevOps 带来的最大价值。