因为最近学习到 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"
}
]
}
复制代码