對於常用node的開發人員來講,每次搭建後臺服務,都須要考慮如何創建一個更好的文件結構,而大部分的工做都是重複的,有時候會直接拷貝之前的項目文件,可是須要刪除或修改不少東西,並且有不少都不須要的文件,這就很煩惱。html
想到像vue-cli
那樣的腳手架一鍵生成基礎項目模版,那我何不作多個屬於本身的項目模版。使用的時候只須要一行命令就能夠省去不少勞動力,不只省時省事,並且能夠定製本身想要的項目模版。說幹就幹,作腳手架以前先把模版作好,根據以前作小程序時搭建的express後端服務,這裏作了一個基於express的純後端模版。前端
目前已經發布了腳手架工具qiya-cli
,可使用此模版快速生成後端項目,使用方法以下:vue
npm install qiya-cli -g
qiya init
複製代碼
關於腳手架工具qiya-cli
的更多功能能夠參看qiya-cli,另外關於這個腳手架的搭建過程與發佈,我會再寫一篇文章詳細介紹。node
註冊與登陸接口mysql
支持JWT驗證git
Joi參數校驗github
支持mysql的orm框架sequelizeweb
apidoc接口文檔自動生成redis
全局參數配置算法
自動重啓
redis支持
自動化測試
.
├── README.md // 說明文檔
├── app.js // express實例化文件
├── bin
│ └── www // 主入口文件
├── config
│ ├── config.js // 數據庫配置
│ └── index.js // 全局參數配置
├── control // Controller層目錄
│ └── userControl.js
├── helper // 自定義API Error拋出錯誤信息
│ └── AppError.js
├── joi-rule // Joi 參數驗證規則
│ └── user-validation.js
├── models // sequelize須要的數據庫models
│ ├── index.js // 處理當前目錄的全部model
│ └── user.js // user表的model
├── package-lock.json
├── package.json
├── public
│ └── apidoc // 自動生成的apidoc文檔
├── routes // 路由目錄
│ └── user.js
├── service // service層目錄
│ └── user.js
└── until
└── token.js// token下發與驗證
複製代碼
package.json
因爲個人腳手架工qiya-cli
使用了Metalsmith和Handlebars 修改模板文件,因此能夠看到在項目名稱、項目介紹、做者處使用模版語法。 此模版主要有三個scripts
命令,npm run dev
開發環境使用, npm run start
生產環境使用, npm run apidoc
自動生成apidoc文檔(默認啓動後訪問 http://localhost:4000/apidoc便可看到api文檔和進行接口測試),
{
"name": "{{project}}",
"version": "1.0.0",
"description": "{{description}}",
"main": "index.js",
"scripts": {
"dev": "NODE_ENV=development nodemon ./bin/www",
"start": "NODE_ENV=production node ./bin/www",
"apidoc": "apidoc -i ./routes/ -o ./public/apidoc/",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "{{author}}",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"cookie-parser": "^1.4.4",
"debug": "^4.1.1",
"env2": "^2.2.2",
"express": "^4.16.4",
"express-validation": "^1.0.2",
"http-status": "^1.3.1",
"ioredis": "^4.6.2",
"joi": "^14.3.1",
"jsonwebtoken": "^8.5.1",
"mysql2": "^1.6.5",
"sequelize": "^5.1.0",
"sequelize-cli": "^5.4.0",
"uuid": "^3.3.2"
},
"apidoc": {
"name": "{{project}}",
"version": "1.0.0",
"description": "{{project}}項目API文檔",
"title": "{{project}} API",
"url": "http://localhost:4000",
"forceLanguage": "zh-cn"
},
"devDependencies": {
"nodemon": "^1.18.10"
}
}
複製代碼
.env.example
爲了防止敏感數據被放到git倉庫中,我引入一個被 .gitignore 的 .env 的文件,以 key-value 的方式,記錄系統中所須要的可配置環境參數。並同時配套一個.env.example 的示例配置文件用來放置佔位,.env.example 能夠放心地進入 git 版本倉庫。在實際使用過程當中,只需拷貝一份此文件重命名爲.env,而後修改成真實的配置信息便可
# 服務的啓動名字和端口,但也能夠缺省不填值,默認值的填寫只是必定程度減小起始數據配置工做
HOST = 127.0.0.1
PORT = 4000
# MySQL 數據庫連接配置
MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_DB_NAME = 數據庫名
MYSQL_USERNAME = 數據庫用戶名
MYSQL_PASSWORD = 數據庫密碼
#jwt secret祕鑰(本身設置一個複雜的)
JWTSECRET = JWTSECRET
複製代碼
routes > user.js
因爲引入了apidoc,因此在寫路由的時候注意按照如下格式書寫api說明。若是是我的項目,可能會感受麻煩,不須要這種方式,可是若是是協同工做或者項目比較大的時候,有一個良好的api文檔就顯的很是重要了。因此建議平常項目中養成寫api文檔的習慣,對之後的工做將會有很大的幫助。更詳細的apidoc文檔配置能夠參考【ApiDoc】官方文檔(翻譯)或官網
const express = require('express')
const router = express.Router()
const control = require('../control/userControl')
const validate = require('express-validation')
const paramValidation = require('../joi-rule/user-validation')
/**
* @api {post} /v1/user/login 用戶登陸
* @apiDescription 用戶登陸
* @apiName login
* @apiGroup user
* @apiParam {string} [username] 用戶名
* @apiParam {string} password 密碼
* @apiSuccess {sting} token token
* @apiSuccessExample {json} Success-Response:
* {head:{'code':0,'msg':'ok'},data:{'token':''}
* @apiSampleRequest http://127.0.0.1:4000/v1/user/login
* @apiError (Error 400) {String} EMPTY_ERROR [沒有傳入用戶名或密碼]
* @apiErrorExample {json} 參數爲空
* {"message": ["\"password\" is not allowed to be empty"],"code": 400,"stack": {}}
* @apiVersion 1.0.0
*/
router.route('/user/login')
.post(validate(paramValidation.userLogin), control.login)
/**
* @api {post} /v1/user/sign 用戶註冊
* @apiDescription 用戶註冊
* @apiName sign
* @apiGroup user
* @apiParam {string} username 用戶名
* @apiParam {string} password 密碼
* @apiParam {string} gender 性別
* @apiParam {string} avatar_url 頭像
* @apiSuccess {string} result result
* @apiSuccessExample {json} Success-Response:
* {head:{'code':0,'msg':'ok'},data:{'result':'註冊成功'}
* @apiSampleRequest http://127.0.0.1:4000/v1/user/sign
* @apiError (Error 400) {String} EMPTY_ERROR [沒有傳入用戶名或密碼]
* @apiErrorExample {json} 參數爲空
* {"message": ["\"password\" is not allowed to be empty"],"code": 400,"stack": {}}
* @apiError (Error 200) {String} EXIST_USER_ERROR [用戶名已存在]
* @apiErrorExample {json} 用戶已存在
* {head:{'code':0,'msg':'ok'},data:{'result':'用戶名已存在'}
* @apiVersion 1.0.0
*/
router.route('/user/sign')
.post(validate(paramValidation.userSign), control.sign)
module.exports = router
複製代碼
joi-rule > user-validation.js
Joi參數校驗規則
const Joi = require('joi')
module.exports = {
//POST /v1/user/login
userLogin: {
body: {
username: Joi.string().required(),
password: Joi.string().required(),
}
},
// POST /v1/user/sign
userSign: {
body: {
username: Joi.string().required(),
password: Joi.string().regex(/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[\(\)])+$)([^(0-9a-zA-Z)]|[\(\)]|[a-z]|[A-Z]|[0-9]){6,}$/).min(8).required(),
gender: Joi.any().allow('1', '2', '0'),
avtar_url: Joi.string().regex(/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/)
}
}
}
複製代碼
untils > token.js
jwt驗證的實現邏輯,包括token的下發和校驗
const jwt = require('jsonwebtoken')
// 下發token
function createJwt(opt) {
const payload = {
user_id: opt.user_id,
name: opt.name,
exp: Math.floor(new Date().getTime() / 1000) + 60 * 60,
}
return jwt.sign(payload, process.env.JWTSECRET)
}
// 解析token
function parse(token) {
if (token) {
try {
return jwt.verify(token, process.env.JWTSECRET)
} catch (err) {
return null
}
}
return null
}
// 驗證token
function verifyToken(token, user_id) {
if (token) {
jwt.verify(token, process.env.JWTSECRET, (error, decode) => {
if (error) {
console.log('token 驗證錯誤信息', error)
return false
}
if (decode.user_id) {
return user_id == decode.user_id
} else {
return false
}
})
} else {
return false
}
}
module.exports = {
createJwt,
parse,
verifyToken
}
複製代碼
models > index.js
sequelize實例化,以及映射數據庫表
const fs = require('fs')
const path = require('path')
const Sequelize = require('sequelize')
const configs = require('../config/config')
const basename = path.basename(__filename)
const env = process.env.NODE_ENV || 'development'
const config = {
...configs[env],
define: {
underscored: true
}
}
const db = {}
let sequelize = null
if (config.use_env_variable) { // 連線字串的方式連線
sequelize = new Sequelize(process.env[config.use_env_variable], config)
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config)
}
// require 將相同目錄底下的 .js 以 model.name 當索引值放到 db 物件中。
// 執行每個 model 的 「define」將 資料表與 js 對應上
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
})
.forEach(file => {
var model = sequelize['import'](path.join(__dirname, file))
db[model.name] = model
})
// 來執行 db 物件裡的每個 .associate method
// 執行每個 「model 關聯」 的設定,也就是關聯式資料庫的 foreign key 的設定與 js 對應上。
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
// 將全域的物件與類別,也放進 db 物件中。
// 將全部關於 MVC 的 M 都收斂在 db 裏
db.sequelize = sequelize
db.Sequelize = Sequelize
module.exports = db
複製代碼
Sequelize是一款基於Nodejs功能強大的異步ORM框架。同時支持PostgreSQL, MySQL, SQLite and MSSQL多種數據庫,很適合做爲Nodejs後端數據庫的存儲接口,爲快速開發Nodejs應用奠基紮實、安全的基礎。
相關文檔:
apidoc是一款能夠由源代碼中的註釋直接自動生成api接口文檔的工具,它幾乎支持目前主流的全部風格的註釋。例如: Javadoc風格註釋(能夠在C#, Go, Dart, Java, JavaScript, PHP, TypeScript等語言中使用)
相關文檔:
joi就比如是一個驗證器,你能夠本身規範schema來限制資料格式,有點像是正規表示法,這邊來舉個例子好了,利如PORT只容許輸入數字若輸入字串就會被阻擋PORT: Joi.number(),這樣有好處萬一有使用者不按照規範輸入數值他會在middleware拋出一個錯誤告訴你這邊有問題要你立刻修正。
相關文檔:
JWT是JSON Web Token的縮寫,一般用來解決身份認證的問題,JWT是一個很長的base64字串在這個字串中分爲三個部分別用點號來分隔,第一個部分爲Header,裏面分別儲存型態和加密方法,一般系統是預設HS256雜湊演算法來加密,官方也提供許多演算法加密也能夠手動更改加密的演算法,第二部分爲有效載荷,它和會話同樣,能夠把一些自的定義數據存儲在Payload裏例如像是用戶資料,第三個部分爲Signature,作爲檢查碼是爲了預防前兩部分被中間人僞照修改或利用的機制。
Header(標頭):用來指定哈希算法(預設爲HMAC SHA256) Payload(內容):能夠放一些本身要傳遞的資料 Signature(簽名):爲簽名檢查碼用,會有一個serect string來作一個字串簽署 把上面三個用「。」接起來就是一個完整的JWT了!
使用流程: 使用者登入-> 產生API Token -> 進行API 路徑存取時先JWT 驗證-> 驗證成功才容許訪問該API
相關文檔:
目前此模版已存在qiya-cli
中,後續將會在qiya-cli
中添加更多的模版,方便平常開發,減小重複工做。
目前計劃中的項目模版
若是你有更好的項目模版,不管是前端仍是後端,都歡迎提交PR到github.com/gengchen528…,或者在下方留言。
固然你的模版必需要有一份詳細的功能說明及核心文件詳解,而且在package.json中把項目名稱使用{{project}}
,項目說明使用description
,做者使用author
替換。
我會把好的項目模版添加到qiya-cli
中,而且註明模版提供者與提供者git連接。但願qiya-cli
可以成爲一個全面的項目模版工具,只要你想要的模版都能在qiya-cli
中快速找到。
腳手架
模版:
qiya-cli-express-template(此模版)
若是你有好的項目模版,歡迎聯繫我