项目背景
项目中有很多查询类的接口,我们项目后台用的是JAVA,众所周知,静态语言的灵活性和js要差很多,为了能够更灵活的提供服务,也为了扩展自己的技能池,作为前端开发的我自告奋勇来完成node后台的开发。我把开发过程中使用的一些方法和遇到的坑在这里列出来,希望能够帮助一些初学者快速上手。
1.技术选型
框架选择:expres
语言选择:typescript
数据库:mysql
内存数据库: redis
2.环境变量配置
一般开发环境都会分为各种环境,就我们公司的业务而言,我们分为dev(开发环境),beta(预发布),prod(正式环境),由于这三个环境都是在公司的服务器上,都算是在线上运行,我们的mysql、redis配置都是是用的内网地址,故在开发的时候添加了一个debug模式,也就是本地开发环境,用于连接线上数据库地址。
为什么要设置环境变量
我们每个项目由于有多个环境,每个环境的数据库地址或者其他数据有可能每个环境不太一样,这个时候我们就需要通过环境变量来区分当前环境并来获取特定的数据。
如何获取环境变量
在node里一般通过process.env.NODE_ENV来获取当前运行的环境。我们这里也是通过这个方式来获取。所以就需要我们在启动的时候将当前的环境变量传进去。
设置环境变量的两种方式
1.第一种方法:在package.json的scripts启动命令中添加 export NODE_ENV=debug ,debug可以替换为dev、beta、prod等。
2.第二种方法:如果使用的是pm2启动程序。那么需要在项目的根目录下添加pm2的配置文件,名字可以自定义,我这里命名为ecosystem.json,内容如下。
{
"apps": [
{
"name": "tal_pac_node",
"script": "./dist/index.js",
"exec_mode": "cluster", //应用程序启动模式,这里设置的是cluster_mode(集群),默认是fork
"instances": "4", // 启动的实例个数
"watch": false, // 是否监听文件变动然后重启
"instance_var": "INSTANCE_ID", // 必须设置这个,否则会和config.js 冲突
"ignore_watch": [ // 不用监听的文件
"node_modules",
"logs"
],
"min_uptime": "60s", // 应用运行少于时间被认为是异常启动
"max_memory_restart": "500M", // 最大内存限制
"max_restarts": 30, // 最大异常重启次数,即小于min_uptime运行时间重启次数;
"autorestart": true, // 默认为true, 发生异常的情况下自动重启
"env_development": {
"NODE_ENV": "development",
},
"env_production": {
"NODE_ENV": "production",
},
"env_beta": {
"NODE_ENV": "beta",
}
}
]
}
2.1.fork模式和cluster模式的区别
2.1.1. fork模式
fork模式使用最基本的进程运行方式,只是单实例运行server,无法实现TCP连接共享;好处是可以修改exec_interpreter,使用pm2运行js之外的语言,例如php或者python
2.1.2. cluster模式
利用Node.js Cluster模块实现,只能用于启动node进程,无法应用于其他语言。可以启动多个server实例,并在各个实例之间实现负载均衡而且共享TCP连接,可以提升服务器的响应性能。
2.2 环境变量配置
env_development、env_production、env_beta 就是我们定义的环境变量了。启动的时候在process.env.NODE_ENV就是可以得到相应的development、production、beta了。注意:这里配置的时候前边需要加上env_。
"scripts": {
"clean": "rimraf ./dist",
"build": "npm run clean && npm run build-ts && npm run tslint",
"production": "pm2 startOrRestart ecosystem.json --env production",
"beta": "pm2 startOrRestart ecosystem.json --env beta",
"development": "pm2 startOrRestart ecosystem.json --env development"
}
2.3 如何根据环境变量获取对应的数据
我这里是用的是config这个库。使用方法
npm i config -S
安装以后,在项目的根目录下新建config文件夹。在文件夹下新建'环境变量名'.json。如development.json beta.json。
需要注意的是, 如果process.env.NODE_ENV是undefined,那么会默认的去获取development.json里的数据。
获取配置文件数据的方式
import config from 'config'
// 获取db参数
let db: any = config.get('db')
3.路由配置
项目使用的是expres框架。至于为什么使用exprss是经过综合考虑的。简单、问题少是最重要的因素。
用过express的都知道,express使用路由都是通过以下方式实现
const app = express()
app.use('/api' , router)
之前有的做法就是一个个添加,比如
app.use('/api1' , router)
app.use('/api2' , router)
app.use('/api3' , router)
这种方式实现非常繁琐。也不利于维护。
故我在本项目中使用动态加载的方法来实现。
其实原理很简单,就是读取定义路由的文件夹,遍历文件夹下的文件,获取文件名称,比如我定义的是xxx.routes.ts为路由文件的名称。我可以使用正则表达式来匹配路由文件。
import fs from 'fs'
import path from 'path'
import glob from 'glob'
export function initRouters(app: any) {
// 初始化路由
let routers: any = getApiRouters()
Object.keys(routers).forEach((api: string) => {
let router = require(routers[api])
app.use('/' + api, router)
})
}
function getApiRouters() {
let res: any = {}
let apiDir = path.posix.join(__dirname, '../controllers')
let files = glob.sync(apiDir + '/*/*routes.js')
files.forEach(file => {
let api: string = file.split('/')[file.split('/').length - 1].split('.')[0]
res[api] = file
})
return res
}
获取路由对象,其中xxx为key,文件地址问value。然后遍历对象的keys,通过require动态加载路由。 这里是用的是glob来遍历文件夹。因为我的目录结构如下。
+-- config
+-- src
| +-- controllers
| +-- xxx
| +-- xxx.controllers.ts
| +-- xxx.routes.ts
| +-- init
| +-- index.ts
| +-- mysql.ts
| +-- redis.ts
| +-- router.ts
最后在项目启动时调用initRouters方法就好了。注意调用文件的不同有可能会导致路由文件夹的路径改变,需要修改后使用。
我这里是在init文件夹下,在项目启动时,统一初始化。
import { init } from './init/index'
const app = express()
init(app)
4.mysql初始化
项目中对数据库的操作使用的是sequelize。支持promise,orm,sql语句查询,完美~ 需要注意的是,要进行鉴权操作。
import { Sequelize } from 'sequelize'
import config from 'config'
// 获取db参数
let db: any = config.get('db')
/**
* 连接指定类型的数据库
* host:数据库地址
* max:连接池最大连接数量
* min:连接池最小连接数量
* idle:每个线程最长等待时间
* @type {Sequelize}
*/
export const sequelize = new Sequelize(db.dbName, db.user, db.password, {
host: db.host,
dialect: 'mysql',
port: db.port,
logging: false,
pool: {
max: 20,
min: 0,
idle: 20000
}
})
sequelize
.authenticate()
.then(err => {
console.log(process.env.NODE_ENV + '---mysql数据库连接成功')
})
.catch(err => {
console.log('Unable to connect to database', err)
})
5.错误代码
接口的返回值统一定义为
{
code: Number,
result: Any,
message: String
}
我个人的接口习惯是除非是无法捕获的问题,http staus都会返回200 然后后边跟着业务码 0 代表请求成功。错误代码通过errorCode.js维护
const codes: any = {
'0': 'success',
'20001': '获取看板数据失败'
}
export function generateResult(
code: number,
data?: any,
message?: string
): object {
return {
code: +code,
message: message || codes[`${code}`],
result: data
}
}
在方法里通过使用generateResult生成返回结果。
res.status(200).json(generateResult(0,data))
6.项目部署运行
公司现在有统一的发布平台jenkins,所以node项目也是通过jenkins编译发布。 大致思路就是在jenkins服务器进行编译,然后将代码发到部署服务器上,最后执行npm run development/beta/prodection jenkins如何部署这里就不多写了,如有需要请留言。
对于个人而言其实是没有jenkins的,所以最好的办法就是服务器拉起项目代码,然后执行编译(如有必要),最后启动项目。这些也都可以通过pm2来实现。
首先在项目根目录下新建ecosystem.json文件
{
"apps": [{
"name": "app-name",
"script": "index.js"
}],
"deploy": {
"production": {
"user": "server user name",
"host": ["sever ip"],
"port": "ssh 端口",
"ref": "origin/master", // 项目分支
"repo": "git@github.com:XXX/XXX.git",
"path": "/www/XXX/production",
"ssh_options": "StrictHostKeyChecking=no",
"post-deploy": "npm install --registry=https://registry.npm.taobao.org && pm2 startOrRestart ecosystem.json --env production",
"env": {
"NODE_ENV": "production"
}
}
}
}
deploy下可以添加多个环境变量,这里只写一个production,其他的可自行添加。
最好在服务器添加git的ssh支持,这样就不需要再输入git用户名密码了。 git如何配置ssh请自行搜索。
第一次执行的时候需要执行,可以更换相应的环境变量
pm2 deploy ecosystem.json production setup
以后在执行只需要执行以下即可,不需要添加setup了。
pm2 deploy ecosystem.json production
这样,node项目在服务器就启动了。
7.pm2 常用命令
pm2 ls // 查看所有运行的程序
pm2 logs // 查看项目输出
pm2 delete all // 删除所有程序
pm2 start app-name // 启动程序