☰
Mac上安装配置Nginx部署前后端分离项目:以黑马苍穹外卖为例
2026/10/1 13:50:19 网站建设 项目流程

1. 为什么黑马苍穹外卖项目要在Mac上部署nginx

1.1 苍穹外卖的典型前后端分离架构

先把这个项目的底子摸清楚。黑马苍穹外卖是一套非常标准的Java全栈教学项目,后端基于Spring Boot开发,对外暴露的是一堆RESTful接口,端口默认走8080;前端则是Vue工程,本地开发时通过Vite或Webpack启一个Dev Server跑在5173或8081这种端口上。日常开发时前后端各跑各的,接口联调用代理插件转发一下就行,不会觉得缺了什么。

但一旦进入联调、演示、部署验证阶段,问题就来了。前端打包后的资源是纯静态文件(HTML、CSS、JS、图片),它需要一个Web服务器来托管;后端接口跑在8080端口,前端页面访问时如果直接写死http://localhost:8080,跨域问题、环境切换问题全都会冒出来。这时候nginx就是最顺手的那个中间层。

nginx在这个项目里实际干了三件事:第一,托管前端打包出来的静态文件;第二,把/api开头的请求反向代理到后端的Spring Boot服务;第三,统一入口端口,让你只需要记住一个地址(比如http://localhost)就能同时访问页面和接口。

1.2 nginx在项目里到底干了什么活

很多初学者容易把nginx想得太玄,其实它就是一台轻量级的Web服务器加反向代理服务器。在苍穹外卖这种项目里,它的角色可以理解成一个前台接待:

用户浏览器来了一个请求,如果是想看页面,nginx直接从磁盘上把index.html、app.js这些静态文件拿出来返回;如果是想查数据、下单、登录,请求路径里带了/api前缀,nginx就把这个请求转交给后面真正干活的Spring Boot服务,拿到结果后再原样送回给浏览器。

这样做最直接的好处有三个。第一是解决了跨域,因为浏览器访问的是http://localhost,请求后端也是通过http://localhost/api,同源策略轻松满足,不需要在后端代码里写一堆CORS配置。第二是隐藏了后端真实服务地址,外部只知道nginx的地址,不知道后面有几台后端服务、跑在什么端口。第三是将来项目上线以后,如果后端要多开几个实例扛流量,nginx前面配置一个负载均衡就能搞定,不需要动前端代码。

1.3 Mac作为开发部署环境的特殊性

Mac上部署nginx和Linux服务器上部署,大思路完全一样,但有几个细节差异必须单独说。

首先是安装方式,Linux上常见做法是apt install nginx或yum install nginx,Mac上最主流的是通过Homebrew安装,装完之后文件目录结构和Linux发行版有区别,这一点坑了不少人——照着网上Linux的教程改配置,结果路径压根对不上。

其次是权限问题,Mac的系统保护机制对/usr/local、/opt/homebrew这些目录有额外限制,如果操作不当,启动nginx时容易踩Permission denied的坑。

第三是端口占用,Mac上很多开发软件喜欢占80端口或8080端口,比如自带的Apache、其他开发服务器、Docker容器,经常导致nginx启动报address already in use。

另外还有一个很实际的点:很多人是在Mac上开发调试,最后把项目部署到云服务器上。如果在Mac上把整套流程跑通了,到了Linux服务器上无非就是换个安装命令的事,所以拿Mac当演练场,成本低、反馈快,特别适合把nginx这套东西吃透。

2. Mac上安装nginx的几种方式与选择

2.1 用Homebrew安装nginx

Mac上安装nginx,我实测下来最省心的就是Homebrew。如果你已经装好了brew,一条命令的事:

brew install nginx

装完之后先别急着用,先看一眼安装输出,里面会明确告诉你几个关键信息:配置文件在哪个路径、默认端口是多少、怎么通过brew services管理nginx。

这里要特别提醒一个点:Intel芯片Mac和Apple Silicon芯片Mac的路径前缀不一样。Apple Silicon(M1/M2/M3)上Homebrew默认装在/opt/homebrew,Intel机型上装在/usr/local。默认情况下,nginx的主配置路径是:

# Apple Silicon /opt/homebrew/etc/nginx/nginx.conf # Intel /usr/local/etc/nginx/nginx.conf

你可以顺手验证一下自己的brew前缀:

brew --prefix

拿到结果就明白自己该去哪个目录找配置文件了。

2.2 brew安装时的常见报错和解决办法

按照网上的教程装brew,很多人第一步就卡住了。比较常见的报错有这么几类。

第一类:Command Line Tools未安装。

报错信息里通常会出现xcode-select: error: command line tools are already installed或者提示找不到git、clang之类。解决办法是在终端执行:

xcode-select --install

系统会弹窗让你安装Xcode Command Line Tools,装完后重新执行brew安装命令。

第二类:下载速度极慢或者直接卡住。

brew安装时要从GitHub拉取目录仓库,网络情况不好的时候确实很折磨。解决办法是切换国内镜像源,中科大、清华、阿里云都有brew镜像。以中科大的方式为例,在终端设置环境变量后重新安装:

export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"

第三类:权限不足。

如果你是用普通用户安装,而/opt/homebrew目录的属主不是当前用户,会碰到Permission denied。最粗暴的解决办法是:

sudo chown -R $(whoami) /opt/homebrew

但这里我多说一句,如果你不清楚这台Mac的管理员情况,优先用sudo去执行brew命令,而不是大面积修改目录权限,避免影响系统其他功能。

2.3 验证安装结果

安装完成后,用下面几个命令确认nginx能不能正常跑起来:

nginx -v nginx -t brew services start nginx

nginx -v用来查看版本号,nginx -t用来测试配置语法,如果输出syntax is ok和test is successful,说明配置没问题。brew services start nginx会把nginx注册成后台服务,开机自启,适合长期使用;如果你只想临时跑一下,用nginx命令直接启动,不需要注册服务。

启动后在浏览器访问http://localhost:8080,看到Welcome to nginx的页面,说明安装成功了。这里默认端口是8080,不是Linux发行版常见的80端口,别搞混。

3. 搞懂nginx目录结构与核心配置

3.1 Mac上nginx的文件都放在哪了

nginx装好之后,有几个目录你最好心里有数:

用途Apple Silicon路径Intel路径
主配置/opt/homebrew/etc/nginx/nginx.conf/usr/local/etc/nginx/nginx.conf
服务器配置目录/opt/homebrew/etc/nginx/servers//usr/local/etc/nginx/servers/
默认网页根目录/opt/homebrew/var/www/usr/local/var/www
日志目录/opt/homebrew/var/log/nginx//usr/local/var/log/nginx/

实际使用中我不太建议把所有配置都塞进nginx.conf,写多了文件又长又乱,还容易误改。更好的习惯是:nginx.conf里只保留全局配置和http块的公共设置,每个站点单独建一个配置文件放在servers/目录下(有些版本是sites-enabled/),用include指令引入。后期维护的时候,一个项目对应一个文件,想禁用某个站点直接移走这个文件就行,清晰得很。

3.2 配置文件逐段拆解

先看一份最基本的nginx.conf,注意我已经把注释简化了,方便逐块理解:

# 以哪个用户身份运行,Mac上直接用当前用户即可 user staff; # 工作进程数,一般设为auto,让nginx自己按CPU核数来 worker_processes auto; # 错误日志级别和路径 error_log /opt/homebrew/var/log/nginx/error.log notice; # 进程ID存放位置,管理nginx进程时要用 pid /opt/homebrew/var/run/nginx.pid; events { # 每个工作进程的最大连接数 worker_connections 1024; } http { # 引入mime类型映射,让nginx知道.xxx后缀对应什么Content-Type 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 /opt/homebrew/var/log/nginx/access.log main; # 开启sendfile,静态文件传输效率高很多 sendfile on; keepalive_timeout 65; # gzip压缩,前端资源体积能小一大截 gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # 引入servers目录下所有以.conf结尾的站点配置 include servers/*.conf; }

events块里的worker_connections决定了并发能力上限。计算公式很简单:最大并发连接数约等于worker_processes * worker_connections。对于苍穹外卖这种开发调试项目,1024绰绰有余,不用刻意调大。

http块里的gzip开关很多人容易漏掉。前端打包出来的JS和CSS动辄几百KB,开启gzip后传输体积能压缩掉60%到70%,页面打开速度肉眼可见地变快。在本地环境跑苍穹外卖时也许感觉不明显,但把这个配置带到服务器上,效果立竿见影。

3.3 最容易搞错的服务块匹配规则

每个站点配置里都会有server块,它代表一个虚拟主机。最核心的匹配规则有两个维度:listen端口和server_name域名。

比如你配置了:

server { listen 80; server_name localhost; ... }

那么浏览器访问http://localhost时命中这个配置。如果同一个端口下有多个server块但server_name不同,nginx会根据请求头里的Host字段来区分。要特别注意:如果你既没有配置server_name又没有指定default_server,nginx会把第一个server块当作默认站点,访问本机IP时也会走到它身上——这是个很容易踩的坑,多个项目配置文件放在servers目录下时,加载顺序就直接决定了谁接盘默认请求。

location块里的匹配优先级也是重点。规则可以概括成:=精确匹配最优先,^~前缀匹配其次,正则匹配(~或~*)再次,最后是普通前缀匹配。在苍穹外卖这种前端单页应用里,最常用的两个location就是根路径/和接口路径/api/,前者负责静态页面和前端路由,后者负责转发后端请求。把这两个规则理清,后面实战配置就不会发怵。

4. 黑马苍穹外卖项目nginx配置实战

4.1 部署思路:静态页面交给nginx,动态请求转给后端

苍穹外卖项目前端打完包之后,会生成一个dist目录,里面是index.html、assets之类的静态资源。后端的Spring Boot服务正常跑在localhost:8080。nginx的作用就是在两者之间搭一座桥。

整体访问流程是:

  1. 浏览器输入http://localhost(80端口)
  2. nginx收到请求,判断路径
  3. 如果是根路径或静态资源路径,直接返回dist目录下的文件
  4. 如果路径以/api开头,转发到http://localhost:8080
  5. 后端处理完业务,返回JSON数据,nginx原样回传给前端

这样前端代码里所有请求都写成相对路径/api/...,不写死IP和端口,不管部署到Mac还是云服务器,只要nginx配置对应调整就行。

4.2 配置静态资源托管

先建一个站点配置文件,比如/opt/homebrew/etc/nginx/servers/sky-take-out.conf,内容如下:

server { listen 80; server_name localhost; # 前端打包产物目录,改成你自己的实际路径 root /Users/mac/Projects/sky-take-out/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }

这段配置里最关键的是try_files $uri $uri/ /index.html;。它的作用是:当用户刷新页面时,如果访问的是像/order/list这种前端路由,服务器上并没有这个真实文件,nginx就回退到index.html,由前端路由接管。不写这一行,你刷新页面就会看到404。

把虚拟目录路径替换成你本机实际的dist路径后,先测试配置:

nginx -t

确认没问题后重新加载:

nginx -s reload

这时候访问http://localhost,你应该能看到苍穹外卖的登录页了。

4.3 配置反向代理解决跨域

页面能打开只是第一步,点击登录的时候你会发现请求全部失败,控制台报跨域错误或者404。原因很简单:前端页面在80端口,后端接口在8080端口,浏览器直接发请求到8080就跨域了(或者你前端把后端地址写死成localhost:8080导致地址不一致)。

解决办法就是在之前的server块里增加一个location块做反向代理:

server { listen 80; server_name localhost; root /Users/mac/Projects/sky-take-out/dist; index index.html; location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://localhost: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; } }

这里有一个非常关键的细节:proxy_pass http://localhost:8080;后面的URL没有带结尾的斜杠。这种情况下,nginx会把完整的原始URI(包括/api前缀)转发给后端,后端收到的请求还是/api/...,与你后端Controller里定义的@RequestMapping("/api/...")是对应的。

如果proxy_pass写成http://localhost:8080/;,nginx在转发时会把匹配到的/api/前缀替换掉,后端收到的就变成了/...,大概率直接404。这个斜杠的区别,建议你亲手改一次试一试,踩过一次坑基本就长记性了。

另外,苍穹外卖项目里有WebSocket通信(比如订单状态推送),WebSocket在nginx里需要额外处理,否则会一直握手失败。在同一个server块里加上:

location /ws/ { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }

proxy_http_version 1.1和Upgrade、Connection这两个头是WebSocket代理的核心,缺一不可。proxy_read_timeout设置长一点,避免连接空闲时间稍长就被nginx掐断。

4.4 可选:负载均衡配置

虽然本地开发用不上,但既然学nginx,负载均衡是绕不开的经典功能。如果你后端起多个实例来做测试(比如8080和8081各跑一个),用upstream就能轻松实现轮询转发。

# 定义一组后端服务器 upstream sky_backend { server localhost:8080; server localhost:8081; } server { listen 80; server_name localhost; root /Users/mac/Projects/sky-take-out/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://sky_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

默认策略是轮询,每个请求依次分给8080、8081。想让某个实例多扛点流量,可以在server后面加weight参数,比如server localhost:8080 weight=3;。这只是最基本的用法,生产环境里还有ip_hash、least_conn等策略,但原理都不难,看一遍官方文档就能上手。

4.5 启动、重载与验证

配置写好后的完整操作流程:

# 1. 检查配置语法 nginx -t # 2. 加载新配置(不会中断现有连接) nginx -s reload # 3. 查看nginx进程和监听端口 ps -ef | grep nginx lsof -i :80

验证时从两个维度去检查:一是浏览器访问http://localhost,页面能正常渲染、样式和图片都加载出来;二是打开浏览器开发者工具,切到Network面板,刷新页面,看到/api/...的请求返回200,响应体是JSON数据,说明反向代理通了。

还有一个技巧:直接看nginx日志。访问日志在/opt/homebrew/var/log/nginx/access.log,报错信息在error.log。排查问题时先看error.log,很多莫名其妙的404和502都是在这里露出真相的。

5. 常见问题与排查心得

5.1 端口被占用

现象:执行nginx启动时终端提示bind() to 0.0.0.0:80 failed (48: Address already in use)。

原因:80端口已经被其他程序占用。Mac上最常见的是系统自带的Apache,或者你自己起过其他Web服务。

排查:

lsof -i :80

看输出结果里是哪个进程占了端口。如果是httpd,说明Apache在跑,用sudo apachectl stop停掉。如果是你自己之前起的服务,用kill结束对应进程。

这里有个建议:开发调试阶段如果80端口一直被各种服务抢占,干脆把nginx的listen改成8080或8888,省得天天跟端口打架。只要前端页面访问的端口和nginx监听的端口保持一致即可。

5.2 前端页面能开但接口报404

现象:页面加载正常,但登录、查询列表等接口请求返回404。

排查方向:逐层看请求到底有没有到达后端。

首先确认后端服务本身是好的,直接访问http://localhost:8080/api/xxx,如果后端能返回数据,说明问题出在代理配置上。

其次看nginx日志,access.log里会记录转发情况。重点检查两点:一是请求是否真的命中了location /api/;二是proxy_pass后面到底有没有多余的斜杠。

大部分404问题都是proxy_pass的URI替换规则没搞明白导致的。我的建议是:只要后端接口路径本身就带/api前缀,proxy_pass后面的地址就不要加斜杠,让它原样转发,省心。

5.3 页面能开但接口报502

现象:接口请求返回502 Bad Gateway。

原因:nginx成功把请求转发给了后端地址,但后端没有响应。典型情况有两个:Spring Boot服务根本没启动,或者监听端口跟nginx配置的不一致。

排查:

# 确认后端端口有没有在监听 lsof -i :8080 # 看后端日志

另外还有一种隐蔽情况:后端服务启动在IPv6地址上,而nginx转发用的是IPv4的localhost。比如Spring Boot在Mac上如果绑定了::1,nginx转发到127.0.0.1:8080就会连不上。解决办法是配置文件里明确写server.port=8080同时设置server.address=127.0.0.1,或者nginx转发地址也统一用一个。

5.4 静态资源更新了但页面还是旧的

现象:前端代码重新npm run build打包后,浏览器刷新页面还是旧版本。

原因:浏览器缓存了旧的JS、CSS文件,或者nginx本身的缓存策略。

解决办法:前端打包时给文件名加上hash(Vue CLI和Vite默认就会打hash),每次构建后文件名都不同,浏览器自然重新拉取。真遇到顽固缓存,浏览器开发工具里勾选Disable cache再刷新,或者用Command + Shift + R强制刷新。

如果想让nginx在响应头里主动告诉浏览器缓存策略,可以加:

location ~* \.(png|jpg|jpeg|gif|ico|svg|css|js)$ { expires 7d; add_header Cache-Control "public, max-age=604800"; }

这套配置适合图片等不常变的资源,开发阶段建议先不加,不然每次改完样式刷新还是旧效果,容易产生“代码没生效”的错觉。

5.5 日志排查速查表

日志路径典型问题
访问日志/opt/homebrew/var/log/nginx/access.log请求是否到达nginx、返回状态码
错误日志/opt/homebrew/var/log/nginx/error.log配置文件语法错误、端口绑定失败、上游连接失败
后端日志取决于Spring Boot配置接口是否报错、SQL是否异常、服务是否正常启动

排查问题时我的习惯是“从前到后”:先在浏览器按F12看请求状态,再从nginx access.log确认请求有没有到这一层,最后看后端日志判断是不是业务代码出了问题。大部分联调问题都能在这一条链路里定位到。

6. 给新手的几点配置习惯建议

第一个建议是把配置拆文件。一个站点一个配置文件放在servers/目录下,命名清晰,比如sky-take-out.conf,不要所有站点都堆在nginx.conf里。改配置之前先备份一份,用nginx -t检查后再reload,这是最基本的操作规范。

第二个建议是注意reload和restart的区别。nginx -s reload是平滑重载,能保留现有连接,适合配置变更;nginx -s stop是强制停止,会掐断所有连接。日常改配置用reload就够了,千万别动不动就重启。

第三个建议是理解Mac环境和Linux环境的差异。Homebrew装的nginx默认以当前用户运行,日志和缓存目录都在/opt/homebrew下;Linux上多数以nginx用户运行,目录在/etc/nginx和/var/log/nginx。换环境部署时,路径和权限是首先要核对的两件事。

最后一个实际操作中的体会:拿黑马苍穹外卖这种真人项目练手nginx,比单纯看教程效果好太多了。因为项目本身有前端、有后端、有代理需求,你会真正遇到404、502、跨域、缓存这些五花八门的问题,每一个问题排下来,对nginx的理解都会深一层。在Mac上把这一整套跑通,回头往云服务器上部署,其实就是一个换安装命令、换配置路径的过程,底层的逻辑完全通用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询