nest.js 自定义ioredis/mysql2 provider

58 阅读2分钟

需求

  • 不喜欢用nest内置的mysql和缓存模块,自己配置mysql和redis provider
  • 但是这些模块的官方api过于原始,要求自己扩展这些模块的api方法。

实现

  • mysql2.factory.ts
import { ConfigService } from '@nestjs/config';
import { ConnectionOptions } from 'mysql2/promise';
import * as mysql from 'mysql2/promise';

// 扩展mysql2/promise库的Connection接口
declare module 'mysql2/promise' {
  export interface Connection {
    get(tableName: string, whereMap: object): any;

    delete(tableName: string, whereMap: object): any;

    update(tableName: string, setMap: object, whereMap: object): any;

    fetch(sql: string, param: object | (number | string)[] | null): any;

    fetch(sql: string): any

    /**
     *  查询第一个记录
     * @param sql
     * @param param
     */
    first(sql: string, param: object | (number | string)[] | null): any;

    /**
     *  查询第一个记录
     * @param sql
     */
    first(sql: string): any

    insert(tableName: string, setMap: object): any;
  }
}
export async function mysqlFactory (configService: ConfigService) {
  const access: ConnectionOptions = {
    host: configService.get('mysql.host'),
    user: configService.get('mysql.user'),
    password: configService.get('mysql.pass'),
    database: configService.get('mysql.db'),
    namedPlaceholders: true,
  };
  // console.log(access);
  const conn = await mysql.createConnection(access);
  //
  conn.fetch = async function (sql: string, param: object | (number | string)[] | null = {} ) {
    const [result] = await conn.query(sql, param);
    return result;
  };
  //
  conn.first = async function (sql: string, param: object | (number | string)[] | null = {} ) {
    if(!/.*\s+limit\s+1\s*$/.test(sql)) {
      sql += ' limit 1';
    }
    const [result] = await conn.query(sql, param);
    return result[0];
  };
  // 单表
  conn.get = async function (tableName: string, whereMap: object ) {
    const param = [];
    const whereArr = [];
    for(const key of Object.keys(whereMap)) {
      whereArr.push(``${key}` = ?`);
      param.push(whereMap[key]);
    }
    const where = whereArr.join(' and ');
    const [result] = await conn.query(`select * from ${tableName} where ${where} limit 1`, param);
    return result[0];
  };
  // 单表
  conn.delete = async function (tableName: string, whereMap: object ){
    const param = [];
    const whereArr = [];
    for(const key of Object.keys(whereMap)) {
      whereArr.push(``${key}` = ?`);
      param.push(whereMap[key]);
    }
    const where = whereArr.join(' and ');
    const [ result ] = await conn.query(`delete from ${tableName} where ${where}`, param);
    return result['affectedRows'];
  };
  // 单表/多表
  conn.update = async function (tableName: string, setMap: object, whereMap: object ) {
    const param = [];
    const setArr = [];
    for(const key of Object.keys(setMap)) {
      setArr.push(``${key}` = ?`);
      param.push(setMap[key]);
    }
    const setData = setArr.join(',');
    const whereArr = [];
    for(const key of Object.keys(whereMap)) {
      whereArr.push(`${key} = ?`);
      param.push(whereMap[key]);
    }
    const whereData = whereArr.join(' and ');
    const sql = `update ${tableName} set ${setData} where ${whereData}`;
    const [ result ] = await conn.query(sql, param);
    // console.log(result);
    return result['affectedRows'];
  };
  // todo replace into table() value()
  conn.insert = async function (tableName: string, setMap: object) {
    const param = [];
    const setFieldArr = [];
    const setValueArr = [];
    for(const key of Object.keys(setMap)) {
      setFieldArr.push(``${key}``);
      setValueArr.push(`?`);
      param.push(setMap[key]);
    }
    const setField = setFieldArr.join(',');
    const setValue = setValueArr.join(',');
    const sql = `insert into ${tableName} (${setField}) value(${setValue})`;
    const [ result ] = await conn.query(sql, param);
    return result['insertId'];
  };
  return conn;
}
  • ioredis.factory.ts
import { ConfigService } from '@nestjs/config';
import Redis, { RedisOptions } from 'ioredis';

// 编译通过,eslint不通过
declare module 'ioredis' {
  export interface RedisCommander {
    pkeys(key: string): Promise<string[]>;
  }
}
// 编译通过,eslint不通过
declare module 'ioredis' {
  export interface Redis {
    pkeys(key: string): Promise<string[]>;
  }
}
export async function redisFactory(configService: ConfigService) {
  const KeyPrefix = configService.get('redis.prefix') || '';
  const access: RedisOptions = {
    keyPrefix: configService.get('redis.prefix') || '',
    username: configService.get('redis.user') || '',
    password: configService.get('redis.pass') || '',
    db: configService.get('redis.db') || 0,
    host: configService.get('redis.host') || 'localhost',
    port: configService.get('redis.port') || 6379,
  };
  const conn = new Redis(access);
  conn.pkeys = async function (key: string) {
    const rs = await conn.keys(KeyPrefix+key);
    return rs.map(item=>item.substring(KeyPrefix.length));
  };
  return conn;
}
  • app.module.ts
import { mysqlFactory } from '../../factory/mysql2.factory';
import { redisFactory } from '../../factory/ioredis.factory';
import { Module } from '@nestjs/common';
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [loadConfig],
      envFilePath: 'src/config/.env',
    }),
  ],
  controllers: [],
  providers: [
    {
      provide: 'REDIS',
      useFactory: redisFactory,
      inject: [ConfigService],
    },
    {
      provide: 'MYSQL',
      useFactory: mysqlFactory,
      inject: [ConfigService],
    },
    TokenService,
    SettingService,
    PermissionService,
  ],
  exports: ['MYSQL', 'REDIS', TokenService, SettingService, PermissionService],
})
export class AppModule {}

疑问

  • 请问,如何完美扩展ioredis的方法?