实践[typescript] node工具类实现

170 阅读6分钟

项目地址:node-utils

该项目是我使用的一个小工具类,也是我自己编写的,但是还不是很完善,目前只针对日志进行处理,实现了

  • yml文件解析
  • 控制台日志输出
  • 日志之持久化存储等功能

类图

image.png

测试文件

import {Logger} from '../src/index'
const logger = new Logger.Logger()
const testDemo:Array<any> = [
  "string",
  12313123,
  false,
  null,
  undefined,
  [1,2,3,4,5],
  {name:"test"},
  Symbol("test")
]
setInterval(()=>{
  testDemo.forEach((item)=>{
    logger.info(item)
  })
  logger.info("测试")
  logger.warn("测试")
  logger.error("测试")
},500)

可输出的结果

[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  false
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  初始化node.utils.config.yml
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  { log: { pid: true, console: true, time: true } }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  { pid: true, console: true, time: true }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  string
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  12313123
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  false
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  null
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  undefined
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  [ 1, 2, 3, 4, 5 ]
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  { name: 'test' }
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  Symbol(test)
[2022/1/11 下午3:28:37 INFO ] (pid: 12068) :  测试
[2022/1/11 下午3:28:37 WARN ] (pid: 12068) :  测试
[2022/1/11 下午3:28:37 ERROR ] (pid: 12068) :  测试

使用到的依赖

    "moment": "^2.29.1"      日期处理库
    "yamljs": "^0.3.0"       yaml文件解析库

目录文件结构

├── src
│   ├── config                配置类
│   │   ├── abstractConfig.ts 抽象配置类
│   │   ├── configHandler.ts  配置处理类
│   │   ├── config.ts         配置处理接口定义
│   │   └── configYaml.ts     针对yaml文件的操作配置
│   ├── index.ts
│   └── modules
│       └── log
│           ├── color.ts
│           ├── index.ts
│           └── log.ts
├── test
│   └── test.ts

该项目使用typescript作为基础编程语言,分为了三大块,分别是配置处理,模块集,测试

配置处理模块

  1. abstractConfig.ts

该类主要是作为主要的抽象继承类,包含了一些路径初始化的声明,配置文件处理和获取配置文件。这样做的目的是为了兼容和扩展更多的配置文件类型,并且不需要修改高层抽象模块,只需要针对相应的模块单独编写实现类即可,这也是开闭原则和依赖倒置原则的体现

export abstract class abstractLogConfig<T> {
  // 配置url
  protected path !: string;
  constructor(path: string){
    this.path = path
  }
  abstract handler():this
  abstract get(): T|null
}

2 configYaml.ts

该代码文件是针对yaml实现的解析,可以看到该逻辑非常简单由于使用了yamljs方法,就不需要自己单独实现处理,而直接解析即可

export class LogConfigYaml extends abstractLogConfig<ConfigI> {
  private config ?:ConfigI
  constructor(path:string) {
    super(path)
    this.init()
  }
  init(){
    const val = yamljs.load(this.path)
    if(val) {
      this.config=val
    }
  }

  handler(): this {
    throw new Error("Method not implemented.");
  }

  get(): ConfigI | null {
    return this.config || null
  }
}
  1. configHandler.ts

该文件是对config的一些处理函数,暂时只用到了isNodeUtilsConfigYml这一个函数,目的是判断文件路径是否有权限操作,这里用到了read和write连个权限等级

export class configHandler{
  static isNodeUtilsConfigYml(filePath: string): boolean{
    try {
      fs.accessSync(filePath,constants.R_OK|constants.W_OK)
      return true
    } catch {      
      return false
    }
  }
}

这部分的判断逻辑是比较有意思的,它是利用一个变量存储,通过位或运算拼接成一个变量,具体操作请看fsconstants文档

constants.R_OK 4 对应的二进制为 0100
constants.W_OK 2 对应的二进制为 0010
位或运算得到 6 也就是   0110

这种实现在现在的vue3和Linux内核的文件系统模块中经常使用
  1. config.ts

该文件主要是定义了输入输出,日志配置,配置实现等接口声明

export interface OutConfigI {
  /**
   * 文件名格式
   */
  name?: string,
  /**
   * 输出路径
   */
  path?: string,
  /**
   * 输出文件后缀
   */
  ext?: string
} 
export interface LoggerConfig{
  /**
   * 输出时间
   */
  time?: boolean,
  /**
   * 输出到控制台
   */
  console?: boolean,
  /**
   * 输出pid编号
   */
  pid?: boolean,
  /**
   * 日志输出文件路径
   */
  out?: OutConfigI
}
export interface ConfigI {
  /**
   * 日志配置
   */
  log?: LoggerConfig
}

modules模块

  1. color.ts

该模块定义了日志在不同级别下的控制台样式

export enum STYLE_COLOR {
  'bold'          = '\x1B[1m%s\x1B[22m',
  'italic'        = '\x1B[3m%s\x1B[23m',
  'underline'     = '\x1B[4m%s\x1B[24m',
  'inverse'       = '\x1B[7m%s\x1B[27m',
  'strikethrough' = '\x1B[9m%s\x1B[29m',
  'white'         = '\x1B[37m%s\x1B[39m',
  'grey'          = '\x1B[90m%s\x1B[39m',
  'black'         = '\x1B[30m%s\x1B[39m',
  'blue'          = '\x1B[34m%s\x1B[39m',
  'cyan'          = '\x1B[36m%s\x1B[39m',
  'green'         = '\x1B[32m%s\x1B[39m',
  'magenta'       = '\x1B[35m%s\x1B[39m',
  'red'           = '\x1B[31m%s\x1B[39m',
  'yellow'        = '\x1B[33m%s\x1B[39m',
  'whiteBG'       = '\x1B[47m%s\x1B[49m',
  'greyBG'        = '\x1B[49;5;8m%s\x1B[49m',
  'blackBG'       = '\x1B[40m%s\x1B[49m',
  'blueBG'        = '\x1B[44m%s\x1B[49m',
  'cyanBG'        = '\x1B[46m%s\x1B[49m',
  'greenBG'       = '\x1B[42m%s\x1B[49m',
  'magentaBG'     = '\x1B[45m%s\x1B[49m',
  'redBG'         = '\x1B[41m%s\x1B[49m',
  'yellowBG'      = '\x1B[43m%s\x1B[49m'
}
  1. log.ts

这个代码主要实现了日志的输出和针对yaml实现配置的类,说实话写的不算好,像文件文件存储操作完全可以分离出去单独作为一个文件扩展,虽然现在也是实现了,但是在一个文件内写入,还是有点膈应,应该作为模块引入。

该模块主要实现了日志的打印输出,指定了三种日志格式(这三种日志格式也可以作为日志类引入,这样以后扩展的时候就不需要修改该文件了)

该文件包含了三大块内容

  • 初始化配置文件
  • 日志打印输出
  • 日志持久化输出

日志持久化是通过定义写入流实现数据写入,避免了重复打开文件带来的IO开销和进程资源调度开销

export interface LoggerOutPutI {
  info (...args: any): void
  warn (...args: any): void
  error(...args: any): void
}
export enum LOG_TYPE{
  start = "START",
  info  = "INFO",
  warn  = "WARN",
  error = "ERROR"
}
// 获取命令所在的路径
const base_path = resolve("./",'node.utils.config.yml');
export class Logger implements LoggerOutPutI{
  config: LoggerConfig = {
    time: true,
    console: true,
    pid : true
  }
  writeFile :WriteFile|undefined;
  constructor(config ?:LoggerConfig|undefined) {
    this.start(typeof config === 'string')
    if (typeof config === 'object'){
      this.config = config
    } else if(!config && configHandler.isNodeUtilsConfigYml(base_path)) { // 如果不存在就对其取反
      this.initConfigYml()
    }
    this.initConfig()
  }
  // 初始化本类配置
  initConfig(){
    if(this.config){
      if(this.config.out){
        this.writeFile = new WriteFile(this.config.out)
      }
    }
  }
  // 初始化yml
  initConfigYml(){
    try{
      this.start("初始化node.utils.config.yml")
      const configYml = new LogConfigYaml(base_path).handler().get()
      if(configYml?.log) {
        this.config = Object.assign(this.config,configYml.log)
      }
    } catch (e) {
      this.error(e)
      this.warn("初始化失败,赋值为默认")
    }
  }
  // 构建log
  private log(type: LOG_TYPE, ...args: any): void {
    let time = this.config.time? new Date().toLocaleString(): '';
    let color: string= '';
    let pid = process.pid;
    switch(type){
      case LOG_TYPE.info:
        color = STYLE_COLOR.green
        break;
      case LOG_TYPE.warn:
        color = STYLE_COLOR.yellow
        break;
      case LOG_TYPE.error:
        color = STYLE_COLOR.red
        break;
      case LOG_TYPE.start:
        color = STYLE_COLOR.magenta
      default:
        break;
    }
    
    if(this.config.console){
      if (this.config.pid) {
        console.log(`[${time} ${color} ] (pid: %s) : `,type, pid , ...args)
        if(this.writeFile){
          this.writeFile.write(`[${time} ${type} ] (pid: ${pid}) : ${JSON.stringify(args)}`)
        }
      } else {
        console.log(`[${time} ${color} ] : `,type , ...args)
        if(this.writeFile){
          this.writeFile.write(`[${time} ${type} ] : ${args}`)
        }
      }
    }
    
  }
  private start(...args: any): void {
    this.log(LOG_TYPE.start,...args)
  }
  info(...args: any): void {
    this.log(LOG_TYPE.info,...args)
  }
  warn(...args: any): void {
    this.log(LOG_TYPE.warn,...args)
  }
  error(...args: any): void {
    this.log(LOG_TYPE.error,...args)
  }

}

class WriteFile {
  private config!:OutConfigI
  private wStream !:WriteStream
  constructor(config:OutConfigI) {
    this.config = config
    this.init()
  }
  private init(){
    if(this.config){
      const outPut  = this.config.path|| resolve(base_path,"./logs")
      const name = moment().format(this.config.name)
      const ext = this.config.ext || 'log'
      try {
        mkdirSync(outPut)
      } catch (error) {
        
      }
      this.wStream = createWriteStream(resolve(outPut,+''+name+'.'+ext),{
        encoding:"utf-8",
      })
    }
  }
  public write(args:any){
    this.wStream.write(args+'\r\n')
  }
}
  1. index.ts

模块统一出口

import * as logger from './log'
export const Logger = {
  ...logger
}

总结

整个工具包还算简单。不复杂,但想要真正写好还需不断学习,提升自身编程设计能力和编码设计能力