Java云原生技术栈学习与应用指南

云原生技术栈:Java开发者的必修课

各位Java开发者,大家好!欢迎来到今天的讲座。今天我们要聊的是“Java云原生技术栈的学习与应用指南”。如果你还在用传统的Java开发方式,那么你可能已经落后了。云原生(Cloud-Native)已经成为现代软件开发的主流趋势,而Java作为一门历史悠久且广泛应用的编程语言,自然也不会缺席这场变革。

什么是云原生?简单来说,云原生是一种构建和运行应用程序的方法,它充分利用了云计算的优势,如弹性、可扩展性、自动化等。云原生应用通常基于微服务架构,使用容器化技术(如Docker),并通过Kubernetes等平台进行编排和管理。Java作为一种成熟的语言,结合这些云原生技术,可以帮助我们更高效地开发、部署和维护应用。

在这篇文章中,我们将从以下几个方面深入探讨Java云原生技术栈:

  1. 为什么选择Java进行云原生开发?
  2. 云原生的核心概念和技术栈
  3. Java与微服务架构的最佳实践
  4. 容器化与Docker的使用
  5. Kubernetes入门与应用
  6. 云原生开发工具链
  7. 可观测性与监控
  8. CI/CD流水线的搭建
  9. 云原生安全与最佳实践
  10. 未来展望与学习资源

希望通过今天的分享,能够帮助你在Java云原生的道路上迈出坚实的一步。准备好了吗?让我们开始吧!


1. 为什么选择Java进行云原生开发?

首先,我们来聊聊为什么Java是云原生开发的理想选择。虽然近年来出现了许多新兴的语言和框架,但Java依然保持着强大的生命力,尤其是在企业级应用开发中。以下是Java在云原生开发中的几个优势:

1.1 成熟的生态系统

Java拥有庞大的生态系统,涵盖了从Web开发到大数据处理的各种场景。Spring Framework、Hibernate、Apache Kafka等知名框架和库都为Java开发者提供了丰富的工具。特别是在微服务架构中,Spring Boot和Spring Cloud已经成为事实标准,极大地简化了微服务的开发和部署。

1.2 强大的性能与稳定性

Java的JVM(Java虚拟机)经过多年的优化,已经具备了极高的性能和稳定性。JVM的垃圾回收机制、即时编译(JIT)等功能使得Java应用能够在高并发环境下保持良好的性能表现。此外,Java的多线程模型也使得它非常适合处理复杂的业务逻辑和并发任务。

1.3 广泛的社区支持

Java拥有全球最大的开发者社区之一,这意味着你可以轻松找到各种问题的解决方案。无论是Stack Overflow上的问答,还是GitHub上的开源项目,Java都有大量的资源可供参考。此外,Java的官方文档也非常详细,适合各个层次的开发者学习。

1.4 与云平台的无缝集成

大多数主流云平台(如AWS、Google Cloud、Azure等)都提供了对Java的原生支持。通过这些平台提供的SDK和服务,Java应用可以轻松地与云基础设施进行交互。例如,AWS Lambda允许你直接在云端运行Java函数,而不需要管理底层的服务器。

1.5 丰富的工具链

Java拥有一个非常完善的工具链,涵盖了从开发、测试到部署的各个环节。Maven、Gradle等构建工具可以帮助你自动化项目的构建和依赖管理;JUnit、TestNG等测试框架则确保了代码的质量;而像Jenkins、GitLab CI这样的CI/CD工具则可以帮助你实现持续集成和持续交付。


2. 云原生的核心概念和技术栈

了解了Java的优势之后,接下来我们来聊聊云原生的核心概念和技术栈。云原生并不是指某种特定的技术,而是一种开发和运维的理念。它强调的是如何利用云计算的特性,构建更加灵活、可扩展的应用程序。以下是云原生的几个核心概念:

2.1 微服务架构

微服务架构是云原生的核心之一。与传统的单体应用不同,微服务将应用程序拆分为多个独立的服务,每个服务负责处理特定的业务功能。这些服务之间通过轻量级的通信协议(如HTTP/REST、gRPC等)进行交互。

微服务的优势在于:

  • 独立部署:每个服务都可以独立部署,不会影响其他服务的运行。
  • 技术多样性:不同的服务可以使用不同的技术栈,灵活性更高。
  • 易于扩展:可以根据需求对特定的服务进行水平扩展,提升系统的整体性能。
  • 故障隔离:某个服务出现问题时,不会导致整个系统崩溃,提高了系统的容错能力。

在Java中,Spring Boot和Spring Cloud是最常用的微服务框架。Spring Boot提供了快速构建微服务的基础,而Spring Cloud则提供了服务发现、配置管理、负载均衡等功能,帮助你更好地管理和维护微服务集群。

2.2 容器化与Docker

容器化是云原生的另一个重要概念。传统的虚拟机(VM)虽然可以隔离应用程序,但启动速度慢、资源占用大。相比之下,容器(Container)更加轻量级,启动速度快,资源利用率更高。Docker是目前最流行的容器化工具,它允许你将应用程序及其依赖打包成一个独立的容器镜像,方便部署和迁移。

Docker的核心概念包括:

  • 镜像(Image):包含应用程序及其依赖的只读模板。
  • 容器(Container):基于镜像创建的可执行实例。
  • Dockerfile:定义如何构建镜像的文本文件。
  • Docker Compose:用于定义和运行多容器应用的工具。

在Java开发中,我们可以使用Docker来打包Spring Boot应用。以下是一个简单的Dockerfile示例:

# 使用官方的OpenJDK镜像作为基础镜像
FROM openjdk:17-jdk-alpine

# 设置工作目录
WORKDIR /app

# 将构建好的JAR文件复制到容器中
COPY target/my-app.jar /app/my-app.jar

# 暴露应用程序的端口
EXPOSE 8080

# 启动应用程序
ENTRYPOINT ["java", "-jar", "/app/my-app.jar"]

2.3 Kubernetes

Kubernetes(简称K8s)是目前最流行的容器编排平台。它可以帮助你自动化容器的部署、扩展和管理。Kubernetes的核心概念包括:

  • Pod:Kubernetes中最基本的调度单元,可以包含一个或多个容器。
  • Service:用于定义网络访问规则,使得Pod之间的通信更加方便。
  • Deployment:用于管理Pod的生命周期,支持滚动更新、回滚等功能。
  • Ingress:用于管理外部流量的入口,支持负载均衡、SSL终止等功能。
  • ConfigMapSecret:用于存储应用程序的配置和敏感信息。

在Java开发中,Kubernetes可以帮助你轻松地管理和扩展微服务集群。例如,你可以使用Helm(Kubernetes的包管理工具)来部署和管理Spring Boot应用。

2.4 API网关

在微服务架构中,API网关起到了至关重要的作用。它充当了客户端与后端服务之间的桥梁,负责路由请求、身份验证、限流等功能。常见的API网关有Zuul、Spring Cloud Gateway等。

Spring Cloud Gateway是一个基于Spring WebFlux构建的API网关,具有高性能和低延迟的特点。以下是一个简单的Spring Cloud Gateway配置示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user_service
          uri: http://user-service:8080
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

2.5 配置管理

在云原生环境中,配置管理是非常重要的。传统的配置文件(如application.properties)可能会导致配置混乱,尤其是在多个环境(如开发、测试、生产)中。因此,我们需要一种集中式的配置管理方案。

Spring Cloud Config是一个基于Git的配置中心,它可以将应用程序的配置存储在远程仓库中,并根据环境动态加载不同的配置。以下是一个简单的Spring Cloud Config客户端配置示例:

spring:
  application:
    name: my-app
  cloud:
    config:
      uri: http://config-server:8888

3. Java与微服务架构的最佳实践

在掌握了云原生的核心概念之后,接下来我们来看看Java与微服务架构的最佳实践。微服务架构虽然带来了灵活性和可扩展性,但也引入了一些新的挑战。为了确保微服务的稳定性和可靠性,我们需要遵循一些最佳实践。

3.1 服务拆分原则

在设计微服务时,我们应该遵循“单一职责原则”(Single Responsibility Principle),即每个服务只负责处理特定的业务功能。服务的粒度不宜过大或过小,通常建议每个服务的代码量在1000行左右。

此外,我们还需要考虑服务之间的依赖关系。尽量减少服务之间的耦合,避免出现“紧耦合”的情况。可以通过API网关或消息队列(如RabbitMQ、Kafka)来解耦服务之间的通信。

3.2 服务注册与发现

在微服务架构中,服务之间的通信通常是动态的。因此,我们需要一个服务注册与发现机制,使得服务可以自动发现彼此的地址。Eureka、Consul、Nacos等都是常用的服务注册与发现工具。

以Eureka为例,以下是一个简单的Eureka客户端配置示例:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

3.3 负载均衡

当多个服务实例同时运行时,我们需要使用负载均衡来分配请求。Ribbon、Feign等都是Spring Cloud提供的负载均衡工具。它们可以根据不同的策略(如轮询、随机、加权等)将请求分发到不同的服务实例上。

以下是一个使用Feign进行负载均衡的示例:

@FeignClient(name = "user-service")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    User getUserById(@PathVariable("id") Long id);
}

3.4 断路器与熔断

在微服务架构中,服务之间的调用可能会出现超时、失败等情况。为了避免某个服务的故障影响整个系统,我们可以使用断路器(Circuit Breaker)来保护系统。Hystrix、Resilience4j等都是常用的断路器实现。

以下是一个使用Hystrix的示例:

@HystrixCommand(fallbackMethod = "fallbackGetUser")
public User getUser(Long id) {
    return restTemplate.getForObject("http://user-service/users/" + id, User.class);
}

public User fallbackGetUser(Long id) {
    return new User(id, "Fallback User");
}

3.5 分布式事务

在微服务架构中,分布式事务是一个常见的挑战。由于服务之间的调用是异步的,传统的ACID事务无法保证数据的一致性。为此,我们可以使用Saga模式或TCC(Try-Confirm-Cancel)模式来实现分布式事务。

Saga模式的核心思想是将一个复杂的事务拆分为多个子事务,每个子事务都是幂等的。如果某个子事务失败,则可以回滚前面的所有子事务。以下是一个简单的Saga模式示例:

@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private ShippingService shippingService;

    public void placeOrder(Order order) {
        try {
            paymentService.charge(order.getAmount());
            shippingService.ship(order.getAddress());
        } catch (Exception e) {
            // 回滚支付
            paymentService.refund(order.getAmount());
            throw e;
        }
    }
}

4. 容器化与Docker的使用

在云原生环境中,容器化是必不可少的。Docker为我们提供了一个轻量级的容器化解决方案,使得应用程序可以在任何环境中一致地运行。接下来,我们来看看如何使用Docker来容器化Java应用。

4.1 构建Docker镜像

要将Java应用容器化,首先需要编写一个Dockerfile,定义如何构建Docker镜像。我们已经在前面介绍了一个简单的Dockerfile示例,这里再补充一些细节。

  • 选择合适的基础镜像:对于Java应用,可以选择openjdkadoptopenjdk作为基础镜像。如果你的应用体积较大,可以考虑使用alpine版本的基础镜像,以减小镜像大小。
  • 多阶段构建:为了进一步减小镜像大小,可以使用多阶段构建。多阶段构建允许我们在构建过程中使用不同的基础镜像,最终只保留运行时所需的文件。

以下是一个使用多阶段构建的Dockerfile示例:

# 第一阶段:构建阶段
FROM maven:3.8.1-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 第二阶段:运行阶段
FROM openjdk:17-jdk-alpine
WORKDIR /app
COPY --from=build /app/target/my-app.jar /app/my-app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/my-app.jar"]

4.2 运行Docker容器

构建好Docker镜像后,可以使用docker run命令来启动容器。以下是一些常用的docker run选项:

  • -d:以后台模式运行容器。
  • -p:将主机的端口映射到容器的端口。
  • -v:将主机的目录挂载到容器中。
  • --name:为容器指定一个名称。

以下是一个启动Spring Boot应用的示例命令:

docker run -d -p 8080:8080 --name my-app my-app:latest

4.3 Docker Compose

如果你的应用由多个服务组成,可以使用Docker Compose来定义和运行多容器应用。docker-compose.yml文件用于描述每个服务的配置,包括镜像、端口、环境变量等。

以下是一个简单的docker-compose.yml示例:

version: '3'
services:
  app:
    image: my-app:latest
    ports:
      - "8080:8080"
    depends_on:
      - db
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb

5. Kubernetes入门与应用

在容器化的基础上,Kubernetes为我们提供了一个强大的容器编排平台。通过Kubernetes,我们可以轻松地管理和扩展容器化的应用。接下来,我们来看看如何在Kubernetes中部署和管理Java应用。

5.1 创建Kubernetes资源

在Kubernetes中,所有的资源都是通过YAML文件来定义的。以下是一个简单的Deployment资源示例,用于部署Spring Boot应用:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-app:latest
          ports:
            - containerPort: 8080

5.2 暴露服务

为了让外部用户能够访问Kubernetes中的服务,我们需要创建一个Service资源。Service可以通过多种方式暴露服务,常见的有ClusterIPNodePortLoadBalancer

以下是一个使用LoadBalancer暴露服务的示例:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

5.3 使用Helm部署应用

Helm是Kubernetes的包管理工具,可以帮助我们更方便地部署和管理应用。Helm使用Chart来描述应用的配置,类似于Docker的docker-compose.yml文件。

以下是一个简单的Helm Chart结构:

my-app/
├── Chart.yaml
├── values.yaml
└── templates/
    ├── deployment.yaml
    └── service.yaml

values.yaml文件用于定义应用的默认配置,templates目录下的文件用于生成Kubernetes资源。

5.4 监控与日志

在Kubernetes中,监控和日志管理是非常重要的。Prometheus和Grafana是常用的监控工具,可以帮助我们实时监控应用的性能指标。ELK(Elasticsearch、Logstash、Kibana)则是常用的日志管理工具,可以帮助我们集中收集和分析日志。


6. 云原生开发工具链

在云原生开发中,除了上述的技术栈之外,还有一些常用的工具可以帮助我们提高开发效率。接下来,我们来看看这些工具的具体用途。

6.1 GitOps

GitOps是一种基于Git的持续交付方法,它将基础设施和应用程序的配置都存储在Git仓库中。通过GitOps,我们可以实现自动化部署和回滚,确保系统的稳定性和一致性。

Argo CD和Flux是两个常用的GitOps工具。它们可以帮助我们自动同步Git仓库中的配置到Kubernetes集群中。

6.2 CI/CD流水线

CI/CD(持续集成/持续交付)是云原生开发中不可或缺的一部分。通过CI/CD流水线,我们可以自动化构建、测试和部署应用。Jenkins、GitLab CI、CircleCI等都是常用的CI/CD工具。

以下是一个简单的Jenkins Pipeline示例:

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                sh 'kubectl apply -f k8s/deployment.yaml'
            }
        }
    }
}

6.3 服务网格

服务网格(Service Mesh)是一种用于管理微服务之间通信的基础设施。Istio、Linkerd等都是常用的服务网格工具。它们可以帮助我们实现流量管理、安全性和可观测性等功能。

Istio通过注入Sidecar代理来拦截服务之间的通信,从而实现了流量控制、限流、熔断等功能。以下是一个简单的Istio配置示例:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts:
    - my-app
  http:
    - route:
        - destination:
            host: my-app
            subset: v1

7. 可观测性与监控

在云原生环境中,可观测性(Observability)是确保系统稳定性的关键。通过监控、日志和追踪,我们可以及时发现并解决问题。接下来,我们来看看如何实现可观测性。

7.1 监控

Prometheus是一个开源的监控系统,支持多种数据采集方式。我们可以使用Prometheus Operator来部署Prometheus,并通过Grafana可视化监控数据。

在Java应用中,Micrometer是一个常用的监控库,它可以与Prometheus、Datadog等监控系统集成。以下是一个使用Micrometer的示例:

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final MeterRegistry meterRegistry;

    @Autowired
    public MyService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    public void doSomething() {
        meterRegistry.counter("my.service.calls").increment();
        // 业务逻辑
    }
}

7.2 日志

在云原生环境中,日志管理非常重要。ELK(Elasticsearch、Logstash、Kibana)是一个常用的日志管理工具,可以帮助我们集中收集和分析日志。

在Java应用中,我们可以使用Logback或Log4j2作为日志框架,并通过Loki等工具将日志发送到ELK集群。以下是一个使用Logback的示例:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

7.3 分布式追踪

在微服务架构中,分布式追踪可以帮助我们跟踪请求在多个服务之间的流动。Jaeger、Zipkin等都是常用的分布式追踪工具。

在Java应用中,我们可以使用Spring Cloud Sleuth来实现分布式追踪。以下是一个使用Sleuth的示例:

spring:
  sleuth:
    sampler:
      probability: 1.0

8. CI/CD流水线的搭建

CI/CD流水线是云原生开发中不可或缺的一部分。通过CI/CD流水线,我们可以自动化构建、测试和部署应用。接下来,我们来看看如何搭建一个完整的CI/CD流水线。

8.1 选择CI/CD工具

目前市面上有许多CI/CD工具,常见的有Jenkins、GitLab CI、CircleCI等。每种工具都有其优缺点,选择时可以根据团队的需求和技术栈来决定。

  • Jenkins:功能强大,插件丰富,适合大型项目。
  • GitLab CI:与GitLab集成度高,适合中小型项目。
  • CircleCI:配置简单,适合快速迭代的项目。

8.2 配置CI/CD流水线

无论使用哪种工具,CI/CD流水线的基本步骤都相似,主要包括:

  • 拉取代码:从Git仓库中拉取最新的代码。
  • 构建应用:编译代码,打包成Docker镜像。
  • 运行测试:执行单元测试、集成测试等。
  • 部署应用:将应用部署到Kubernetes集群中。

以下是一个使用Jenkins的CI/CD流水线示例:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/your-repo/my-app.git'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh 'docker build -t my-app:latest .'
            }
        }
        stage('Push Docker Image') {
            steps {
                sh 'docker push my-app:latest'
            }
        }
        stage('Deploy to Kubernetes') {
            steps {
                sh 'kubectl apply -f k8s/deployment.yaml'
            }
        }
    }
}

8.3 自动化测试

在CI/CD流水线中,自动化测试是非常重要的。通过自动化测试,我们可以确保每次代码变更都不会引入新的问题。常见的自动化测试工具包括JUnit、TestNG、Cucumber等。

以下是一个使用JUnit的单元测试示例:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class MyServiceTest {

    @Test
    void testDoSomething() {
        MyService myService = new MyService();
        myService.doSomething();
        // 断言
    }
}

9. 云原生安全与最佳实践

在云原生环境中,安全性是一个不容忽视的问题。由于云原生应用通常运行在公共云或混合云环境中,因此我们需要采取一系列措施来确保应用的安全性。接下来,我们来看看云原生安全的最佳实践。

9.1 网络安全

网络安全是云原生安全的基础。我们可以通过以下措施来加强网络安全性:

  • 使用VPC:在云平台上创建虚拟私有云(VPC),限制外部访问。
  • 配置防火墙:通过防火墙规则限制不必要的网络流量。
  • 启用TLS/SSL:为应用启用传输层安全协议,确保数据传输的安全性。

9.2 身份认证与授权

身份认证和授权是云原生应用的重要组成部分。我们可以通过以下方式来实现身份认证和授权:

  • 使用OAuth2:通过OAuth2协议实现第三方登录和授权。
  • JWT(JSON Web Token):使用JWT来传递用户身份信息,确保令牌的安全性。
  • RBAC(基于角色的访问控制):通过RBAC来限制用户对资源的访问权限。

9.3 数据加密

在云原生环境中,数据加密是非常重要的。我们可以通过以下方式来加密敏感数据:

  • 静态加密:使用加密算法(如AES)对存储在磁盘上的数据进行加密。
  • 传输加密:使用TLS/SSL对传输中的数据进行加密。
  • 密钥管理:使用云平台提供的密钥管理服务(如AWS KMS、Azure Key Vault)来管理加密密钥。

9.4 安全扫描

为了确保应用的安全性,我们可以在CI/CD流水线中集成安全扫描工具。常见的安全扫描工具包括SonarQube、OWASP ZAP、Snyk等。这些工具可以帮助我们检测代码中的漏洞和安全隐患。

以下是一个使用SonarQube的示例:

pipeline {
    agent any
    stages {
        stage('SonarQube Scan') {
            steps {
                withSonarQubeEnv('sonarqube') {
                    sh 'mvn sonar:sonar'
                }
            }
        }
    }
}

10. 未来展望与学习资源

随着云计算技术的不断发展,云原生已经成为现代软件开发的主流趋势。Java作为一门成熟的编程语言,结合云原生技术,可以帮助我们更高效地开发、部署和维护应用。未来,我们可以期待更多的创新和进步,例如Serverless架构、边缘计算等。

10.1 Serverless架构

Serverless架构是一种无服务器的计算模型,开发者无需管理底层的服务器,只需编写函数即可。AWS Lambda、Google Cloud Functions、Azure Functions等都是常见的Serverless平台。Java也可以用于编写Serverless函数,帮助我们实现更轻量级的应用。

10.2 边缘计算

边缘计算是一种将计算能力分布到网络边缘的架构,可以降低延迟、提高响应速度。随着5G网络的普及,边缘计算将成为云原生应用的一个重要发展方向。Java可以与边缘计算平台(如KubeEdge)结合,帮助我们构建更加高效的分布式应用。

10.3 学习资源

最后,给大家推荐一些学习云原生技术的资源:

  • 官方文档:Spring官方文档、Docker官方文档、Kubernetes官方文档等都是非常权威的学习资料。
  • 书籍:《云原生Java:使用Spring Boot、Docker和Kubernetes》、《Kubernetes权威指南》等书籍可以帮助你深入理解云原生技术。
  • 在线课程:Coursera、Udemy等平台上有很多关于云原生的课程,适合初学者和进阶者学习。

总结

今天的讲座就到这里了。通过今天的分享,相信大家对Java云原生技术栈有了更深入的了解。云原生不仅仅是技术的变革,更是思维方式的转变。希望每一位Java开发者都能在这个新时代中找到自己的位置,迎接未来的挑战。

如果你有任何问题或想法,欢迎在评论区留言。感谢大家的聆听,祝大家 coding happy!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注