2021 / 07 / 29

nginx本地配置

本文字数: 7970阅读时间: 19分钟

在平时开发过程中,一般都是用webpack)+http-proxy完成对后端接口的调用,公司的微服务有很多,很多时候再调试的时候需要切换配置文件或者修改配置文件来调试接口,但是由于公司老项目体积太大,所以每次修改配置重启服务的时间都可以泡一杯☕️~

想法

这里想的是用Electron) + Vue2.x做的客户端,通过nginx来代理本地的某个端口到服务器,用node的child_process,修改nginx.conf配置文件来启动、重启、重载nginx服务本地nginx服务。

开工

NginxService.js 用来管理nginx服务

首先是要区分windows还是 mac os,这里暂时没有考虑Linux,其实是因为懒zzzz。 区分是不是windows

const isWindows = process.platform === 'win32'

然后获取nginx的配置文件 (配置文件为了方便导入导出和配置,是通过读取json文件用ejs生产最终的nginx.conf)

nginx配置的模板文件conf.ejs

worker_processes  1;  
  
error_log  "<%=logPath%>";  
error_log  "<%=logPath%>"  notice;  
error_log  "<%=logPath%>"  info;  
  
events {  
    worker_connections  1024;  
}  
http {  
    include       mime.types;  
    client_body_temp_path "<%=tempPath%>/client_body_temp";  
    proxy_temp_path "<%=tempPath%>/proxy_temp";  
    fastcgi_temp_path "<%=tempPath%>/fastcgi_temp";  
    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;  
    #gzip  on;  
    server {  
        listen       <%=port%>;  
        server_name  127.0.0.1;  
        error_page   500 502 503 504  /50x.html;  
        location = /50x.html {  
            root   html;  
        }  
        location / {  
            proxy_pass <%=defaultMode%>;  
        }<% rules.map(rule => {%>  
  location <%=rule.path%> {  
            proxy_pass <%=rule.proxy%>;  
        }<% }) %>  
  }  
}

配置json文件

{  
  "mode": "boss-test", // 当前环境  
  "port": "8890", // 代理端口 需要跟前端的代理地址一致  
  "host": {  
      "boss-test": "https://test.www.xxxx.com.cn", // 个个环境的代理地址  
      "boss-dev": "https://dev.www.xxxx.com.cn",  
      "boss-pro": "https://xxxxx.xxxx.com",  
      "m.xxxx": "https://test.m.xxxx.com.cn",  
  },  
 "proxyRules": [ // 针对单独的特定接口的环境代理,例如我在测试环境需要调用一个开发环境的端口  
      {  
      "id": "06eb1141-44e6-423e-bb83-c6cff5b9cc86",  
      "mode": "boss-pro", // 单独接口的环境  
      "rule": "/boss-api", // 代理规则  
      "status": false // 是否是用配置  
      }
    ]
}

因为linux和windos的路径问题,读取配置需要注意反斜杠和斜杠的转译,所以处理的代码如下

if (isWindows) {  
  const truePath = path.resolve(path.join(__static, `./nginx/windows/`))  
  this.nginx = '"' + truePath + '/nginx.exe" ' + '-p "' + truePath + '"'  
  this.nginx = this.nginx.replace(/\\/g, '/')  
} else {  
  this.nginx = path.resolve(path.join(__static, `./nginx/mac/bin/nginx`))  
}

然后就是启动nginx服务了,通过用child_process.exec启动子进程,因为用electron打包之后是看不到文件的,所以这里要添加日志记录每一步的操作,为了以后方便排错, 重启和重载服务很简单,这里贴出代码不一一讲解。

async start () {  
  try {  
  let config = this.config  // 获取执行路径
  if (isWindows) {  
  config = `"${config}"`  // windows 的特殊处理- -
  }  
  const {stderr} = await exec(`${this.nginx} -c ${config}`)  // 启动
  console.log(stderr)  
  await addLog('nginx start successful')  // 记录日志
  return true  
  } catch (e) {  
  await addLog('nginx start error ->' + e)  
  console.log(e)  
  return false  
  }  
}
async stop () {  
  try {  
  if (isWindows) {  
  await exec(`taskkill /fi "imagename eq nginx.EXE" /f`)  
 } else {  
  await exec(`ps -lef|grep -i nginx:|awk '{ print $2}'|xargs kill -9`)  
 } } catch (e) {  
  await addLog('nginx stop error ->' + e)  
  console.log(e)  
 }  await addLog('nginx stop successful')  
  return true  
}
async reload () {  
  try {  
  if (await this.test()) {  
  if (isWindows) {  
  const {stdout, stderr} = await exec(`${this.nginx} -s reload -c "${this.config}"`)  
  console.log(stdout)  
  console.log(stderr)  
 } else {  
  const {stdout, stderr} = await exec(`${this.nginx} -s reload`)  
  console.log(stdout)  
  console.log(stderr)  
 }  await addLog('nginx reload successful')  
  return true  
  } else {  
  return false  
  }  
 } catch (e) {  
  await addLog('nginx reload error ->' + e)  
  console.log(e)  
  return false  
  }  
}

这里要讲一下,这里是通过用命令检测配置nginx的端口有没有被占用来看nginx的状态(没想到更好的方案),如果有更好的办法,欢迎在评论区指出^^

async status () {  
  try {  
  const jsonObj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))  
  if (isWindows) {  
  await exec(`netstat -ano|findstr ${jsonObj.port}`)  
 } else {  
  await exec(`lsof -i:${jsonObj.port}`)  
 }  await addLog('nginx get status successful')  
  return true  
  } catch (e) {  
  await addLog('nginx get status error ->' + e)  
  console.log(e)  
  return false  
  }  
}

main.js 引入NginxService, 与渲染进程通信

这里直接放出代码,主要用的是electron的(ipcMain)[https://electronjs.org/docs/api/ipc-main#ipcmain]来从主进程到渲染进程的异步通信。

listen.js

import nginxService from './NginxService'  
import path from 'path'  
import fs from 'fs'  
import {renderConfig, changeMode, updatePort, deleteMode, addHost, addRules, deleteRule, updateRule, importFile, cleanLogs} from './nginx/index'  
const jsonPath = path.resolve(path.join(__static, './nginx/config/proxy.json'))  
const { ipcMain } = require('electron')  
ipcMain.on('get-rules', async (event, arg) => {  
  event.returnValue = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))  
})  
ipcMain.on('get-path', async (event) => {  
  event.returnValue = path.resolve(path.join(__static, './nginx/config/proxy.json'))  
})  
ipcMain.on('get-error-log-path', async (event) => {  
  event.returnValue = path.resolve(path.join(__static, `./nginx/${process.platform === 'win32' ? 'windows' : 'mac'}/logs/error.log`))  
})  
ipcMain.on('get-logs', async (event) => {  
  event.returnValue = fs.readFileSync(path.resolve(path.join(__static, `./log.txt`)), 'utf8')  
})  
ipcMain.on('get-logs-path', async (event) => {  
  event.returnValue = path.resolve(path.join(__static, `./log.txt`))  
})  
ipcMain.on('clean-logs', async (event) => {  
  event.returnValue = await cleanLogs()  
})  
ipcMain.on('get-status', async (event, arg) => {  
  event.returnValue = await nginxService.status()  
})  
ipcMain.on('stop-nginx', async (event, arg) => {  
  event.returnValue = await nginxService.stop()  
})  
ipcMain.on('start-nginx', async (event, arg) => {  
  nginxService.start()  
  setTimeout(() => {  
  event.returnValue = true  
  }, 1000)  
})  
ipcMain.on('render-config', async (event, arg) => {  
  event.returnValue = await renderConfig()  
})  
ipcMain.on('reload-nginx', async (event, arg) => {  
  event.returnValue = await nginxService.reload()  
})  
ipcMain.on('add-host', async (event, arg) => {  
  event.returnValue = await addHost(...arg)  
})  
ipcMain.on('add-rule', async (event, arg) => {  
  event.returnValue = await addRules(...arg)  
})  
ipcMain.on('delete-rule', async (event, arg) => {  
  event.returnValue = await deleteRule(arg)  
})  
ipcMain.on('update-rule', async (event, arg) => {  
  event.returnValue = await updateRule(...arg)  
})  
ipcMain.on('update-mode', async (event, arg) => {  
  event.returnValue = await changeMode(arg)  
})  
ipcMain.on('delete-mode', async (event, arg) => {  
  event.returnValue = await deleteMode(arg)  
})  
ipcMain.on('update-port', async (event, arg) => {  
  event.returnValue = await updatePort(arg)  
})  
ipcMain.on('import-file', async (event, arg) => {  
  event.returnValue = await importFile(arg)  
})

再从main.js引入listen.js即可

// 监听render事件    
require('./linten')

渲染进程代码

主要就是用vue+element-ui写的界面再与主进程通信调用nginx的服务,这里就不贴出来了,可以到项目地址查看。

总结

总的来说,这个东西难度不大,但是很省事,比较不是谁都能忍受每次重启项目要等5-6分钟,也得找个时间优化一下老项目了,历史遗留问题太多了zzzz.

更多阅读