NGINX 部署前后端分离项目

41 阅读3分钟

最近写学校的作业想到可以用服务器部署写完的本地项目就买了个阿里云然后部署了一个服务,下面是完整的过程

部署后端服务

我的后端用的是fastapi,用的conda做的虚拟环境管理,一开始想的是用systemctl直接把服务跑起来,我的service文件是这样的

[Unit]
Description=MindLens Python backend service
After=network.target

[Service]
User=admin
WorkingDirectory=/usr/local/bin/MindLens/backend
ExecStart=/root/miniconda3/envs/comp/bin/python /usr/local/bin/MindLens/backend/main.py

[Install]
WantedBy=multi-user.target

很没意外的失败了,主要遇到的问题是权限不足,当时一直在报错的信息是

mindlens-backend.service: Failed at step EXEC spawning /usr/local/bin/MindLens/start_python.sh: Permission denied

后面我看了一些StackOverflow的教程例如 serverfault.com/questions/9… 里面的高赞回答说的是

SELinux prevents you from running a system service where the binary is in a user's home directory, or in your case, the root user's home directory. To fix the problem, copy the binary to a proper directory such as /usr/local/bin and call it from there.

大概意思就是systemctl不能执行用户目录下的代码,随后我把代码放到/usr目录但是发现还是有这个问题,于是我就想不如直接写一个脚本,然后用脚本启动fastapi,给脚本777的权限就可以避免这个。

#!/bin/bash

source /root/miniconda3/etc/profile.d/conda.sh

cd /usr/local/bin/MindLens/backend

# 激活指定的 conda 环境

conda activate comp

# 你可以在这里添加任何需要执行的命令,例如启动一个程序

echo "已成功激活 conda 环境 'comp'!"

python /usr/local/bin/MindLens/backend/main.py

这是最后的脚本,这里要注意就是要先执行source conda路径来在当前的bash上初始化conda环境否则后续的切换环境这一步是没法正常操作的,会出现丢包的问题。

随后就很顺利的激活服务了,然后

sudo systemctl enable mindlens-backend.service

就可以实现开机自启动,有关systemctl service的文章可以看其他教程

前端部署

完整的nginx配置

server {
    listen       80;
    server_name  120.77.171.237;

    charset utf-8;  # 防止中文显示出现乱码

    # 将 /mindlens 路径映射到前端资源目录
    location /mindlens/ {
            # 设置前端静态资源路径
        alias   /usr/local/bin/MindLens/web/dist/;  # 前端构建产物的路径
        index  index.html index.htm;  # 默认文件
        try_files $uri $uri/ /index.html;  # 对于单页应用,返回 index.html
    }

    # 错误页面处理
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }

    # API 代理到后端
    location /api/ {
        proxy_pass http://localhost:8000/api/;  # 后端服务的地址
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

基础配置

这里很多教程都是直接把文件放到nginx的目录下面,但实际上放在哪里都可以,另外nginx的配置也不一定要放在nginx根目录的配置文件中。 下面是我nginx根目录的配置文件

user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
# 
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}

这里最关键的是第一行需要更改nginx的用户,我直接选择root避免后续的权限问题,否则很容易出现403 forbidden,如果出现大概率就是权限问题

后端反向代理配置

刚刚已经用systemctl启动了一个python的服务,这里就可以用反向代理来实现跨域和后端部署,这个流程主要是因为浏览器的同源策略会禁止浏览器所在的源访问其他的源,这个源主要取决你发起者的源头 比如:

图片.png 这里我请求后端,因为后端已经被反向代理所以没有origin,但是可以看出来是跑在120.77.171.237:80端口的,因为这是http协议的默认端口。所以如果我们把服务挂在后端但是直接去请求就会出现跨域。因为从80端口请求到后端的端口这个过程实际上也是跨域了。

nginx的反向代理就是后端的服务器前面套了一个代理服务器,这和正向代理不一样,正向代理是在客户端前面套一个代理服务器,从服务器的视角看他只能看到代理服务器,但是反向代理是客户端看不到后端服务的具体信息(比如这里就看不到后端的端口,因为被反向代理了)

nginx对应的后端配置

    # API 代理到后端
    location /api/ {
        proxy_pass http://localhost:8000/api/;  # 后端服务的地址
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

这里一定要注意如果location的后面有斜杠代理地址的后面也必须有斜杠

前端nginx部署

前端首先要在本地进行打包,我想做到当访问/mindlens的时候可以跳转到我的服务而不是直接写到根目录

前端打包

我用vite做项目管理首先就是需要前端打包

/// <reference types="vitest" />

import path from 'node:path'
import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import VueMacros from 'unplugin-vue-macros/vite'
import { VueRouterAutoImports } from 'unplugin-vue-router'
import VueRouter from 'unplugin-vue-router/vite'
import { defineConfig } from 'vite'

export default defineConfig({
 base: '/mindlens/',
 resolve: {
   alias: {
     '~/': `${path.resolve(__dirname, 'src')}/`,
   },
 },
 plugins: [
   VueMacros({
     defineOptions: false,
     defineModels: false,
     plugins: {
       vue: Vue({
         script: {
           propsDestructure: true,
           defineModel: true,
         },
       }),
     },
   }),

   // https://github.com/posva/unplugin-vue-router
   VueRouter(),

   // https://github.com/antfu/unplugin-auto-import
   AutoImport({
     imports: [
       'vue',
       '@vueuse/core',
       VueRouterAutoImports,
       {
         // add any other imports you were relying on
         'vue-router/auto': ['useLink'],
       },
     ],
     dts: true,
     dirs: [
       './src/composables',
     ],
     vueTemplate: true,
   }),

   // https://github.com/antfu/vite-plugin-components
   Components({
     dts: true,
     resolvers: [
       AntDesignVueResolver({
         importStyle: false, // css in js
       }),
     ],
   }),

   // https://github.com/antfu/unocss
   // see uno.config.ts for config
 ],

 // https://github.com/vitest-dev/vitest
 test: {
   environment: 'jsdom',
 },
})

这里最重要的就是base这个配置,这个的配置结果主要是可以让最终的构建产物的结构是

图片.png 而html中对应chunk的加载却是

<script type="module" crossorigin src="/mindlens/assets/index-um0TqLQj.js"></script>

这一点非常关键,因为后续我们要用nginx的alias配置覆写掉/mindlens/这个url,所以必须确保html对js和css的请求地址中包含/mindlens/

nginx配置

# 将 /mindlens 路径映射到前端资源目录
    location /mindlens/ {
            # 设置前端静态资源路径
        alias   /usr/local/bin/MindLens/web/dist/;  # 前端构建产物的路径
        index  index.html index.htm;  # 默认文件
        try_files $uri $uri/ /index.html;  # 对于单页应用,返回 index.html
    }

这里一定要注意,和后端一样如果location的最后有斜线则alias的后面也必须有否则会出问题,因为这个匹配是直接替换的 另外就是root和alias的选择,这两个配置的区别在于

  • alias是直接将location换成alias的内容,比如当我们访问120.7.1.23/mindlens/的时候他就会变成120.7.1.23/usr/local/bin/MindLens/web/dist/
  • 而如果这个地方是root他则会变成120.7.1.23/usr/local/bin/MindLens/web/dist/mindlens/所以一开始我配置root会出现找不到资源的问题