瑞克
Published on 2026-07-16 / 0 Visits
0
0

Nginx与Tomcat

Nginx与Tomcat——网站的"前台接待"与"后台车间"

导读:如果你开了一家大餐厅,Nginx就是"前台接待员"——负责迎客、安排座位、把订单送到后厨;Tomcat则是"后厨厨师"——专门处理Java菜品(动态网页)。前台接待员还能同时服务很多桌客人(高并发),而后厨厨师则专注做好每一道菜。本章我们就来搭建这套"前厅+后厨"系统!


一、Nginx入门

1.1 Nginx是什么?

Nginx(发音"Engine-X")是一款高性能的 Web服务器反向代理服务器,由俄罗斯程序员Igor Sysoev开发。

Nginx的核心能力

能力说明类比
Web服务器直接提供静态文件(HTML/CSS/JS/图片)餐厅的自助餐区
反向代理把请求转发给后端服务器(如Tomcat)前台接待员把订单送到后厨
负载均衡把流量分配到多台后端服务器前台把客人分配到多个服务窗口
缓存加速缓存后端响应,减轻后厨压力热门菜品提前做好备料
SSL终结统一处理HTTPS加密前台统一处理会员卡验证

1.2 Nginx的事件驱动模型

问题场景:Apache使用"一个连接一个进程/线程"的模型,当并发连接数达到几千时,内存消耗巨大。

解决方案:Nginx使用事件驱动(Event-Driven)+ 异步非阻塞模型,一个Worker进程可以同时处理数千个连接。

生活类比

  • Apache = 每个客人配一个专属服务员(人少时服务好,人多时服务员不够用)
  • Nginx = 少数几个服务员同时照看很多桌客人(客人点完菜后服务员去服务下一桌,菜做好了再回来上菜)
        用户请求 ──→ [Master进程] ──→ [Worker1] ──┐
                       管理        ──→ [Worker2] ──┤──→ 后端服务器
                                     ──→ [Worker3] ──┘

1.3 安装Nginx

操作RHEL系(CentOS/Rocky)Debian系(Ubuntu)
安装yum install nginx -yapt install nginx -y
启动systemctl start nginxsystemctl start nginx
开机自启systemctl enable nginxsystemctl enable nginx
重载配置nginx -s reloadnginx -s reload
测试配置nginx -tnginx -t

提示:如需安装最新稳定版,需先添加Nginx官方源。RHEL系创建 /etc/yum.repos.d/nginx.repo;Debian系通过 add-apt-repository 添加PPA。


二、Nginx目录结构

路径说明
/etc/nginx/nginx.conf主配置文件
/etc/nginx/conf.d/虚拟主机配置目录
/usr/share/nginx/html/默认网站根目录
/var/log/nginx/日志目录(access.log/error.log)

三、nginx.conf详解

3.1 配置文件整体结构

# ===== 全局块 =====
user nginx;                          # 运行用户
worker_processes auto;               # Worker进程数(auto=匹配CPU核心数)
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# ===== events块 =====
events {
    use epoll;                       # 事件模型(Linux推荐epoll)
    worker_connections 10240;        # 每个Worker最大并发连接数
}

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

    log_format main '$remote_addr - [$time_local] "$request" $status $body_bytes_sent';
    access_log /var/log/nginx/access.log main;

    sendfile on;        # 零拷贝传输
    keepalive_timeout 65;
    gzip on;            # 响应压缩
    gzip_types text/plain text/css application/json application/javascript;

    include /etc/nginx/conf.d/*.conf;  # 引入虚拟主机配置
}

3.2 关键配置参数

参数含义推荐值
worker_processesWorker进程数auto
worker_connections每个Worker最大连接数10240
keepalive_timeout长连接超时(秒)65
sendfile零拷贝传输on
gzip响应压缩on

互动提问:如果服务器有8核CPU,worker_processes 设多少最合适?为什么不能设太大?
(答:设8或auto。设太大会导致进程切换开销增加,反而降低性能。)


四、虚拟主机(Server块)

4.1 为什么需要虚拟主机?

问题场景:一台服务器只有一个公网IP,但要托管 www.school-a.comwww.school-b.com 两个网站。

解决方案:Nginx通过虚拟主机,根据请求的域名(Host头)将流量路由到不同的网站目录。

生活类比:一栋大楼有多个门牌号,快递员根据门牌号把包裹送到不同的公司——虽然都在同一栋楼里。

4.2 配置多个虚拟主机

网站A配置/etc/nginx/conf.d/school-a.conf):

server {
    listen 80;
    server_name www.school-a.com school-a.com;
    root /var/www/school-a;
    index index.html index.htm;
    access_log /var/log/nginx/school-a.access.log main;

    location / {
        try_files $uri $uri/ =404;
    }
}

网站B配置/etc/nginx/conf.d/school-b.conf):

server {
    listen 80;
    server_name www.school-b.com school-b.com;
    root /var/www/school-b;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # 静态资源缓存30天
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
    }
}
# 创建目录、放入测试页面、设置权限
mkdir -p /var/www/school-a /var/www/school-b
echo "<h1>Welcome to School A</h1>" > /var/www/school-a/index.html
echo "<h1>Welcome to School B</h1>" > /var/www/school-b/index.html
chown -R nginx:nginx /var/www/
nginx -t && nginx -s reload

五、反向代理(proxy_pass)

5.1 什么是反向代理?

问题场景:后端有Tomcat(Java应用)、Node.js等多个服务,它们分别运行在不同的端口上。但用户访问时只输入域名,不可能记住端口号。

解决方案:Nginx作为反向代理,统一监听80/443端口,根据URL路径把请求转发到对应的后端服务。

生活类比:反向代理 = 公司前台。来访者只认识公司总机号码(80端口),前台根据来访目的(URL路径)转接到不同部门(后端服务)。

用户浏览器 ──→ Nginx(:80) ──→ /api/   → Tomcat(:8080)
                              /admin/ → Node.js(:3000)
                              /       → 静态文件

5.2 反向代理配置

server {
    listen 80;
    server_name www.example.com;

    # 转发给Tomcat
    location /app/ {
        proxy_pass http://127.0.0.1:8080/;
        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;
    }

    # 静态文件直接由Nginx处理
    location /static/ {
        alias /var/www/static/;
        expires 7d;
    }
}

关键headerHost(传递域名)、X-Real-IP(真实客户端IP)、X-Forwarded-For(代理链IP)、X-Forwarded-Proto(原始协议)。


六、负载均衡

6.1 为什么需要负载均衡?

问题场景:一台Tomcat服务器扛不住每秒1万次的请求,怎么办?

解决方案:部署多台Tomcat服务器,用Nginx把请求均匀分配给它们。

生活类比:银行有5个窗口,大堂经理(Nginx)引导客户到空闲的窗口办理业务,避免所有人挤在一个窗口。

6.2 负载均衡策略

策略1:轮询(Round Robin)——默认

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

请求依次分配给每台服务器:1→2→3→1→2→3...

策略2:权重(Weight)

upstream backend {
    server 192.168.1.10:8080 weight=5;   # 性能好的多分配
    server 192.168.1.11:8080 weight=3;
    server 192.168.1.12:8080 weight=2;
}

性能强的服务器承担更多请求。

策略3:IP哈希(ip_hash)

upstream backend {
    ip_hash;   # 同一IP始终分配到同一后端
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

解决session问题——同一个用户的请求始终到同一台后端。

策略4:最少连接(least_conn)

upstream backend {
    least_conn;  # 分配给当前连接数最少的服务器
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

策略5:fair(需第三方模块)——按后端响应时间分配,响应快的多分配。

6.3 健康检查与故障转移

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;  # 备用服务器
}
  • max_fails=3:失败3次后标记为宕机
  • fail_timeout=30s:30秒后重试
  • backup:仅在所有主服务器不可用时启用

互动提问:如果3台后端服务器性能不同(4核/8核/16核),应该用什么负载均衡策略?
(答:使用weight权重策略,按性能比例分配,如 weight=2/4/8。)


七、Rewrite规则

7.1 为什么需要URL重写?

问题场景

  • 老网站改版后URL变了,旧链接访问会404
  • 需要把HTTP自动跳转到HTTPS
  • 需要美化URL(去掉.php/.jsp等后缀)

解决方案:Nginx的rewrite模块可以在请求到达location之前或之后修改URL。

7.2 Rewrite常用指令

# HTTP强制跳转HTTPS(最常用)
server {
    listen 80;
    server_name www.example.com;
    return 301 https://$host$request_uri;
}

# 旧URL重定向
rewrite ^/old-page\.html$ /new-page.html permanent;    # 301永久
rewrite ^/temp\.html$ /new-temp.html redirect;          # 302临时

# 伪静态
rewrite ^/article/(\d+)$ /article.php?id=$1 last;

rewrite flag说明

Flag含义
last内部重写,重新匹配location
break内部重写,继续当前location
redirect302临时重定向
permanent301永久重定向

八、日志管理

8.1 日志格式自定义

# 定义详细的日志格式(在http块中)
log_format detailed '$remote_addr - [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time';

# 使用自定义格式
server {
    access_log /var/log/nginx/access.log detailed;
}

常用日志变量$remote_addr(客户端IP)、$request(请求行)、$status(状态码)、$request_time(响应时间)、$upstream_response_time(后端响应时间)。

8.2 日志切割

Nginx不会自动切割日志,使用logrotate配置(/etc/logrotate.d/nginx):

/var/log/nginx/*.log {
    daily
    rotate 30          # 保留30天
    compress           # 压缩旧日志
    missingok
    notifempty
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

九、SSL/HTTPS配置

9.1 为什么需要HTTPS?

问题场景:HTTP是明文传输,用户名密码在传输过程中可以被中间人窃取。

解决方案:配置SSL证书,启用HTTPS加密传输。

9.2 SSL配置实战

server {
    listen 443 ssl;
    server_name www.example.com;

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_session_cache shared:SSL:10m;

    add_header Strict-Transport-Security "max-age=31536000" always;

    root /var/www/html;
    index index.html;
    location / { try_files $uri $uri/ =404; }
}

# HTTP自动跳转HTTPS
server {
    listen 80;
    server_name www.example.com;
    return 301 https://$host$request_uri;
}

生成自签名证书(测试用)

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/nginx/ssl/server.key -out /etc/nginx/ssl/server.crt \
    -subj "/CN=www.example.com/O=MyOrg/C=CN"

注意:生产环境应使用Let's Encrypt免费证书。安装certbot后一条命令搞定:

# RHEL: yum install certbot python3-certbot-nginx -y
# Debian: apt install certbot python3-certbot-nginx -y
certbot --nginx -d www.example.com

十、LNMP架构

LNMP = Linux + Nginx + MySQL + PHP,是一套经典的Web服务架构。

用户浏览器 → Nginx(静态文件直接处理,动态请求转发)→ PHP-FPM → MySQL

Nginx + PHP-FPM 核心配置

server {
    listen 80;
    server_name www.phpsite.com;
    root /var/www/phpsite;
    index index.php index.html;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

安装PHP-FPM

操作RHEL系Debian系
安装yum install php-fpm php-mysqlnd -yapt install php-fpm php-mysql -y
启动systemctl start php-fpmsystemctl start php8.1-fpm

十一、Tomcat入门

11.1 Tomcat是什么?

Tomcat是Apache基金会开发的 Java Web容器(Servlet容器),用于运行Java Web应用(JSP、Servlet)。

生活类比:如果Nginx是"餐厅前台",那Tomcat就是"后厨"——专门处理需要"现做"的动态菜品(Java应用)。

11.2 安装JDK和Tomcat

安装JDK

操作RHEL系Debian系
安装JDK 11yum install java-11-openjdk java-11-openjdk-devel -yapt install openjdk-11-jdk -y
安装JDK 17yum install java-17-openjdk java-17-openjdk-devel -yapt install openjdk-17-jdk -y
查看版本java -versionjava -version
设置JAVA_HOMEexport JAVA_HOME=/usr/lib/jvm/java-11-openjdkexport JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

安装Tomcat(两个系统通用步骤):

# 下载并解压Tomcat(以9.0为例)
cd /opt
wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.80/bin/apache-tomcat-9.0.80.tar.gz
tar -xzf apache-tomcat-9.0.80.tar.gz && ln -s apache-tomcat-9.0.80 tomcat

# 创建tomcat用户(安全实践,不用root运行)
useradd -r -s /sbin/nologin tomcat
chown -R tomcat:tomcat /opt/tomcat

# 设置环境变量(/etc/profile.d/tomcat.sh)
export CATALINA_HOME=/opt/tomcat
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk   # Debian系用 java-11-openjdk-amd64

# 创建systemd服务文件 /etc/systemd/system/tomcat.service
# [Service] 段关键配置:
# Type=forking  User=tomcat
# Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk"
# Environment="CATALINA_HOME=/opt/tomcat"
# ExecStart=/opt/tomcat/bin/startup.sh
# ExecStop=/opt/tomcat/bin/shutdown.sh

# 启动
systemctl daemon-reload && systemctl enable --now tomcat

11.3 Tomcat目录结构

目录说明
bin/启动/停止脚本
conf/配置文件(server.xml等)
logs/日志(catalina.out等)
webapps/Web应用部署目录
work/JSP编译缓存

十二、server.xml配置详解

12.1 核心配置结构

<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <!-- 连接器:定义Tomcat监听的端口和协议 -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               maxThreads="200" minSpareThreads="25"
               acceptCount="100" URIEncoding="UTF-8" />

    <Engine name="Catalina" defaultHost="localhost">
      <!-- 虚拟主机 -->
      <Host name="localhost" appBase="webapps"
            unpackWARs="true" autoDeploy="true" />
    </Engine>
  </Service>
</Server>

层级关系:Server → Service → Connector + Engine → Host → Context

12.2 Connector关键参数

参数含义推荐值
port监听端口8080
maxThreads最大线程数200~500
acceptCount队列等待数100
connectionTimeout连接超时(ms)20000
URIEncodingURL编码UTF-8

12.3 配置虚拟主机(Host)

<Engine> 内添加新的 <Host> 节点:

<Host name="www.myapp.com" appBase="/data/myapp"
      unpackWARs="true" autoDeploy="true">
    <Context path="" docBase="/data/myapp/ROOT" reloadable="true" />
</Host>

十三、部署WAR包

13.1 部署步骤

# 方法1:直接复制到webapps(最简单,Tomcat自动解压)
cp myapp.war /opt/tomcat/webapps/

# 方法2:部署为ROOT应用(访问根路径)
rm -rf /opt/tomcat/webapps/ROOT
cp myapp.war /opt/tomcat/webapps/ROOT.war

# 验证部署
tail -f /opt/tomcat/logs/catalina.out    # 查看启动日志
curl -I http://localhost:8080/myapp/     # 测试访问

十四、多实例部署(CATALINA_BASE)

14.1 为什么需要多实例?

问题场景:一台服务器上需要运行多个不同的Java应用,每个应用需要独立的配置、日志和webapps目录。

解决方案:使用 CATALINA_BASE 实现多实例,共享同一份Tomcat程序文件(CATALINA_HOME),但各自有独立的运行目录。

# 创建实例目录并复制配置
mkdir -p /opt/tomcat-instance1/{conf,logs,webapps,temp,work}
mkdir -p /opt/tomcat-instance2/{conf,logs,webapps,temp,work}
cp /opt/tomcat/conf/* /opt/tomcat-instance1/conf/
cp /opt/tomcat/conf/* /opt/tomcat-instance2/conf/

# 修改各实例端口避免冲突
# instance1: Server=8006, Connector=8081
# instance2: Server=8007, Connector=8082

chown -R tomcat:tomcat /opt/tomcat-instance1 /opt/tomcat-instance2

创建systemd服务(以instance1为例):

# /etc/systemd/system/tomcat-instance1.service
[Service]
Type=forking
User=tomcat
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/opt/tomcat-instance1"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
systemctl daemon-reload
systemctl start tomcat-instance1
systemctl start tomcat-instance2

十五、Nginx + Tomcat集群

15.1 整体架构

                    ┌──→ Tomcat-1 (:8080) ──→ MySQL主库
用户 ──→ Nginx ──┤──→ Tomcat-2 (:8080) ──→ MySQL从库
                    └──→ Tomcat-3 (:8080) ──→ Redis(Session共享)

15.2 Nginx负载均衡配置

upstream tomcat_cluster {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;
    keepalive 32;
}

server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://tomcat_cluster;
        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_http_version 1.1;
        proxy_set_header Connection "";
    }

    # 静态文件由Nginx处理
    location ~* \.(html|css|js|jpg|png|gif|ico)$ {
        root /var/www/static;
        expires 7d;
    }
}

15.3 Session管理方案

问题:用户登录Tomcat-1后,下次请求被分配到Tomcat-2,Session丢失。

方案原理优缺点
ip_hash同一IP始终到同一Tomcat简单但故障时session丢失
Session复制Tomcat之间同步Session占用带宽,不适合大集群
Redis共享Session所有Tomcat读写Redis推荐方案,高可用
JWT Token无状态认证适合API,不适合传统Web

互动提问:如果不用Redis,只用ip_hash策略来保持Session,会有什么风险?
(答:当某台Tomcat宕机时,该服务器上所有用户的Session会丢失。Redis共享Session不受此影响。)


十六、常见故障排查

16.1 Nginx常见故障

问题可能原因排查方法
403 Forbidden权限不足或SELinux阻止ls -la 检查权限,getenforce 检查SELinux
502 Bad Gateway后端服务未启动或崩溃检查Tomcat/PHP-FPM状态和日志
504 Gateway Timeout后端响应超时增加 proxy_read_timeout 或优化后端
配置不生效忘记重载或语法错误nginx -t 测试 + nginx -s reload
SSL证书错误证书路径错误或过期openssl x509 -in cert.pem -noout -dates

Nginx排障命令

nginx -t                           # 测试配置文件语法
ps aux | grep nginx                # 查看Nginx进程
tail -100 /var/log/nginx/error.log # 查看错误日志
curl -I http://localhost/          # 测试是否正常响应
nginx -V                           # 查看编译参数(排查模块问题)

16.2 Tomcat常见故障

问题可能原因排查方法
启动失败端口被占用或JAVA_HOME未设置`ss -tlnp
页面404WAR包未正确部署检查webapps目录和catalina.out日志
内存溢出(OutOfMemory)JVM堆内存不足增加-Xmx参数
响应缓慢线程池不足或GC频繁调整maxThreads,分析GC日志
无法关闭shutdown端口被改或进程卡死kill -9 PID(最后手段)

Tomcat排障与调优

tail -200 /opt/tomcat/logs/catalina.out  # 查看Tomcat日志
ss -tlnp | grep 8080                     # 检查端口占用
jps -l                                   # 查看Java进程
jstack PID                               # 查看线程栈(排查卡死问题)

# 增加JVM内存(编辑 /opt/tomcat/bin/setenv.sh)
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx1024m -XX:+UseG1GC"

16.3 综合排障流程

网站打不开 → ping服务器 → 不通 → 检查防火墙/路由
            → ping通
            → curl测试 → 无响应 → systemctl status nginx
            → 有响应
            → 502 → Tomcat挂了(systemctl status tomcat)
            → 504 → 后端超时(查看Tomcat负载和日志)
            → 403 → 权限/SELinux问题
            → 200但空白 → 应用bug(查应用日志)

附录A:运维常用命令精选

命令用途示例
nginx -t测试配置语法nginx -t
nginx -s reload重载配置nginx -s reload
nginx -V查看版本和编译参数nginx -V
systemctl start tomcat启动Tomcatsystemctl start tomcat
tail -f catalina.out实时查看Tomcat日志tail -f /opt/tomcat/logs/catalina.out
curl -I URL测试HTTP响应头curl -I http://localhost:8080/
ss -tlnp检查端口监听状态`ss -tlnp
java -version查看JDK版本java -version
jps -l查看Java进程jps -l
jstack PID查看Java线程栈jstack 12345
abHTTP压力测试ab -n 1000 -c 100 http://localhost/

附录B:趣味命令

# 1. 用一行命令启动一个简易静态网站(适合临时分享文件)
python3 -m http.server 8888
# 然后浏览器访问 http://你的IP:8888

# 2. 查看Nginx处理了多少个请求(需要配置stub_status)
curl -s http://localhost/nginx_status

# 3. 实时查看访问量最高的IP(Top 10)
watch -n 5 'awk "{print \$1}" /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10'

# 4. 看看Nginx承受了多少个并发连接
ss -tn | grep ':80 ' | wc -l

# 5. 一键查看Tomcat的JVM内存使用情况
jmap -heap $(pgrep -f 'catalina')

# 6. 快速生成测试用SSL证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout test.key -out test.crt -subj "/CN=test.local"

# 7. 用Apache Bench做简单压力测试
# RHEL: yum install httpd-tools  |  Debian: apt install apache2-utils
ab -n 1000 -c 50 http://localhost/

# 8. 监控Nginx错误日志,出现错误时高亮提醒
tail -f /var/log/nginx/error.log | grep --color=always -E "error|crit|alert|emerg|$"

# 9. 查看Tomcat部署了哪些应用
ls -la /opt/tomcat/webapps/ | grep "^d"

# 10. 测试Nginx响应速度(10次取平均)
for i in $(seq 1 10); do curl -o /dev/null -s -w "%{time_total}\n" http://localhost/; done | awk '{sum+=$1} END {print "平均:", sum/NR, "秒"}'

小结:Nginx以其事件驱动的高并发能力,成为Web服务的"前台接待";Tomcat作为Java Web容器,承担动态内容的处理。两者配合负载均衡、SSL终结、反向代理等能力,构成了企业级Web服务的基础架构。掌握Nginx+Tomcat的部署和调优,是运维工程师的核心技能之一。


Comment