本文包含 Nginx 部署 Web 应用、Docker 部署 Web 应用、Jenkins 、CI/CD 部署 Web 应用 等内容。快速使用也可以。深入了解
需要了解 NodeJs 知识:NodeJs 环境配置。
需要了解 Docker 知识:Docker 相关知识。
需要了解 Jenkins 知识:Jenkins 相关知识。
注意:此处部署是在 Ubuntu 操作系统中进行。如果安装慢,可以修改一下镜像源。
目前主流框架的渲染方式有: SPA 客户端渲染;SSR 服务端渲染
# 知识梳理
- nginx 中 location 后面的值最后增加
/和 proxy_pass 值后面携带/会在代理时移除代理标识,否则反之。 - 关于 nginx 中 try_files 和 重定向 return 与 rewrite 的关系问题,重定向尽量在 location 中写。
| 指令 | 是否终止处理 | 是否改变 URI | 是否影响浏览器地址 | 执行时机 | URL 编码(# -> %23) | 说明 |
|---|---|---|---|---|---|---|
| return | ✅ 是(立即) | ❌ 否(但可跳转) | ✅ 是(如果是 3xx) | 早 | ❌否 | 重写 URL,立即返回, 不会被 URL 编码 # -> %23 |
| rewrite ... last | ❌ 否 | ✅ 是(内部) | ❌ 否 | 早 | ✅是 | 重写 URL,然后重新进入 try_files |
| rewrite ... break | ❌ 否 | ✅ 是(内部) | ❌ 否 | 早 | ✅是 | 重写 URI,但不再执行后续 rewrite |
| rewrite ... permanent | ✅ 是 | ✅ 是 | ✅ 是 | 早 | ✅是 | 返回 301,终止处理,不进 try_files |
| try_files | ❌ 否(但处理完请求) | ✅ 是(内部跳转) | ❌ 否 | 中 | ❌否 | 它是内部处理,地址不变 |
# Linux 系统
# Nginx 部署
Nginx 部署 Web 项目时,如多个项目使用 443 端口,只需要配置多个 server 并且使用不同的 server_name 即可。
# SPA 应用部署
# 安装 Nginx
sudo apt update | |
sudo apt install nginx |
# 启动 Nginx
sudo systemctl status nginx | |
or | |
service nginx start |
# 测试 Nginx
# 浏览器查看
浏览器访问 服务器 IP,就可以看到 Nginx 的页面。注意要在防火墙开通 80 端口。
# 命令查看
sudo systemctl status nginx | |
or | |
service nignx status |
# 配置 Nginx
此时,防火墙要开通将要使用的端口。
server { | |
# listen 80; # 端口自定义 | |
# listen [::]:80; | |
listen 443 ssl http2; # 有证书时使用 | |
listen [::]:443; | |
gzip on; | |
gzip_types text/plain application/x-javascript application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; | |
server_name [xxxx.com 或者 _]; # 域名 | |
root /项目文件夹路径; # 项目文件夹 | |
index index.html; # 项目 index 页 | |
# SSL 证书配置 有证书就配置,无证书就不配置 | |
ssl_certificate ./cert-file-name.pem; | |
#需要将 cert-file-name.pem 替换成已上传的证书文件的名称。 | |
ssl_certificate_key ./cert-file-privkey.pem; | |
#需要将 cert-file-privkey.key 替换成已上传的证书私钥文件的名称。 | |
ssl_session_timeout 5m; | |
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; | |
#表示使用的加密套件的类型。 | |
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; | |
#表示使用的 TLS 协议的类型。 | |
ssl_prefer_server_ciphers on; | |
# 后端部分 反向代理 | |
location /xxx/ { | |
proxy_next_upstream http_500 http_502 http_503 http_504 error timeout invalid_header; | |
proxy_set_header Host $host; | |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
# 请求后端的代理地址服务器的 IP | |
proxy_pass http://IP:PORT/; | |
# Docker 服务端在宿主机中时 ip 是 ifconfig 命令输出中的 docker0-inet/eth0-inet 的数据,通过宿主机 / 网桥进行通信。 | |
# proxy_pass http://IP:PORT/; | |
# Docker 服务端和前端容器在同一个网络,则使用此格式。容器之间的通信。 | |
# proxy_pass CONTAINER NAME:PORT/; | |
expires 0; | |
} | |
#反向代理可以有多个 | |
# 前端部分 内部重定向 | |
location / { | |
add_header Cache-Control "no-store, no-cache"; #用于控制服务端更新,用户浏览器不同步问题 | |
add_header Pragma no-cache; | |
try_files $uri $uri/ /index.html; | |
} | |
} |
# 检测配置
nginx -t |
# 重启 Nginx
# 方式一
sudo systemctl restart nginx | |
or | |
sudo systemctl reload nginx | |
or | |
service nginx restart |
# 方式二
sudo systemctl stop nginx | |
sudo systemctl start nginx | |
or | |
service nginx stop | |
service nginx start |
# SSR 应用部署
暂时没有尝试,使用后再补充。
# Docker 部署
建议使用一个 Nginx 容器部署多个项目,多容器会出现端口冲突情况如 443 端口。
# SPA 应用部署
# 配置文件部署方式(推荐)
要注意路径关系,我通常会把配置文件和项目文件放在同一个文件夹下。
# 构建 Dockerfile
构建基础镜像
FROM nginx |
# 构建 compose.yml
version: '3' | |
services: | |
frontend: # 服务名称,用于在 Docker Compose 中标识该服务。(可自定义) | |
build: . # 指定 Dockerfile 的路径 | |
container_name: [CONTAINER NAME] # 自定义容器名字 | |
image: [IMAGE NAME:TAG] # 自定义镜像名称:标签 | |
ports: # 注意:需要保持 Nginx 配置文件中的端口修改为与容器映射的端口一致。 | |
- "443:443" # "宿主机端口号:容器端口号" 有证书就用 | |
- "80:80" # "宿主机端口号:容器端口号" | |
volumes: | |
- 宿主机项目资源文件夹路径:容器项目资源文件夹路径(可自定义) # 容器内和宿主机内挂载的项目文件资源会同步变化 | |
- 宿主机Nginx资源文件路径:容器Nginx资源文件路径(可自定义) # 容器内和宿主机内挂载的项目文件资源会同步变化 | |
- 宿主机SSL资源文件夹路径:容器SSL资源文件夹路径(可自定义) # 容器内和宿主机内挂载的项目文件资源会同步变化 有证书就用 | |
restart: unless-stopped # 容器自动重启 |
注意:挂载证书的时候,证书文件一定不可有符号链接。有符号链接会导致容器内无法访问证书文件。可以通过命令
ls -l查看文件是否存在符号链接。 符号链接 Eg:cert.pem -> ../../archive/www.expamle.com/cert.pem。
解决方案: 将证书文件复制到项目文件夹中,然后挂载到容器中。
# 构建 Nginx 配置文件
配置文件如上所示。
# 构建 自定义 Image 和 容器
# 构建并后台启动 | |
docker compose up -d | |
# 构建并启动 | |
docker compose up |
命令应在项目文件夹中执行
此时,浏览器已经可以访问了。
# 逐步部署方式
# 准备工作
# 上传项目文件到服务器
主机中创建项目文件夹,将打包后的应用上传到项目文件夹中或者上传源码再进行应用构建。两种方式均可。由于,本文主要讲解部署,就不过多描述此步骤了。
注意:这里要包含 Nginx 配置文件,如果未包含,则需要手动创建一个 Nginx 配置文件再上传到项目文件夹。
# Nginx 配置文件
这个文件可以创建,也可以直接使用 Nginx 中已经配置好的文件。但是,如果容器映射了 nginx 配置,那么系统中的 nginx 和 docker Nginx 镜像的的配置就不能有重复的端口,否则会出现冲突。
配置文件如上所示。
# 安装 Nginx 镜像
docker pull nginx |
# 方式一
创建容器并运行。
docker run -itd -p [CONTAINER PORT:宿主机 PORT] --name [CONTAINER NAME] -v "宿主机项目文件路径:容器中挂载项目文件路径" -v "宿主机Nginx配置文件路径:容器中挂载Nginx配置文件路径" [CONTAINER NAME/ID] /bin/bash |
命令参数说明:
- -i: 交互式操作。
- -t: 终端。
- -d: 设置容器在在后台一直运行。
- -p: 端口进行映射,将本地 8080 端口映射到容器内部的 80 端口。
- -v: 将本地的文件目录映射到容器内的,映射文件会更具主机项目资源变化发生变化的
- --name: 设置容器名字.
- nginx: nginx 镜像。
- /bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。
# 方式二
逐步创建容器;进入容器;启动程序。
# 创建容器
docker create [CONTAINER NAME/ID] | |
# 此时不可以使用 | |
docker start [CONTAINER NAME/ID] | |
docker create -itd -p 容器端口:项目端口 --name [CONTAINER NAME/ID] -v "宿主机项目文件路径:容器中挂载项目文件路径" -v "宿主机Nginx配置文件路径:容器中挂载Nginx配置文件路径" [CONTAINER NAME/ID] /bin/bash | |
# 此时可以使用 | |
docker start [CONTAINER NAME/ID] |
# 进入容器
docker exec -it [CONTAINER NAME/ID] /bin/bash |
# 启动 Nginx
# 启动 Nginx | |
service nginx start | |
# 停止 Nginx | |
service nginx stop | |
# 重启 Nginx | |
service nginx restart |
此时,浏览器已经可以访问了。
# SSR 应用部署
暂时没有尝试,使用后再补充。
# 宿主机、容器之间网络关系
前端容器 nginx 配置文件中代理地址问题。
- 前端容器 (A) - 宿主机服务端:此时,可以使用的代理地址有,
ifconfig查询网络信息。- 其中,docker0 的 inet 的值 + 端口号。
数据流动:浏览器 -> 宿主机 -> 容器A -> 宿主机 -> 宿主机服务端 -> 宿主机 -> 容器A -> 浏览器; - 或者,eth0 的 inet 的值 + 端口号。数据流动。
浏览器 -> 宿主机 -> 容器A -> 宿主机 -> 宿主机服务端 -> 宿主机 -> 容器A -> 浏览器; - 注意:此时宿主机的防火墙要开启对应端口。
- 也可以将容器的网络模式
network_mode: host, 就可以通过localhost+端口号进行访问。数据流动:浏览器 -> 宿主机 -> 容器A -> 宿主机 -> 宿主机服务端 -> 宿主机 -> 容器A -> 浏览器;
- 其中,docker0 的 inet 的值 + 端口号。
- 前端容器 (A) - 服务端容器 (B):此时,有 3 种处理方式:
- 情况一:默认所有容器都在 docker0 网桥下,所以,代理地址可以使用
docker0的inet的值+端口号或者服务端容器名称+端口号进行访问。(理论上是可行的)。数据流动:浏览器 -> 宿主机 -> 容器A -> 容器B -> 容器A -> 浏览器; - 情况二:两个容器不在同一个网络中,需要服务端容器映射端口,代理地址只能使用
服务器IP+端口号。数据流动:浏览器 -> 宿主机 -> 容器A -> 宿主机 -> 容器B -> 宿主机 -> 容器A -> 浏览器; - 情况三:自定义一个网络,将容器添加到同一个网络下,代理地址使用
服务端容器名称/容器IP+端口号,此时,如果服务不对外,可以不开发对应端口的防火墙。数据流动:浏览器 -> 宿主机 -> 容器A -> 容器B -> 容器A -> 浏览器;
- 情况一:默认所有容器都在 docker0 网桥下,所以,代理地址可以使用
# 部署 WEB 相关 Docker 命令
以下命令为此过程常用命令,无先后顺序。前提条件是已经安装了 Docker。
# 查找 Docker Nginx 镜像 | |
docker search nginx | |
# 安装 Docker Nginx 镜像 | |
docker pull nginx | |
# 查看镜像列表 | |
docker images | |
# 删除镜像 | |
docker rmi 镜像名/镜像ID | |
# 查看正在运行的容器 | |
docker ps | |
# 查看所有容器 | |
docker ps -a | |
# 删除容器 | |
docker rm [CONTAINER NAME/ID] | |
# 启动停止的容器 | |
docker start [CONTAINER NAME/ID] | |
# 停止容器 | |
docker stop [CONTAINER NAME/ID] | |
# 重启容器 | |
docker restart [CONTAINER NAME/ID] | |
# 进入后台运行的容器 | |
docker exec -it [CONTAINER NAME/ID] /bin/bash | |
# 退出容器 | |
exit |
# Jenkins 部署 Web 应用
# SPA 应用自动化部署
# 创建自由风格 (Freestyle project)
本方案只描述如何从 Gitee 仓库 pull 代码;更新项目依赖;打包项目;至于项目部署请看 流水线 (Pipeline) 部分
- 新建项目;
- 填入项目名称;
- 选择自由风格 (Freestyle project);
- General 部分
- 填入项目描述;
- 选择 GitHub 项目 > 填入 GitHub 项目地址;
- 选择参数化构建过程(以下是我是用的参数)
- 布尔值参数 (install、build、deploy)
- 选项参数 (暂时没怎么用)
- 字符参数 (暂时没怎么用)
- 源码管理部分
- 选择 Git
- 添加仓库地址 (我使用的是 gitee 的仓库地址、GitHub 的仓库地址我没有使用成功)
- 添加用户凭证 (账号密码 或 密钥方式)
- Git 地址报错问题
- 系统管理 -> Git installations -> 填入 git 的路径 (可通过命令
whereis git查找 git 路径, 我这里的路径是 /usr/bin/git)
- 系统管理 -> Git installations -> 填入 git 的路径 (可通过命令
- Build Steps 部分
- 执行 shell
shell脚本内容 # !/bin/bashif ${install}; then
echo "执行 npm install";
npm install;
elseecho "不执行 npm install";
fiif ${build}; then
echo "执行 npm run build";
npm run build;
elseecho "不执行 npm run build";
fi - 应用
- 构建
# 创建流水线 (Pipeline)
- 新建项目;
- 填入项目名称;
- 选择流水线 (Pipeline);
- General 部分
- 填写项目描述
- GitHub 项目填写仓库 SSH 地址
- 参数化构建过程(建议与 pipeline 中 parameters 的变量名一致,方可同步数据变化,方便脚本中使用)
- 布尔值参数 PULL 更新代码
- 布尔值参数 INSTALL 更新依赖
- 布尔值参数 BULID 项目打包
- 布尔值参数 DEPLOY 项目部署
- 选项参数 BRANCH master 选择代码分支
- 选项参数 ENVIRONMENT production test 选择部署环境
- 选项参数 DEPLOYTARGET local remote 选择部署目标
- 流水线部分
- 定义 Pipeline script
pipeline { | |
agent any | |
parameters { | |
# 定义参数 | |
choice(name: 'DEPLOYTARGET', choices: ['local', 'remote'], description: '选择部署目标') # 选择参数 | |
choice(name: 'BRANCH', choices: ['master'], description: '选择代码分支') # 选择参数 | |
choice(name: 'ENVIRONMENT', choices: ['production', 'test'], description: '选择部署环境') # 选择参数 | |
booleanParam(name: 'PULL', defaultValue: true, description: '更新代码') # 布尔参数 | |
booleanParam(name: 'INSTALL', defaultValue: true, description: '更新依赖') | |
booleanParam(name: 'BULID', defaultValue: true, description: '项目打包') | |
booleanParam(name: 'DEPLOY', defaultValue: true, description: '项目部署') | |
# password (name: 'SECRET_KEY', defaultValue: '', description: 'Enter a secret key') # 密码参数 | |
# run (name: 'PREVIOUS_BUILD', description: 'Select a previous build', project: 'my-job') # 运行参数 | |
# file (name: 'UPLOAD_FILE', description: 'Select a file to upload') # 文件参数 | |
# text (name: 'CONFIG_FILE', defaultValue: '', description: 'Enter configuration as text') # 文本参数 | |
# string (name: 'ENVIRONMENT', defaultValue: 'dev', description: 'Specify the environment') # 字符串参数 | |
} | |
stages { | |
stage('PULL') { | |
steps { | |
script { | |
if (params.PULL){ | |
# git credentialsId: 'Git', url: 'https://gitee.com/xxx.git' # 默认 master 分支 | |
git branch: params.BRANCH, credentialsId: 'Git', url: 'https://gitee.com/xxx.git' # 指定分支 | |
}else{ | |
echo "This is PULL value ${params.PULL}" | |
} | |
} | |
} | |
} | |
stage('INSTALL') { | |
steps { | |
script { | |
if (params.INSTALL){ | |
sh "npm install" | |
}else{ | |
echo "This is INSTALL value ${params.INSTALL}" | |
} | |
} | |
} | |
} | |
stage('BULID') { | |
steps { | |
script { | |
if (params.BULID){ | |
switch(params.ENVIRONMENT){ | |
case "production": | |
sh "npm run build" # 自己手动添加 mode 即可,我通常是使用 webpack 或 vite | |
break; | |
default: | |
# test | |
sh "npm run build" # 自己手动添加 mode 即可,我通常是使用 webpack 或 vite | |
} | |
}else{ | |
echo "This is BULID value ${params.BULID}" | |
} | |
} | |
} | |
} | |
stage('DEPLOY') { | |
steps { | |
script { | |
if (params.DEPLOY){ | |
switch(params.DEPLOYTARGET){ | |
case "local": | |
echo "部署到本地服务器"; | |
sh 'rm -rf /var/www/frontend/xxx/*' | |
sh 'mv /var/lib/jenkins/workspace/xxx/dist/* /var/www/frontend/xxx/' | |
break; | |
default: | |
# remote | |
echo "部署到远程服务器"; | |
# sh 'ls /var/lib/jenkins/workspace/xxx/dist/**' | |
sshPublisher( | |
publishers: [ | |
sshPublisherDesc( | |
configName: '这里填写SSH Servers中的Name', | |
transfers: [ | |
sshTransfer( | |
cleanRemote: false, | |
excludes: '', | |
execCommand: 'sudo chmod -R 777 /var/www/frontend/xxx/ && rm -rf /var/www/frontend/xxx/assets && rm -rf /var/www/frontend/xxx/index.html && rm -rf /var/www/frontend/xxx/favicon.ico && sudo mv /var/www/frontend/xxx/dist/* /var/www/frontend/xxx && sudo rm -rf /var/www/frontend/xxx/dist', | |
execTimeout: 120000, | |
flatten: false, | |
makeEmptyDirs: false, | |
noDefaultExcludes: false, | |
patternSeparator: '[, ]+', | |
sourceFiles: 'dist/**', # 源文件 | |
# removePrefix: 'dist/**', # 移除源文件夹 | |
remoteDirectory: '/', # 远程目标文件夹 | |
# remoteDirectorySDF: false, | |
) | |
], | |
usePromotionTimestamp: false, | |
useWorkspaceInPromotion: false, | |
verbose: true # 打印 log | |
) | |
] | |
) | |
echo 'Credentials SUCCESS' | |
# sshServer: [ | |
# sshServerInfo( | |
# host: 'IP 地址或主机名 ', # 远程服务器的 IP 地址或主机名 | |
# port: 22 # SSH 端口号,如果不是默认端口 (22), 请修改 | |
# ) | |
# ] | |
} | |
}else{ | |
echo "This is DEPLOY value ${params.DEPLOY}" | |
} | |
} | |
} | |
} | |
} | |
} |
- 应用
- 构建
# 部署目标 (DEPLOYTARGET)
# 部署到当前服务器 (local)
解决方案如下:
- 方案一 SSH
ssh root@IP地址 | |
root password | |
sudo chmod -R 777 /var/www/frontend/xxx/ # 也要设置文件权限 | |
rm -rf /var/www/frontend/xxx/* | |
mv /var/lib/jenkins/workspace/xxx/dist/* /var/www/frontend/xxx/ |
方案二 更改 Jenkins 权限
部署到本地服务器,我是需要移动打包好的项目文件,所以 jenkins 用户出现权限不足的问题。(私人服务器就随便了,不过还是更加推荐远程部署方式)Ubuntu 命令
查看用户列表
cat /etc/passwd | cut -d : -f1查看目标文件夹的所属组
ls -ld /var/www/frontend/xxx/添加 Jenkins 用户到目标文件夹所属的组
sudo usermod -aG 组的名字 jenkins更改目标文件夹的权限,使得 Jenkins 用户有写入权限(不包含目录下的文件夹)
sudo chmod o+w /var/www/frontend/xxx/递归更改目标文件夹及其下所有文件和文件夹的权限(包含目录下的所有文件夹和文件)
sudo chmod -R 777 /var/www/frontend/xxx/
如果你想确保目标文件夹下的所有文件和文件夹都具有读写权限,可以使用以下命令:
递归更改目标文件夹及其下所有文件和文件夹的权限
sudo chmod -R 777 /var/www/frontend/xxx/
这里的 -R 选项表示递归,777 表示给予读写权限(4 表示读,2 表示写,1 表示执行,所以 4+2+1=7)。方案三 创建公开的文件夹
直接在更目录创建文件夹,不归属任何账户。直接移动打包好的文件即可。
# 部署到远程服务器 (remote)
- 安装 SSH 插件:
在 Jenkins 中,通过 "系统管理 (Manage System)" -> "插件管理 (Manage Plugins)" -> "Available plugins" 标签页找到并安装 "Publish Over SSH" 插件。 - 配置 SSH 插件:
在 Jenkins 中,通过 "系统管理 (Manage System)" -> "系统配置 (Configure System)",找到 "Publish over SSH" 部分 -> "SSH Servers",配置远程服务器的凭据。
凭据中的参数:- Name: 自己随便定义名字
- Hostname: 服务器 IP 地址
- Username: 服务器用户名
- Remote Directory: 远程服务器目标文件
- 点击高级,设置 Use password authentication, or use a different key 中的 Passphrase / Password
- 保存即可
- 修改 Jenkins Pipeline 脚本:
在 Jenkins Pipeline 中,使用 sshPublisher 步骤来执行远程命令,将打包好的项目传输到远程服务器。
# SSR 应用自动化部署
暂时没有尝试,使用后再补充。
# Window 系统
# 命令
# 查看 Nginx 本版 | |
nginx -v | |
# 重新加载配置 | |
nginx -s reload | |
# 检查 Nginx conf 的配置 | |
nginx -t | |
# 启动 Nginx | |
start nginx | |
# 强制停止 | |
nginx -s stop | |
# 优雅退出 | |
nginx -s quit | |
# kill nginx 进程 | |
taskkill /F /IM nginx |
# 下载 Nginx 压缩包
传送门
# 使用
将压缩包放在一个路径没有中文和空格的路径下
# 配置
这里需要注意的地方就是 worker_processes: auto 和 include xxx/*.conf
#user nobody; | |
worker_processes auto; #这里改成auto | |
#error_log logs/error.log; | |
#error_log logs/error.log notice; | |
#error_log logs/error.log info; | |
#pid logs/nginx.pid; | |
events { | |
worker_connections 1024; | |
} | |
http { | |
include mime.types; | |
default_type application/octet-stream; | |
#log_format main '$remote_addr - $remote_user [$time_local] "$request" ' | |
# '$status $body_bytes_sent "$http_referer" ' | |
# '"$http_user_agent" "$http_x_forwarded_for"'; | |
#access_log logs/access.log main; | |
sendfile on; | |
#tcp_nopush on; | |
#keepalive_timeout 0; | |
keepalive_timeout 65; | |
include xxx/*.conf; # 这里指定自定义 config,但是不要与 nginx.conf 在同一个层级下 | |
#gzip on; | |
server { | |
listen 80; | |
server_name localhost; | |
#charset koi8-r; | |
#access_log logs/host.access.log main; | |
location / { | |
root html; | |
index index.html index.htm; | |
} | |
#error_page 404 /404.html; | |
# redirect server error pages to the static page /50x.html | |
# | |
error_page 500 502 503 504 /50x.html; | |
location = /50x.html { | |
root html; | |
} | |
# proxy the PHP scripts to Apache listening on 127.0.0.1:80 | |
# | |
#location ~ \.php$ { | |
# proxy_pass http://127.0.0.1; | |
#} | |
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 | |
# | |
#location ~ \.php$ { | |
# root html; | |
# fastcgi_pass 127.0.0.1:9000; | |
# fastcgi_index index.php; | |
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; | |
# include fastcgi_params; | |
#} | |
# deny access to .htaccess files, if Apache's document root | |
# concurs with nginx's one | |
# | |
#location ~ /\.ht { | |
# deny all; | |
#} | |
} | |
} |
过去无法挽回,未来可以改变,有的人成日殚精竭虑,却掀不起什么风浪,有的人却因一念之差,让世界天翻地覆,这就是命运权重。