Browse Source

修复日志问题,将redis,node-cache,log4js,nedb改为插件形式,优化代码

zhusiqing 3 years ago
parent
commit
81c7ada4f0

+ 10 - 5
src/app.ts

@@ -23,8 +23,10 @@ import sessionMiddleware from './middlewares/session';
 // import authMiddleware from './middlewares/auth';
 import localDbMiddleware from './middlewares/localDb';
 
-import { loggerInstance } from '@utils/logger';
 import server from '@utils/server';
+import Plugins from '@/plugins';
+
+const plugins =  Plugins()
 
 const app = new Koa<DefaultState, Context>();
 
@@ -42,12 +44,15 @@ app.use(responseMiddleware());
 // post params解析
 app.use(bodyParser());
 // 日志
-app.use(logsMiddleware());
+app.use(logsMiddleware(plugins.logger));
 
 // 本地嵌入式数据库
-app.use(localDbMiddleware())
+app.use(localDbMiddleware(plugins.db))
 // redis
-app.use(redisMiddleware());
+app.use(redisMiddleware({
+  redis: plugins.redis,
+  memoryCache: plugins.memoryCache
+}));
 // 限流
 app.use(limitMiddleware());
 // session
@@ -66,7 +71,7 @@ app.on('error', (err, ctx: Context) => {
   const errMsg: string = err.message || '服务出错';
   console.log(errMsg);
   console.log(ctx);
-  loggerInstance(ctx).error(errMsg)
+  plugins.logger(ctx).error(errMsg)
 });
 
 // 注册路由

+ 1 - 1
src/config.ts

@@ -25,7 +25,7 @@ export default {
   staticPath: '../public',
   // 本地嵌入式数据库
   localDb: {
-    dir: resolve(__dirname, '../db'),
+    _dir: resolve(__dirname, '../db'),
     user: 'dbUser.json',
     doc: 'dbDoc.json',
     tag: 'dbTag.json'

+ 1 - 0
src/middlewares/auth.ts

@@ -19,6 +19,7 @@ export default () => {
       }
       await next()
     } else {
+      ctx.status = 500
       ctx.throw(500, 'session获取失败')
     }
   }

+ 2 - 0
src/middlewares/limit.ts

@@ -6,12 +6,14 @@ export default () => {
     const isMatch = config.checkHost.find(el => host.match(el))
     if (!isMatch) {
       // 域名不对
+      ctx.status = 403
       ctx.throw(403, '请求出错')
     }
     const ip: string = ctx.ip;
     const key: string = `client_ip_limit_${ip}`;
     const limit: number = Number(await ctx.$redis.get(key)) || 0;
     if (limit > config.limit.times) {
+      ctx.status = 403
       ctx.throw(403, '请求频次过高');
       return
     };

+ 2 - 29
src/middlewares/localDb.ts

@@ -1,33 +1,6 @@
-import Nedb from 'nedb';
-import { resolve } from 'path';
-import config from '@config';
+import { LocalDb } from '@/plugins';
 
-export interface InterfaceDB {
-  user: Nedb
-  doc: Nedb,
-  tag: Nedb
-};
-
-const { localDb } = config;
-
-const filename = name => resolve(localDb.dir, name);
-
-const db: InterfaceDB = {
-  user: new Nedb({
-    filename: filename(localDb.user),
-    autoload: true
-  }),
-  doc: new Nedb({
-    filename: filename(localDb.doc),
-    autoload: true
-  }),
-  tag: new Nedb({
-    filename: filename(localDb.tag),
-    autoload: true
-  })
-};
-
-export default () => {
+export default (db: LocalDb) => {
   return async(ctx, next) => {
     ctx.$db = db;
     await next();

+ 28 - 10
src/middlewares/logs.ts

@@ -1,8 +1,27 @@
 import { Context, Next } from 'koa';
-import { middlewareLog } from '@utils/logger';
+import { InterfaceLogger, commonInfo, output } from '@/plugins/logger';
 
-export default () => {
-  const loggerMiddleware = middlewareLog();
+export default (logger: InterfaceLogger) => {
+  const loggerMiddleware = async(ctx, next) => {
+    const $log = logger(ctx)
+    ctx.$log = $log;
+    const a = Date.now()
+    await next();
+    const b = Date.now()
+    if (ctx.response && ctx.status < 400) {
+      const commonInfo: commonInfo = {
+        ip: ctx.ip,
+        time: b - a
+      };
+      if (ctx.request.body) {
+        commonInfo.params = ctx.request.body;
+      };
+      $log.info(output(ctx, null, commonInfo))
+    }
+    else {
+      ctx.throw(ctx.status, ctx.response);
+    }
+  }
   return async(ctx: Context, next: Next) => {
     // 静态文件不处理
     const isApi = /^\/api/.test(ctx.url);
@@ -10,12 +29,11 @@ export default () => {
       await next();
       return;
     };
-    return loggerMiddleware(ctx, next)
-      .catch(error => {
-        const { body, status = 500, message } = error;
-        const errorText = `res: ${JSON.stringify({ status, message, body })}`;
-        ctx.$log.error(errorText);
-        ctx.status = status
-      });
+    return loggerMiddleware(ctx, next).catch(error => {
+      const { body, status = 500, message } = error;
+      const errorText = `res: ${JSON.stringify({ status, message, body })}`;
+      ctx.$log.error(errorText);
+      ctx.status = status
+    });
   };
 };

+ 10 - 94
src/middlewares/redis.ts

@@ -1,100 +1,16 @@
-import redis from 'redis';
-// 内存存储
-import nodeCache from 'node-cache';
-import util from 'util';
-import consola from 'consola/dist/consola';
-import config from '@config';
-
-// 是否使用redis
-let isUseRedis: boolean = true
-// 内存存储实例化
-const cache = new nodeCache()
-
-// 创建redis连接
-const client = redis.createClient({
-  host: config.redis.host,
-  port: config.redis.port,
-  password: config.redis.password,
-  auth_pass: config.redis.password,
-  retry_strategy: function(options) {
-    if (options.error && options.error.code === "ECONNREFUSED") {
-      // redis连接失败时使用内存存储
-      isUseRedis = false
-      // End reconnecting on a specific error and flush all commands with
-      // a individual error
-      return new Error("The server refused the connection");
-    }
-    if (options.total_retry_time > 1000 * 60 * 60) {
-      // End reconnecting after a specific timeout and flush all commands
-      // with a individual error
-      return new Error("Retry time exhausted");
-    }
-    if (options.attempt > 10) {
-      // End reconnecting with built in error
-      return;
-    }
-    // reconnect after
-    return Math.min(options.attempt * 1000, 3000);
-  }
-});
-client.on('error', err => {
-  consola.error(err);
-});
-client.on('ready', () => {
-  consola.success('redis is ready');
-});
-client.on('connect', () => {
-  consola.success('redis is connecting');
-  isUseRedis = true
-});
-client.on('reconnecting', () => {
-  consola.success('redis is reconnecting...');
-});
-client.on('end', () => {
-  consola.success('redis is closed');
-});
-
-// redis查询和设置await/async化
-const promisify = util.promisify;
-const getClient = (key) => promisify(client.get).bind(client)(key);
-const setClient = (key, value) => promisify(client.set).bind(client)(key, value);
-const setexClient = (key, ttl, value) => promisify(client.setex).bind(client)(key, ttl, value);
-
-// 内存存储promise化,为了和redis使用方法保持一致
-const getCache = (key: string): Promise<string|undefined> => {
-  console.log(key);
-  return new Promise(resolve => {
-    resolve(cache.get(key))
-  })
-}
-const setCache = (key: string, ttl: number = 0, value: string): Promise<boolean> => {
-  return new Promise((resolve, reject) => {
-    const success = cache.set(key, value, ttl)
-    if (success) {
-      resolve(success)
-    } else {
-      reject(success)
-    }
-  })
-}
-const setexCache = (key: string, ttl: number, value: string, ) => setCache(key, ttl, value)
-
-// 初始化测试
-// client.setex('test', 30, '111111');
-// send_sms_verify_code_sms_13500001111
+import { Redis, MemoryCache } from '@/plugins';
+interface InterfaceCache {
+  redis: Redis,
+  memoryCache: MemoryCache
+};
 
-export default () => {
+export default (cache: InterfaceCache) => {
   return async (ctx, next) => {
-    consola.info('redis is available:', isUseRedis);
-    if (isUseRedis) { // 如果redis连接失败,采用内存
-      ctx.$redis = { set: setClient, get: getClient, setex: setexClient };
+    if (cache.redis.isUseRedis) { // 如果redis连接失败,采用内存
+      ctx.$redis = cache.redis;
     } else {
-      ctx.$redis = {
-        set: setCache,
-        get: getCache,
-        setex: setexCache
-      }
-    }
+      ctx.$redis =cache.memoryCache
+    };
     await next();
   };
 };

+ 53 - 0
src/plugins/index.ts

@@ -0,0 +1,53 @@
+import { RetryStrategyOptions } from 'redis';
+import config from '@config';
+import RedisPlugin from '@/plugins/redis';
+import MemoryCachePlugin from '@/plugins/memoryCache';
+import LocalDbPlugin from '@/plugins/localDb';
+// TODO: 后面也采用class封装一下
+import { loggerInstance } from '@/plugins/logger';
+
+export type Redis = RedisPlugin
+export type MemoryCache = MemoryCachePlugin
+export type LocalDb = LocalDbPlugin
+const { redis: redisOpt, localDb: localDbOpt } = config
+export default () => {
+  const redisOption = {
+    host: redisOpt.host,
+    port: redisOpt.port,
+    password: redisOpt.password,
+    auth_pass: redisOpt.password,
+    retry_strategy: function(this: RedisPlugin, opts: RetryStrategyOptions) {
+      if (opts.error && opts.error.code === "ECONNREFUSED") {
+        opts.error.code
+        // redis连接失败时使用内存存储
+        this.isUseRedis = false
+        // End reconnecting on a specific error and flush all commands with
+        // a individual error
+        return new Error("The server refused the connection");
+      }
+      if (opts.total_retry_time > 1000 * 60 * 60) {
+        // End reconnecting after a specific timeout and flush all commands
+        // with a individual error
+        return new Error("Retry time exhausted");
+      }
+      if (opts.attempt > 10) {
+        // End reconnecting with built in error
+        return;
+      }
+      // reconnect after
+      return Math.min(opts.attempt * 1000, 3000);
+    }
+  }
+  const localDbOption = localDbOpt
+
+  const redis = new RedisPlugin(redisOption)
+  const memoryCache = new MemoryCachePlugin()
+  const localDb = new LocalDbPlugin(localDbOption)
+
+  return {
+    redis,
+    memoryCache,
+    db: localDb,
+    logger: loggerInstance
+  }
+}

+ 26 - 0
src/plugins/localDb.ts

@@ -0,0 +1,26 @@
+import Nedb from 'nedb';
+import { resolve } from 'path';
+import config from '@config';
+
+export interface InterfaceDB {
+  [key: string]: Nedb
+};
+
+const { localDb } = config;
+const filename = name => resolve(localDb._dir, name);
+
+class LocalDb {
+  db: InterfaceDB
+  constructor(options) {
+    const db: InterfaceDB = {}
+    const { _dir, ...opts } = options
+    Object.entries(opts).forEach(([key, value]) => {
+      db[key] = new Nedb({
+        filename: filename(value),
+        autoload: true
+      })
+    })
+    this.db = db
+  }
+}
+export default LocalDb

+ 26 - 51
src/utils/logger.ts → src/plugins/logger.ts

@@ -1,14 +1,14 @@
-import log4js from 'log4js';
+import log4js, { DateFileAppender, ConsoleAppender } from 'log4js';
 import path from 'path';
 import { Context } from 'koa';
 import config from '@config';
-interface commonInfo {
-  serverIp: string,
+export interface commonInfo {
+  ip: string,
   params?: any,
   time?: number
 }
 // 输出配置
-const output = (ctx, message: string | null | undefined , commonInfo: commonInfo) => {
+export const output = (ctx, message: string | null | undefined , commonInfo: commonInfo) => {
   const {
     method, // 请求方式
     headers, // 请求headers
@@ -31,7 +31,7 @@ const output = (ctx, message: string | null | undefined , commonInfo: commonInfo
   if (ctx.querystring) {
     client.query = ctx.querystring;
   };
-  let text = `[${method}] ${ctx.status} [${origin}${originalUrl}] ${time}ms
+  let text = `[${method}] ${ctx.status} [${origin}${originalUrl}] ${time ? time + 'ms' : ''}
 req: ${JSON.stringify(Object.assign(client, newCommonInfo))}`;
 
   if (Object.prototype.toString.call(message) === '[object Object]'
@@ -51,28 +51,30 @@ const methods: string[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', '
 const defaultConfig = {
   logLevel: 'debug', // 日志级别
   dir: path.resolve(__dirname, '../log'), // 指定日志存放目录名
-  env: 'development', // 指定当前环境,当开发时控制台也输出
-  serverIp: '127.0.0.1'
+  env: 'development' // 指定当前环境,当开发时控制台也输出
 };
 
 // log配置
 const opt = Object.assign({}, defaultConfig, config.logs);
-const { env, appLogLevel, dir, serverIp } = opt;
+const { env, appLogLevel, dir } = opt;
 /**
  * 记录日志的方式
  * 指定要记录的日志分类 log
  * 展示方式为文件类型 dateFile
  * 日志输出的文件名 s-yyyy-MM-dd.log
  */
+// interface appenders {
+//   [key: string]: {
+//     type: string,
+//     filename?: string,
+//     pattern?: string,
+//     alwaysIncludePattern?: boolean,
+//     ip?: string,
+//     layout: object
+//   }
+// }
 interface appenders {
-  [log: string]: {
-    type: string,
-    filename?: string,
-    pattern?: string,
-    alwaysIncludePattern?: boolean,
-    serverIp?: string,
-    layout: object
-  }
+  [key: string]: DateFileAppender | ConsoleAppender
 }
 const appenders: appenders = {
   log: {
@@ -82,7 +84,6 @@ const appenders: appenders = {
     filename: `${dir}/z`,
     pattern: 'yyyy-MM-dd.log',
     alwaysIncludePattern: true,
-    serverIp,
     // 日志输出格式
     layout: {
       type: 'pattern',
@@ -127,7 +128,7 @@ const logger = log4js.getLogger('log');
 interface logFn {
   (message: any, ...args: any[]): void;
 }
-interface interfaceLog {
+export interface InterfaceLog {
   trace: logFn
   debug: logFn
   info: logFn
@@ -137,8 +138,12 @@ interface interfaceLog {
   mark: logFn
 }
 
-export const loggerInstance = (ctx: Context): interfaceLog => {
-  const $log: interfaceLog = {
+export interface InterfaceLogger {
+  (ctx: Context): InterfaceLog
+}
+
+export const loggerInstance: InterfaceLogger = (ctx: Context): InterfaceLog => {
+  const $log: InterfaceLog = {
     trace: () => {},
     debug: () => {},
     info: () => {},
@@ -149,38 +154,8 @@ export const loggerInstance = (ctx: Context): interfaceLog => {
   };
   methods.map(el => {
     $log[el] = (message) => {
-      logger[el](output(ctx, message, { serverIp }));
+      logger[el](output(ctx, message, { ip: ctx.ip }));
     };
   });
   return $log;
 }
-
-export const middlewareLog = () => {
-  // koa上下文存放的log输出方法
-  const contextLogger = {};
-
-  return async(ctx, next) => {
-    methods.map(el => {
-      contextLogger[el] = (message) => {
-        logger[el](output(ctx, message, { serverIp }));
-      };
-    });
-    ctx.$log = contextLogger;
-    const a = Date.now()
-    await next();
-    const b = Date.now()
-    if (ctx.response && ctx.status < 400) {
-      const commonInfo: commonInfo = {
-        serverIp,
-        time: b - a
-      };
-      if (ctx.request.body) {
-        commonInfo.params = ctx.request.body;
-      };
-      logger.info(output(ctx, null, commonInfo))
-    }
-    else {
-      ctx.throw(ctx.status, ctx.response);
-    }
-  }
-}

+ 31 - 0
src/plugins/memoryCache.ts

@@ -0,0 +1,31 @@
+// 内存存储
+import nodeCache, { Options, NodeCacheLegacyCallbacks } from 'node-cache';
+class MemoryCache {
+  _cache: NodeCacheLegacyCallbacks
+  constructor(options?: Options) {
+    // 内存存储实例化
+    this._cache = new nodeCache(options)
+  }
+  // 内存存储promise化,为了和redis使用方法保持一致
+  get = (key: string): Promise<string|undefined> => {
+    console.log(key);
+    return new Promise(resolve => {
+      resolve(this._cache.get(key))
+    })
+  }
+  set = (key: string, ttl: number = 0, value: string): Promise<boolean> => {
+    return new Promise((resolve, reject) => {
+      const success = this._cache.set(key, value, ttl)
+      if (success) {
+        resolve(success)
+      } else {
+        reject(success)
+      }
+    })
+  }
+  setex = (key: string, ttl: number, value: string, ) => this.set(key, ttl, value)
+}
+
+
+
+export default MemoryCache

+ 52 - 0
src/plugins/redis.ts

@@ -0,0 +1,52 @@
+import redis, { ClientOpts, RedisClient } from 'redis';
+import util from 'util';
+import consola from 'consola';
+
+
+// node err回调改为promise工具库
+const promisify = util.promisify;
+class Redis {
+  _options: ClientOpts = {};
+  // 是否使用redis
+  isUseRedis: boolean = false;
+  client: RedisClient | null;
+  constructor(options: ClientOpts) {
+    this._options = options
+    const client = this.initClient(options)
+    this.client = client
+  }
+  initClient(options: ClientOpts) {
+    // 创建redis连接
+    const client = redis.createClient(options)
+    client.on('error', err => {
+      consola.error(err);
+    });
+    client.on('ready', () => {
+      consola.success('redis is ready');
+      this.init(client)
+    });
+    client.on('connect', () => {
+      consola.success('redis is connected');
+      this.isUseRedis = true
+    });
+    client.on('reconnecting', () => {
+      consola.warn('redis is reconnecting...');
+    });
+    client.on('end', () => {
+      consola.warn('redis is closed');
+    });
+    return client
+  }
+  get = (key) => this.client?.get &&  promisify(this.client.get).bind(this.client)(key)
+  set = (key, value) =>this.client?.set && promisify(this.client.set).bind(this.client)(key, value)
+  setex = (key, ttl, value) => this.client?.setex && promisify(this.client.setex).bind(this.client)(key, ttl, value)
+  init(client) {
+    // redis查询和设置await/async化
+    this.get = (key) => promisify(client.get).bind(this.client)(key);
+    this.set = (key, value) => promisify(client.set).bind(this.client)(key, value)
+    this.setex = (key, ttl, value) => promisify(client.setex).bind(this.client)(key, ttl, value)
+  }
+
+}
+
+export default Redis

+ 2 - 6
src/typings/index.d.ts

@@ -3,12 +3,8 @@ import { IMiddleware } from 'koa-router';
 import { RedisClient } from 'redis';
 import { responseHandle } from '@utils/response';
 import { InterfaceDB } from '../middlewares/localDb';
+import { Redis } from '@/plugins';
 
-interface interfaceRedis {
-  get: (key: string) => Promise<string>
-  set: (key: string, value: string) => Promise<void>
-  setex: (key: string, time: number, value: string) => Promise<void>
-}
 interface logFn {
   (message: any, ...args: any[]): void;
 }
@@ -24,7 +20,7 @@ interface interfaceLog {
 
 declare module 'koa' {
   export interface Context extends DefaultContext {
-    $redis: interfaceRedis
+    $redis: Redis
     $response: responseHandle
     $log: interfaceLog
     $db: InterfaceDB