因爲最近學習到 SSR 相關的內容,而且須要作一些內部的工具系統;考慮先熟悉 Koa2+TypeScript 的方式;轉了一圈發現 TypeScript+Koa 的結合基本不多; 因此就有了,如今的總結輸出javascript
跟js項目相比,主要是增長tsconfig.json
和src/@types
的配置, 還有不少庫須要引入TypeScript類型聲明;前端
如js-md5
,同時須要引入@types/js-md5
java
用log4js
這個庫,能夠很好地收集http請求方法、返回狀態、請求url、IP地址、請求時間等,來打印出自定義的日誌。能夠代替console.log()
使用;在使用這個中間件的時候,必須放在第一個中間件,才能保證因此的請求及操做會先通過logger
進行記錄再到下一個中間件。node
import { Context, Next } from 'koa'
import { LogPath } from '../config/constant'
const fs = require('fs')
const path = require('path')
const log4js = require('log4js')
// 這個是判斷是否有logs目錄,沒有就新建,用來存放日誌
const logsDir = path.parse(LogPath).dir
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir)
}
// 配置log4.js
log4js.configure({
appenders: {
console: { type: 'console' },
dateFile: {
type: 'dateFile',
filename: LogPath,
pattern: '-yyyy-MM-dd',
},
},
categories: {
default: {
appenders: ['console', 'dateFile'],
level: 'error',
},
},
})
export const logger = log4js.getLogger('[Default]')
// logger中間件
export const loggerMiddleware = async (ctx: Context, next: Next) => {
// 請求開始時間
const start = +new Date()
await next()
// 結束時間
const ms = +new Date() - start
// 打印出請求相關參數
const remoteAddress = ctx.headers['x-forwarded-for'] || ctx.ip || ctx.ips
const logText = `${ctx.method} ${ctx.status} ${ ctx.url } 請求參數: ${JSON.stringify(ctx.request.body)} 響應參數: ${JSON.stringify( ctx.body )} - ${remoteAddress} - ${ms}ms`
logger.info(logText)
}
複製代碼
在先後端接口請求中,因爲瀏覽器的限制,不一樣域名則會出現跨域的狀況。本實例是採用koa中設置跨域; 採用koa2-cors
庫ios
app.use(Cors(corsHandler))
git
import { Context } from 'koa'
export const corsHandler = {
origin: function (ctx: Context) {
return '*'
},
exposeHeaders: ['Authorization'],
maxAge: 5 * 24 * 60 * 60,
// credentials: true,
allowMethods: ['GET', 'POST', 'OPTIONS', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Requested-With'],
}
複製代碼
配置跨域的時候要注意:設置withCredentials
爲true
時,Access-Control-Allow-Origin
不能設置爲*
github
const instance = axios.create({
baseURL: baseURL,
timeout: 30000,
// withCredentials: true,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
})
複製代碼
在CORS中,Credential不接受http響應首部中的‘Access-Control-Allow-Origin’設置爲通配符‘*’web
新建response.ts
這個中間件主要是用來對返回前端的響應進行統一處理正則表達式
import { logger } from './logger'
import { Context, Next } from 'koa'
export const responseHandler = async (ctx: Context, next: Next) => {
if (ctx.result !== undefined) {
ctx.type = 'json'
ctx.body = {
code: 200,
msg: ctx.msg || '成功',
data: ctx.result,
}
await next()
}
}
export const errorHandler = (ctx: Context, next: Next) => {
return next().catch((err) => {
if (err.code == null) {
logger.error(err.stack)
}
if (err.status === 401) {
ctx.status = 401
ctx.body = 'Protected resource, use Authorization header to get access\n'
} else {
ctx.body = {
code: err.code || -1,
data: null,
msg: err.message.trim() || '失敗',
}
ctx.status = 200
}
return Promise.resolve()
})
}
複製代碼
JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案;數據庫
本項目主要是用到koa-jwt
和jsonwebtoken
這兩個插件
import jsonwebtoken from 'jsonwebtoken'
const { jwtSecret } = require('../../config/index')
export interface UserParams {
username: string
name?: string
avatar?: string
email?: string
gender?: number
phone?: number
accessToken: string
}
export default class JwtAuth {
/** * 獲取用戶token * @static * @param {UserParams} userData * @param {*} [options] * @return {*} {string} * @memberof JwtAuth */
public static signUserToken(userData: UserParams, options?: any): string {
try {
return jsonwebtoken.sign(userData, jwtSecret, options)
} catch (error) {
console.log(error)
}
}
/** * 驗證用戶token值 * @static * @param {string} token * @return {*} {Object} * @memberof JwtAuth */
public static verifyUserToken(token: string): any {
try {
const authorization = token && token.split(' ')[1]
return jsonwebtoken.verify(authorization, jwtSecret)
} catch (error) {
console.log(error)
throw { code: 401, message: 'no authorization' }
}
}
}
複製代碼
寫接口,怎麼能少了接口文檔呢;這裏採用koa集成swagger的方式生成接口文檔。
具體步驟:
koa2-swagger-ui
, swagger-jsdoc
swagger.config
import path from 'path'
import swaggerJSDoc from 'swagger-jsdoc'
import AddressIp from 'ip'
import { PORT } from '../../config/constant'
const swaggerDefinition = {
info: {
// API informations (required)
title: '帳號系統', // Title (required)
version: '1.0.0', // Version (required)
description: '帳號和權限', // Description (optional)
},
host: `http://${AddressIp.address()}:${PORT}`, // Host (optional)
basePath: '/', // Base path (optional)
}
const options = {
swaggerDefinition,
apis: [path.join(__dirname, '../../routes/*.ts')], // all api
}
const jsonSpc = swaggerJSDoc(options)
export default jsonSpc
複製代碼
/doc
路由import Router from 'koa-router'
import { Context } from 'koa'
import swaggerJSDoc from '../middlewares/swagger/swagger.conf'
const routerInit = new Router()
routerInit.get('/docs', (ctx: Context) => {
ctx.body = swaggerJSDoc
})
export default routerInit
複製代碼
// swagger
app.use(
koaSwagger({
routePrefix: '/swagger',
swaggerOptions: {
url: '/docs',
},
})
)
複製代碼
應用的時候,是須要本身手動寫註釋的例如:
/**
* @swagger
* /v1/menu/list/${appId}:
* post:
* description: 獲取菜單列表
* tags: [菜單模塊]
* produces:
* - application/json
* parameters:
* - in: "body"
* name: "body"
* description: "查詢參數"
* schema:
* $ref: "#/definitions/Menu"
* responses:
* 200:
* description: 獲取成功
* schema:
* type: object
* properties:
* total:
* type: number
* rows:
* type: array
* items:
* $ref: '#/definitions/MenuModel'
*
*/
複製代碼
來看當作果:
目前的路由配置是手動添加和註冊的
// 路由
app.use(Role.routes()).use(Role.allowedMethods())
app.use(User.routes()).use(User.allowedMethods())
app.use(Menu.routes()).use(Menu.allowedMethods())
app.use(Auth.routes()).use(Auth.allowedMethods())
複製代碼
在搜索路由自動加載的方案,暫時沒找到適合TypeScript
的庫,卒
若是是用js
能夠考慮這個庫require-directory
咱們用nodejs實現一些功能時,每每須要對用戶輸入的數據進行驗證。然而,驗證是一件麻煩的事情,頗有可能你須要驗證數據類型,長度,特定規則等等,在前端作表單驗證時,咱們經常使用的作法是使用正則,正則表達式也許能夠一步到位,可是他只會給你true or false,若是想要知道數據不符合哪些條件時,那麼你要進一步判斷,下面和你們分享一種可讀性和易用性更好的實現方法。
Joi 是 hapijs 自帶的數據校驗模塊,他已經高度封裝經常使用的校驗功能,本文就是介紹如何優雅地使用 joi 對數據進行校驗。相信你會喜歡上他。便於你們理解,以登陸爲例,通常分兩種方式:A或B (輸入密碼或二維碼),那麼 joi 的配置以下便可實現檢驗:
const Joi = require('joi')
const schema = Joi.object({
name: Joi.string().empty(''),
pageSize: Joi.number().required(),
pageNo: Joi.number().required(),
appId: Joi.number().required(),
})
schema.validateAsync({ ...request })
複製代碼
當一個業務要進行多項數據庫的操做時,拿點贊功能爲例,首先你得在點贊記錄的表中增長記錄,而後你要將對應對象的點贊數加1,這兩個操做是必需要一塊兒完成的,若是有一個操做成功,另外一個操做出現了問題,那就會致使數據不一致,這是一個很是嚴重的安全問題。
具體實踐(就是把多個數據庫操做置於一個事務中):
await sequelize.transaction(async (t: any) => {
await roleModel.update(
{
roleName: request.roleName,
remark: request.remark || '',
},
{
where: {
id: request.id,
},
transaction: t,
}
)
await roleMenuModel.destroy({
where: {
roleId: request.id,
},
force: true,
transaction: t,
})
})
複製代碼
PM2是能夠用於生產環境的Nodejs的進程管理工具,而且它內置一個負載均衡。它不只能夠保證服務不會中斷一直在線,而且提供0秒reload功能,還有其餘一系列進程管理、監控功能。而且使用起來很是簡單。pm2的官方文檔已經進行詳細的配置說明,在這裏就不進行一一簡述,主要講的時個人koa項目怎樣配合PM2進行相關管理或者說部署。也能夠結合在package.json裏面,用自定義命令運行。咱們在package.json
的script
配置
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon --exec ts-node src/app.ts",
"build-ts": "tsc",
"build:test": "rm -fr dist && npm run lint && npm run build-ts",
"serve:test": "cross-env NODE_ENV=development pm2 startOrReload pm2-start.json --no-daemon",
}
複製代碼
pm2-start.json
{
"apps": [
{
"name": "xl-account-server",
"script": "./dist/app.js",
"instances": "2",
"exec_mode": "cluster",
"watch": false,
"watch_delay": 4000,
"ignore_watch" : [
"node_modules",
"src"
],
"max_memory_restart": "1000M",
"min_uptime": "5s",
"max_restarts": 5,
"error_file": "./logs/pm2_xl_account_err.log",
"out_file": "/dev/null",
"log_date_format": "YYYY-MM-DD HH:mm Z"
}
]
}
複製代碼