koa结合koa-router开发nodejs服务

本文档详细介绍了使用Koa构建服务端项目的过程,包括项目初始化、目录结构、配置文件、依赖安装、Babel配置、数据库和Redis配置、路由设置、权限验证以及控制器和模型的创建。项目采用模块化设计,支持HTTP和HTTPS服务,集成数据库操作、路由处理、中间件和权限验证功能。
摘要由CSDN通过智能技术生成
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';

未完待续

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这是一个比较具体的问题,我尽量给出详细的回答。 首先,你需要了解每个工具的作用: - Node.js: 一个基于 Chrome V8 引擎的 JavaScript 运行时,可用于构建高性能的网络应用程序。 - Midway.js: 一个基于 Egg.js 的 Serverless Node.js 框架,提供了函数计算、API 网关等 Serverless 场景下的解决方案。 - Koa: 一个基于 Node.js 平台的 Web 开发框架,旨在提供更小、更富有表现力、更健壮的 Web 应用程序。 - TypeScript: 是 JavaScript 的一个超集,可以编译成纯 JavaScript。 接下来,你可以按照以下步骤进行开发: 1. 创建一个基于 Midway.js 的应用程序: ``` $ npm init midwayjs-app jsapi ``` 2. 安装需要的依赖: ``` $ cd jsapi $ npm install koa koa-bodyparser koa-router cross-env --save $ npm install @midwayjs/koa @midwayjs/faas @midwayjs/socketio --save $ npm install typescript ts-node @types/node --save-dev ``` 3. 创建 TypeScript 配置文件 `tsconfig.json`: ``` { "extends": "@midwayjs/tsconfig/tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"] } ``` 4. 创建一个简单的 TypeScript 控制器: ``` import { Provide, Controller, Get, Post, Body } from '@midwayjs/decorator'; import { Context } from 'koa'; @Provide() @Controller('/api') export class ApiController { @Get('/hello') async hello(ctx: Context) { ctx.body = 'Hello Midwayjs'; } @Post('/share') async share(@Body() body) { const { url } = body; // 解析分享链接 // ... // 返回解析结果 return { title, desc }; } } ``` 5. 创建 Koa 应用程序: ``` import { App, Config, Inject } from '@midwayjs/decorator'; import { ILifeCycle, IMidwayApplication } from '@midwayjs/core'; import { Application } from 'egg'; import * as bodyParser from 'koa-bodyparser'; import * as Router from 'koa-router'; @Config() export class AppConfig implements ILifeCycle { @Inject() app: IMidwayApplication; async onReady() { const app = this.app.getApplication() as Application; // 添加中间件 app.use(bodyParser()); app.use(app.get('koaRouter').routes()); // 启动应用程序 app.listen(3000, () => { console.log('Server listening on http://localhost:3000'); }); } } @App() export class ApiApplication implements ILifeCycle { @Inject() app: IMidwayApplication; async onReady() { const router = this.app.getApplicationContext().get('koaRouter') as Router; router.get('/api/hello', async (ctx) => { ctx.body = 'Hello Koa'; }); } } ``` 6. 启动应用程序: ``` $ npm run dev ``` 7. 使用 curl 或者 Postman 等工具测试 API: ``` $ curl -X GET http://localhost:3000/api/hello ``` ``` $ curl -X POST http://localhost:3000/api/share -d '{"url": "http://example.com"}' ``` 这样,一个基于 Node.js、Midway.js、Koa、TypeScript 的 API 解析程序就完成了。当然,这只是一个简单的示例,你可以根据自己的需求进行修改和扩展。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值