koa-service
├─ babel.config.json - Babel配置项
├─ package-lock.json
├─ package.json
├─ README.md
└─ src - 源文件
├─ app - koa实例
│ ├─ conf.js - koa相关配置项
│ └─ index.js - koa入口文件
├─ auth - 权限验证
│ └─ index.js - 权限验证入口文件
├─ controllers - 控制器
│ ├─ common - 公共控制器
│ ├─ index.js - 控制器入口文件
│ ├─ modules - 模块控制器
│ │ ├─ role.js - 角色控制器
│ │ └─ user.js - 用户控制器
│ └─ util.js - 控制器工具
├─ db - 数据库
│ ├─ conf.js - 数据库配置项
│ └─ index.js - 数据库入口文件
├─ index.js - 总入口文件
├─ model - 数据库模型
│ └─ user.js - 用户模型
├─ redis - redis缓存数据库
│ ├─ conf.js - redis相关配置项
│ └─ index.js - redis入口文件
├─ router - router
│ ├─ conf.js - 路由相关配置项
│ └─ index.js - 路由入口文件
├─ services - 服务
│ ├─ baseService.js - 主服务
│ └─ user.js - 用户服务
└─ utils - 项目工具类
└─ index.js - 项目工具类入口文件
1、创建项目,并初始化项目
$ mkdir koa-service
2、初始化项目
$ npm init -y
# 创建babel配置项文件
$ touch babel.config.json
3、创建相关文件
# 创建源文件文件夹src和公共入口文件
$ mkdir src && touch src/index.js
# 将终端中的路径切换到src
$ cd src
# 以下为创建src目录里面的子文件夹和相应的文件
# 创建koa实例文件夹以及相关文件
$ mkdir app && touch app/conf.js app/index.js
# 创建权限验证文件夹以及相关文件
$ mkdir auth && touch auth/index.js
# 创建控制器中间件文件夹以及相关文件
$ mkdir controllers controllers/common controllers/modules && touch controllers/index.js controllers/util.js controllers/modules/role.js controllers/modules/user.js
# 创建数据库文件夹以及相关文件
$ mkdir db && touch db/conf.js db/index.js
# 创建数据库模型文件夹以及相关文件
$ mkdir model && touch model/user.js
# 创建redis文件夹以及相关文件
$ mkdir redis && touch redis/conf.js redis/index.js
# 创建router文件夹以及相关文件
$ mkdir router && touch router/conf.js router/index.js
# 创建services文件夹以及相关文件
$ mkdir services && touch services/baseService.js services/user.js
# 创建utils文件夹以及相关文件
$ mkdir utils && touch utils/index.js
# 将终端中的路径切换回项目根目录
$ cd ..
4、安装项目所需依赖
# 安装外部依赖
$ npm install @babel/polyfill @koa/router koa koa-body pm2 --save
# 安装项目依赖
$ npm install @babel/cli @babel/core @babel/node @babel/plugin-proposal-decorators @babel/preset-env core-js cross-env ioredis mysql2 node-console-colors sequelize --save-dev
5、修改package.json文件
{
"name": "koa-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "cross-env NODE_ENV=development babel-node src/index",
"test": "echo \"Error: no test specified\" && exit 1",
"clear": "rm -rf lib/**",
"babel": "npm run clear && babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@koa/router": "^10.1.1",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
"pm2": "^5.1.2"
},
"devDependencies": {
"@babel/cli": "^7.16.0",
"@babel/core": "^7.16.0",
"@babel/node": "^7.16.0",
"@babel/plugin-proposal-decorators": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"core-js": "^3.19.1",
"cross-env": "^7.0.3",
"ioredis": "^4.28.0",
"mysql2": "^2.3.3-rc.0",
"node-console-colors": "^1.1.4",
"sequelize": "^6.9.0"
}
}
6、修改babel.config.json
{
// "sourceMaps": true,
// "sourceType": "module",
// "compact": true,
// "minified": true,
"presets": [
[
"@babel/preset-env", // 对 targets 目标浏览器中没有的功能添加转换插件
{
"targets": {
// "esmodules": true,
// "node": "v14.14.0",
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage", // usage: 按需加载 polyfill, entry: 加载全部
"corejs": "3.19.1"
}
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
7、其他相关文件代码
// app/conf.js
import os from 'os';
/**
* port: http\https 服务启动的默认端口
* bodyConfig: koa 正文解析器中间件 koa-body 的配置项
*/
/**
* @description: Koa Config
* @param {number} [port=3000] - 服务启用的默认端口
* @param {object} [bodyConf] - koa-body中间件的配置项(正文解析配置项)
* @param {boolean} [bodyConf.strict=true] - 是否启用严格模式,启用后不会解析 GET, HEAD, DELETE 请求
* @param {array} [bodyConf.parsedMethods=['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']] - 请求方法
* 注意:strict 和 parsedMethods 这两个配置项不能共存
* @param {boolean} [bodyConf.patchNode=false] - 是否将请求体打到原生 node.js 的ctx.req中
* @param {boolean} [bodyConf.patchKoa=true] - 将请求体打到 koa 的 ctx.request 中
* @param {string|number} [bodyConf.jsonLimit='1mb'] - JSON 数据体的大小限制
* @param {string|number} [bodyConf.formLimit='56kb'] - 限制表单请求体的大小
* @param {string|number} [bodyConf.textLimit='56kb'] - 限制 text body 的大小
* @param {string} [bodyConf.encoding='utf-8'] - 表单的默认编码
* @param {boolean} [bodyConf.multipart=false] - 是否支持 multipart-formdate 的表单
* @param {boolean} [bodyConf.urlencoded=true] - 否支持 urlencoded 的表单
* @param {boolean} [bodyConf.text=true] - 是否解析 text/plain 的表单
* @param {boolean} [bodyConf.json=true] - 是否解析 json 请求体
* @param {boolean} [bodyConf.jsonStrict=true] - 是否使用 json 严格模式,true 会只处理数组和对象
* @param {boolean} [bodyConf.includeUnparsed=false] - 是否使用 json 严格模式,true 会只处理数组和对象
* @param {object} [bodyConf.queryString=null] - 请求查询的内容
* @param {object} [bodyConf.formidable={}] - 配置更多的关于 multipart 的选项
* @param {string} [bodyConf.formidable.encoding='utf-8'] - 默认编码
* @param {string} [bodyConf.formidable.uploadDir=(os.tmpdir && os.tmpdir()) || os.tmpDir()] - 设置文件上传目录
* @param {boolean} [bodyConf.formidable.keepExtensions=false] - 是否保留原文件后缀名
* @param {boolean} [bodyConf.formidable.allowEmptyFiles=true] - 是否允许上传空文件
* @param {number} [bodyConf.formidable.minFileSize=1] - 限制上传文件大小的最小值
* @param {number} [bodyConf.formidable.maxFileSize=200 * 1024 * 1024] - 限制上传文件大小的最大值
* @param {number} [bodyConf.formidable.maxFields=1000] - 限制字段的最大个数
* @param {number} [bodyConf.formidable.maxFieldsSize=20 * 1024 * 1024] - 限制字段值的大小的最大值
* @param {string|boolean} [bodyConf.formidable.hash=false] - 是否计算文件的 hash,如果为 true 选择 md5/sha1 进行计算(包括为传入文件计算的校验和,将其设置为某些哈希算法,有关可用算法,请参见crypto.createHash)
* @param {boolean} [bodyConf.formidable.multiples=false] - 是否支持多文件上传
* @param {function} [bodyConf.formidable.onFileBegin=(name, file) => {}] - 文件上传前的回调方法
* @param {boolean|function} [bodyConf.onError=false] - 错误处理方法
*/
const config = {
port: 3000,
bodyConf: {
// strict: true,
// parsedMethods: ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE'],
patchNode: false,
patchKoa: true,
jsonLimit: '1mb',
formLimit: '56kb',
textLimit: '56kb',
encoding: 'utf-8',
multipart: false,
urlencoded: true,
text: true,
json: true,
jsonStrict: true,
includeUnparsed: false,
queryString: null,
formidable: {
encoding: 'utf-8',
uploadDir: (os.tmpdir && os.tmpdir()) || os.tmpDir(),
keepExtensions: false,
allowEmptyFiles: true,
minFileSize: 1,
maxFileSize: 200 * 1024 * 1024,
maxFields: 1000,
maxFieldsSize: 20 * 1024 * 1024,
hash: false,
multiples: false,
onFileBegin: (name, file) => {
console.log(name);
console.log(file);
}
},
onError: (err) => {
console.log(JSON.stringify(err));
}
}
}
export default config;
// app/index.js
import Koa from 'koa';
import KoaBody from 'koa-body';
import Http from 'http';
import Https from 'https';
import Redis from '../redis';
import AppConf from './conf';
import Router from '../router';
import Controllers from '../controllers';
import Auth from '../auth';
import {
getLocalAndNetworkIP,
log
} from '../utils';
// 创建 koa 应用
const app = new Koa();
// 创建路由
const router = Router(Controllers);
// 将redis实例导入到koa实例中的上下文中,方便后期直接使用
app.context.redis = Redis;
// koa 正文解析器中间件,类似于 Express 中的bodyParser
app.use(KoaBody(AppConf.bodyConf));
// 权限验证
app.use(Auth);
// 嵌套路由
app.use(router.routes());
app.use(router.allowedMethods());
// 监听错误日志
app.on('error', err => {
log(err);
});
// 创建 Http 服务,并监听相应端口
Http.createServer(app.callback()).listen(AppConf.port);
// 创建 Https 服务,并监听相应端口
Https.createServer(app.callback()).listen(AppConf.port + 1);
/**
* 以下为开发使用
*/
if (process.env.NODE_ENV !== 'production') {
log(`App running at:`);
// 获取当前机器所有的 IPv4 的 IP
const {
localIp,
networkIpList
} = getLocalAndNetworkIP();
const ipList = [localIp, ...networkIpList];
ipList.forEach((item, index) => {
if (index) {
log(` - Network_http_${index + 1}: http://${item}:${AppConf.port}`);
log(` - Network_https_${index + 1}: https://${item}:${AppConf.port + 1}`);
} else {
log(` - Local_http_${index + 1}: http://${item}:${AppConf.port}`);
log(` - Local_https_${index + 1}: https://${item}:${AppConf.port + 1}`);
}
})
}
// auth/index.js
const validAuth = async (ctx, next) => {
if(ctx.headers.token) {
}
next();
}
export default validAuth;
// controllers/util.js
/**
* @description: 类的前缀装饰器
* @param {string} [prefix] - 前缀字段值
*/
export function prefixController(prefix) {
return function (target) {
target.prefix = prefix;
}
}
/**
* @description: 路由组合方法
* @param {string} [method] - 请求方式
* @param {string} [url] 请求路径
*/
function routerFn(method, url) {
return function (target, property) {
!target.routers && (target.routers = []);
target.routers.push({
name: property,
method,
url
});
}
}
/**
* @description: 自动生成所有的请求装饰器
* @return {object} [obj={}] - 返回所有方法的回调
*/
export function requstMethods() {
const methods = ['get', 'post', 'delete', 'put', 'options', 'head', 'patch', 'all'];
let obj = {};
for (let key of methods) {
obj[key] = function (url) {
return routerFn(key, url);
}
}
return obj;
}
// controllers/index.js
import User from './modules/user';
import Role from './modules/role';
export default {
User,
Role
}
// controllers/modules/user.js
import { prefixController, requstMethods } from '../util';
const {get, post} = requstMethods();
@prefixController('/user')
export default class User {
@get('/')
async getUserList(ctx) {
ctx.body = '12323'
}
@get('/info')
async getUserInfo(ctx) {
console.log(ctx);
}
@post('/add')
async addUserInfo(ctx) {
console.log(ctx);
}
}
// controllers/modules/role.js
import { prefixController, requstMethods } from '../util';
const {get, post} = requstMethods();
@prefixController('/role')
export default class Role {
@get('/')
getRoleList(ctx) {
console.log(ctx);
}
@get('/info')
getRoleInfo(ctx) {
console.log(ctx);
}
}
// db/conf.js
/**
* @description: Database Config
* @param {string} [database] - 数据库名称
* @param {string} [username=null] - 数据库用户名
* @param {string} [password=null] - 数据库密码(支持 SQLite 的 SQLCipher 加密)
* @param {object} [options={}] - 数据库的配置项
* @param {string} [options.host='localhost'] - 关系型数据库主机
* @param {number} [options.port=] - 关系型数据库的端口号
* @param {string} [options.username=null] - 数据库用户名的二次验证
* @param {string} [options.password=null] - 数据库密码的二次验证
* @param {string} [options.database=null] - 数据库名称
* @param {string} [options.dialect] - 要连接的数据库类型。 mysql, postgres, sqlite, mariadb 或者 mssql.
* @param {string} [options.dialectModule=null] - 如果指定,请使用此数据库。例如,如果在连接到pg数据库时希望使用pg.js而不是pg,则应在此处指定“require”(“pg.js”)”
* @param {string} [options.dialectModulePath=null] - 如果指定,请从此路径加载数据库。例如,如果要在连接到pg数据库时使用pg.js而不是pg,则应在此处指定“/path/to/pg.js”
* @param {object} [options.dialectOptions] - 附加选项的对象,这些选项直接传递到连接库
* @param {string} [options.storage] - 仅由sqlite使用。默认设置为:内存:'
* @param {string} [options.protocol='tcp'] - 关系数据库的协议。
* @param {object} [options.define={}] - 模型定义的默认选项。请参阅{@link Model.init}。
* @param {object} [options.query={}] - Default options for sequelize.query
* @param {string} [options.schema=null] - 要使用的模式
* @param {object} [options.set={}] - sequelize.set的默认选项
* @param {object} [options.sync={}] - sequelize.sync的默认选项
* @param {string} [options.timezone='+00:00'] - 将数据库中的日期转换为JavaScript日期时使用的时区。时区还用于在连接到服务器时设置时区,以确保NOW、CURRENT_TIMESTAMP和其他与时间相关的功能的结果位于正确的时区。为获得最佳跨平台性能,请使用+/-HH:MM格式。还将接受moment.js使用的时区的字符串版本(例如“America/Los_Angeles”);这对于捕获夏令时更改非常有用。
* @param {string|boolean} [options.clientMinMessages='warning'] - PostgreSQL的'client_min_messages'会话参数。设置为'false',不覆盖数据库的默认值。
* @param {boolean} [options.standardConformingStrings=true] - PostgreSQL的“standard_conforming_strings”会话参数。设置为'false'不设置选项。警告:将此设置为false可能会暴露漏洞,不建议使用!
* @param {Function} [options.logging=console.log] - 一个每次Sequelize都会执行的函数会记录一些东西。函数可以接收多个参数,但只有第一个参数由“console.log”打印。要打印所有值,请使用“(…msg)=>console.log(msg)`
* @param {boolean} [options.benchmark=false] - 将查询执行时间(毫秒)作为第二个参数传递给日志函数(options.logging)。
* @param {boolean} [options.omitNull=false] - 定义是否应将空值作为值传递以创建/更新SQL查询的标志。
* @param {boolean} [options.native=false] - 定义是否应使用本机库的标志。目前只对postgres有效
* @param {boolean} [options.replication=false] - 使用读/写复制。要启用复制,请传递一个具有两个属性(读和写)的对象。写入应该是一个对象(处理写入的单个服务器),读取一个对象数组(处理读取的多个服务器)。每个读/写服务器可以具有以下属性:`host`、`port`、`username`、`password`、`database``
* @param {object} [options.pool] - 续集连接池配置
* @param {number} [options.pool.max=5] - 池中的最大连接数
* @param {number} [options.pool.min=0] - 池中的最小连接数
* @param {number} [options.pool.idle=10000] - 在释放连接之前,连接可以处于空闲状态的最长时间(以毫秒为单位)。
* @param {number} [options.pool.acquire=60000] - 该池在引发错误之前尝试获取连接的最长时间(毫秒)
* @param {number} [options.pool.evict=1000] - sequelize池删除空闲连接的时间间隔(毫秒)。
* @param {Function} [options.pool.validate] - 验证连接的函数。打电话给客户。默认函数检查客户机是否为对象,以及其状态是否未断开连接
* @param {number} [options.pool.maxUses=Infinity] - 在放弃连接进行替换之前可以使用连接的次数,[`用于最终的群集重新平衡`](https://github.com/sequelize/sequelize-pool).
* @param {boolean} [options.quoteIdentifiers=true] - 设置为'false',使Postgres上的表名和属性不区分大小写,并跳过它们的双引号。警告:将此设置为false可能会暴露漏洞,不建议使用!
* @param {string} [options.transactionType='DEFERRED'] - 设置默认事务类型。有关可能的选项,请参见“Sequelize.Transaction.TYPES”。仅限Sqlite。
* @param {string} [options.isolationLevel] - 设置默认事务隔离级别。有关可能的选项,请参见“Sequelize.Transaction.ISOLATION_Level”。
* @param {object} [options.retry] - 控制何时自动重试查询的标志集。接受[`按承诺重试`]的所有选项(https://github.com/mickhansen/retry-as-promised).
* @param {Array} [options.retry.match] - 仅当错误与这些字符串之一匹配时,才重试查询。
* @param {number} [options.retry.max] - 自动重试失败查询的次数。设置为0可禁用在SQL\u忙错误时重试。
* @param {boolean} [options.typeValidation=false] - 在insert和update上运行内置类型验证程序,并选择with where子句,例如,验证传递给integer字段的参数是否与integer类似。
* @param {object} [options.operatorsAliases] - 基于字符串的运算符别名。传递对象以限制一组别名运算符。
* @param {object} [options.hooks] - 在某些生命周期事件前后调用的全局钩子函数的对象。全局钩子将在为同一事件定义的任何特定于模型的钩子之后运行(有关列表,请参见`Sequelize.model.init()`)。此外,这里还可以定义'beforeConnect()`、'afterConnect()`、'beforeDisconnect()`和'afterDisconnect()`挂钩。
* @param {boolean} [options.minifyAliases=false] - 定义是否应缩小别名的标志(主要用于避免Postgres别名字符限制为64)
* @param {boolean} [options.logQueryParameters=false] - 定义是否在日志中显示绑定参数的标志。
*/
export default {
database: 'dbtest',
username: 'root',
password: '123456',
options: {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306
}
}
// db/index.js
import Sequelize from 'sequelize';
import DBConf from './conf';
const sequelize = new Sequelize(DBConf);
/**
* @description: 检测数据库连接是否正常
*/
async function checkDBConnect() {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
}
checkDBConnect();
export default sequelize;
// model/user.js
import { DataTypes, Model } from 'sequelize';
import { Sequelize } from '../db';
/**
* 在 Sequelize 中可以用两种等效的方式定义模型:
* a、调用 sequelize.define(modelName, attributes, options)
* b、扩展 Model 并调用 init(attributes, options)
*/
/**
* 以 a 方式创建用户模型
*/
// const User = Sequelize.define('User', {
// // 自定义模型属性
// }, {
// // 其他模型参数
// })
class User extends Model {}
User.init({
// 在这里定义模型属性
id: {
type: DataTypes.INET,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
}
}, {
// 这是其他模型参数
Sequelize, // 我们需要传递连接实例
modelName: 'User', // 我们需要选择模型名称
tableName: 'User', // 表名
createdAt: false,
updatedAt: false,
timestamps: false
});
/**
* 定义的模型是类本身
* 即:User === Sequelize.models.User
*/
export default User;
// redis/conf.js
/**
* @description: Redis Server Config
* @param {number} [port=6379] - 端口
* @param {string} [host=localhost] - 主机
* @param {number} [family=4] - 4 (IPv4) or 6 (IPv6)
* @param {string} [path=null] - 本地 domain socket 路径
* 注意:如果设置了 port 那么 host 和 family 字段则会被忽略
* @param {number} [keepAlive=0] - 在开始启用之前,TCP 可以在 socket 上有多少毫秒的延迟
* 如果给 keepAlive 设置了非 number 类型的值,则禁用 keepAlive
* @param {boolean} [noDelay=true] - 是否禁用 Nagle 算法。默认情况下,我们禁用它以减少延迟
* @param {string} [connectionName=null] - 连接名称
* @param {number} [db=0] - 要使用的数据库索引
* @param {string} [password=null] - 如果设置了,客户端将在连接时发送带有此选项值的 AUTH 命令
* @param {string} [username=null] - 类似于 password ,为 Redis ACL 支持提供此选项
* @param {string} [dropBufferSupport=false] - 放弃缓冲区支持以获得更好的性能。建议在处理大型阵列响应时启用此选项,并且您不需要缓冲区支持。
* @param {boolean} [enableReadyCheck=true] - 当与 Redis 服务器建立连接时,服务器可能仍在从磁盘加载数据库。加载时,服务器不会响应任何命令。要解决此问题,当此选项为 true 时,ioredis 将检查 Redis 服务器的状态,当 Redis 服务器能够处理命令时,将发出 ready 事件。
* @param {boolean} [enableOfflineQueue=true] - 默认情况下,如果没有到 Redis 服务器的活动连接,则会将命令添加到队列中,并在连接“就绪”后执行(当 enableReadyCheck 为 true 时,“就绪”表示 Redis 服务器已从磁盘加载数据库,否则表示已建立到 Redis 服务器的连接)。如果此选项为 false ,则在连接未就绪时执行命令时,将返回一个错误。
* @param {number} [connectTimeout=10000] - 在初始连接到 Redis 服务器期间发生超时之前的毫秒数。
* @param {boolean} [autoResubscribe=true] - 重新连接后,如果以前的连接处于订阅模式,客户端将自动重新订阅这些通道。
* @param {boolean} [autoResendUnfulfilledCommands=true] - 如果为 true ,则在重新连接时,客户端将在上一个连接中重新发送未执行的命令(例如块命令)。
* @param {boolean} [lazyConnect=false] - 默认情况下,创建新的 Redis 实例时,它将自动连接到 Redis 服务器。如果要在调用命令之前保持实例断开连接,可以将 lazyConnect 选项传递给构造函数
* @param {Object} [tls] - TLS连接支持. 详情请查阅 https://github.com/luin/ioredis#tls-options
* @param {string} [keyPrefix=''] - 命令中所有键的前缀
* @param {function} [retryStrategy] - 请参阅“快速启动”部分
* @param {number} [maxRetriesPerRequest] - 请参阅“快速启动”部分
* @param {number} [maxLoadingRetryTime=10000] - 当 redis 服务器未就绪时,我们将等待 info 命令的'loading_eta_seconds'或 maxLoadingRetryTime(毫秒),以较小者为准。
* @param {function} [reconnectOnError] - 请参阅“快速启动”部分
* @param {boolean} [readOnly=false] - 为连接启用只读模式。仅适用于群集模式。
* @param {boolean} [stringNumbers=false] - 强制数字始终作为JavaScript字符串返回。处理大数字(超过[-2^53,+2^53]范围)时,此选项是必需的。
* @param {boolean} [enableTLSForSentinelMode=false] - 通过sentinel模式连接Redis时是否支持“tls”选项。
* @param {NatMap} [natMap=null] - sentinel连接器的NAT映射
* @param {boolean} [updateSentinels=true] - 在与现有"sentinel"通信时,使用新的IP地址更新给定的"sentinel"列表。
* @param {boolean} [failoverDetector=false] - 通过订阅相关通道主动检测故障转移。禁用此选项后,ioredis 仍然能够检测故障转移,因为每当发生故障转移时,Redis Sentinel 将断开所有客户端的连接,因此 ioredis 将重新连接到新的主机。当您希望更快地检测故障转移时,此选项非常有用,它将创建到 Redis 服务器的更多 TCP 连接,以便订阅相关通道
* @param {boolean} [enableAutoPipelining=false] - 启用时,事件循环迭代期间发出的所有命令将自动包装在管道中,并同时发送到服务器。这可以显著提高性能
* @param {string[]} [autoPipeliningIgnoredCommands=[]] - 不能自动包装在管道中的命令列表。
* @param {number} [maxScriptsCachingTime=60000] - 默认脚本定义缓存时间
* @extends [EventEmitter] - 详情请查阅 http://nodejs.org/api/events.html#events_class_events_eventemitter
* @extends Commander
*/
export default {
port: 6379, // 如果设置了 port ,则 host 和 family 不生效
host: 'localhost',
family: 4,
path: null,
keepAlive: 0,
noDelay: true,
connectionName: null,
db: 0,
password: null,
username: null,
dropBufferSupport: false,
enableReadyCheck: true,
enableOfflineQueue: true,
connectTimeout: 10000,
autoResubscribe: true,
autoResendUnfulfilledCommands: true,
lazyConnect: false,
// tls: {},
keyPrefix: '',
// retryStrategy: () => {},
// maxRetriesPerRequest: 0,
maxLoadingRetryTime: 10000,
// reconnectOnError: () => {},
readOnly: false,
stringNumbers: false,
enableTLSForSentinelMode: false,
natMap: null,
updateSentinels: true,
failoverDetector: false,
enableAutoPipelining: false,
autoPipeliningIgnoredCommands: [],
maxScriptsCachingTime: 60000
}
// redis/index.js
import Redis from 'ioredis';
import RedisConf from './conf';
const redis = new Redis(RedisConf);
export default redis;
// router/conf.js
/**
* @description: Koa Router Config
* @param {string} [prefix=''] - 路由前缀
* @param {array} [methods=['HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE']] - 支持的请求方法
*/
export default {
prefix: '',
methods: ['HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE']
}
// router/index.js
import Router from '@koa/router';
import RouterConf from './conf';
const router = new Router(RouterConf);
export default function (controllers) {
for (let key in controllers) {
const Temp = controllers[key];
let controller = new Temp();
let routes = [];
controller.routers.forEach(r => {
router[r.method](Temp.prefix + (r.url === '/' ? '' : r.url), controller[r.name]);
let route = {
name: controller[r.name].name,
method: r.method,
url: Temp.prefix + (r.url === '/' ? '' : r.url)
}
routes.push(route);
})
console.log(`${key}注册成功的路由有:\n\t${JSON.stringify(routes)}`);
}
return router;
}
// services/baseServices.js
export default class BaseService {
constructor() {
this.model = {}
}
/**
* @description: 获取
* @param {string|number} id - 唯一ID
*/
async getById(id) {
return await this.model.findByPk(id)
}
async getList(where) {
return await this.model.findAndCountAll({
where,
offset: 0,
limit: 10
})
}
async save(data) {
if (data.id) {
return this.update(data)
}
console.log(data, 'save')
return await this.add(data)
}
async update(data) {
return await this.model.update(data, {
where: {
id: data.id
}
})
}
async add(data) {
return await this.model.create(data)
}
async del(id) {
return this.model.destroy({
where: {
id
}
})
}
}
// services/user.js
import BaseService from './baseService';
import UserModel from '../model/user';
export default class UserService extends BaseService {
constructor() {
super()
this.model = UserModel;
}
}
// utils/index.js
import os from 'os';
import logColors from 'node-console-colors';
/**
* @description: 获取本地和网络的IPv4地址
* @return {object} - localIp: 本地IP,networkIpList: 网络IP列表
*/
export const getLocalAndNetworkIP = () => {
const networkInterfaces = os.networkInterfaces();
let localIp = '127.0.0.1';
let networkIpList = [];
for (const [key, values] of Object.entries(networkInterfaces)) {
values.forEach(({
address,
family,
internal
}) => {
family === 'IPv4' && internal && (localIp = address);
family === 'IPv4' && !internal && (networkIpList.push(address));
})
}
return {
localIp,
networkIpList
}
}
/**
* @description: 美化 console.log
* @param {array|string} [msg=''] - 消息体
* @param {string} [color=''] - 消息体字体颜色
* @param {string} [bgColor=''] - 消息体背景颜色
*/
export const log = (msg = '', color = '', bgColor = '') => {
const type = typeof msg;
if (type === 'object' && !Array.isArray(msg)) {
msg = JSON.stringify(msg);
}
if (type === 'undefined') {
msg = 'undefined';
}
const colors = ['black', 'dark_gray', 'gray', 'white', 'dark_red', 'red', 'dark_yellow', 'yellow', 'dark_cyan', 'cyan', 'dark_green', 'green', 'dark_blue', 'blue', 'dark_purple', 'purple', 'default'];
if (colors.indexOf(color) === -1) {
color = 'dark_cyan';
}
if (colors.indexOf(bgColor) !== -1) {
if (Array.isArray(msg)) {
console.log(logColors.set('fg_' + color, 'bg_' + bgColor, msg.join()));
} else {
console.log(logColors.set('fg_' + color, 'bg_' + bgColor, msg));
}
} else {
if (Array.isArray(msg)) {
console.log(logColors.set('fg_' + color, msg.join()));
} else {
console.log(logColors.set('fg_' + color, msg));
}
}
}
// index.js
import './app';
未完待续