Skip to content

Nest 使用 ssh2-sftp-client

安装

bash
npm install --save ssh2-sftp-client

配置文件

ts
# 开发环境配置
app:
  prefix: ''
  port: 8080
  logger:
    # 项目日志存储路径,相对路径(相对本项目根目录)或绝对路径
    dir: '../logs'
  # 文件相关
  file:
    # 是否为本地文件服务或cos
    isLocal: true
    # location 文件上传后存储目录,相对路径(相对本项目根目录)或绝对路径
    location: '../upload'
    # 文件服务器地址,这是开发环境的配置 生产环境请自行配置成可访问域名
    domain: 'http://localhost:8080'
    # 文件虚拟路径, 必须以 / 开头, 如 http://localhost:8080/profile/****.jpg  , 如果不需要则 设置 ''
    serveRoot: '/profile'
    # 文件大小限制,单位M
    maxSize: 10
# 腾讯云cos配置
cos:
  secretId: ''
  secretKey: ''
  bucket: ''
  region: ''
  domain: ''
  location: ''
# 数据库配置
db:
  mysql:
    host: 'xxxxx'
    username: 'root'
    password: 'xxxxx'
    database: 'prismalianbiao'
    port: 3306
    charset: 'utf8mb4'
    logger: 'file'
    logging: true
    multipleStatements: true
    dropSchema: false
    synchronize: true
    supportBigNumbers: true
    bigNumberStrings: true
  mysqlread:
    host: 'xxxxx'
    username: 'root'
    password: 'xxxxx'
    database: 'prismalianbiao2'
    port: 3306
    charset: 'utf8mb4'
    logger: 'file'
    logging: true
    multipleStatements: true
    dropSchema: false
    synchronize: true
    supportBigNumbers: true
    bigNumberStrings: true
# redis 配置 过期时间1 小时
redis:
  host: 'xxxxx'
  password: 'redis_n6BSFa'
  port: 6379
  db: 0
  keyPrefix: ''
  ttl: 3600
# jwt 配置
jwt:
  secretKey: 'jsopy'
  expiresin: '1h'
  refreshExpiresIn: '2h'
# 权限 白名单配置
perm:
  router:
    whiteList: [
        { path: '/users/config', method: 'GET' },
        { path: '/users/register/jwt', method: 'POST' },
        # { path: '/users/login/jwt', method: 'POST' }, 这里我取消了 因为我默认就是jwt登录
        { path: '/users/login/custom', method: 'POST' },
        { path: '/users/login/basic', method: 'POST' },
        { path: '/users/check/ftp', method: 'POST' },
        { path: '/users/upload/ftp', method: 'POST' },
        { path: '/users/mkdir/ftp', method: 'POST' },
        { path: '/users/deldir/ftp', method: 'POST' },
        { path: '/users/delfile/ftp', method: 'POST' },
      ]
# 拦截器白名单
noglobalinterceptor:
  router:
    whiteList: []
# 用户相关
# 初始密码, 重置密码
user:
  initialPassword: '123456'

# 自己写的测试
testconfig:
  test: 'dev'

# FTP 配置
ftp:
  host: 'xxxxx'
  port: 22
  userName: 'xxxx'
  passWord: 'xxxxxx'
  root: '/xxx/xx/xx/x'

使用

  • 创建 ftp 模块 和 ftp.service.ts

ftp.module.ts

bash

import { Module } from '@nestjs/common';
import { FtpService } from './ftp.service';

@Module({
  providers: [FtpService],
  exports: [FtpService],
})
export class FtpModule {}

ftp.service.ts

ts
import { Injectable } from "@nestjs/common";
import SftpClient from "ssh2-sftp-client";
import { ConfigService } from "@nestjs/config";

@Injectable()
export class FtpService {
  constructor(private readonly configService: ConfigService) {}

  // FTP 初始化
  async init() {
    const client = new SftpClient();
    try {
      await client.connect({
        host: this.configService.get("ftp").host,
        user: this.configService.get("ftp").userName,
        password: this.configService.get("ftp").passWord,
        secure: true,
      });
      console.log("ftp连接成功");
      return client;
    } catch (error) {
      console.log(error);
      return false;
    }
  }

  // 列出目录
  async getList() {
    let client: any = null;
    try {
      client = await this.init();
      // 列出目录内容
      const dir = this.configService.get("ftp").root;
      const files = await client.list(dir);
      console.log(`${dir} 目录内容:`, files);
    } catch (error) {
      console.log(error);
      throw new Error(error);
    } finally {
      // 关闭连接
      await client.end();
    }
  }

  // 创建目录
  async mkdirftp(moduleName) {
    let client: any = null;
    try {
      client = await this.init();
      // 创建目录
      const dir = this.configService.get("ftp").root;
      // 获取年份
      const year = new Date().getFullYear();
      // 获取月份
      const month = new Date().getMonth() + 1;
      // 月份前面补0
      const formattedMonth = month.toString().padStart(2, "0");
      // 获取日期
      const day = new Date().getDate();
      // 日期前面补0
      const formattedDay = day.toString().padStart(2, "0");
      const customdir = `${dir}/${moduleName}/${year}/${formattedMonth}/${formattedDay}/`;
      // 判断目录是否存在 d 就是已经存在  false 就是不存在
      const hasdir = await client.exists(customdir);
      console.log("hasdir");
      console.log(hasdir);
      if (hasdir) {
        console.log(`目录 ${customdir} 已存在`);
        return {
          status: false,
          url: `${customdir}`,
        };
      } else {
        await client.mkdir(customdir, true);
        console.log(`已创建目录 ${dir}`);
        return {
          status: true,
          url: `${customdir}`,
        };
      }
    } catch (error) {
      console.log(error);
    } finally {
      // 关闭连接
      await client.end();
    }
  }

  // 删除目录
  async deldirftp(path) {
    let client: any = null;
    try {
      client = await this.init();
      // 判断目录是否存在
      const dir = this.configService.get("ftp").root;
      //  存在就是d 不存在就是false
      const hasdir = await client.exists(`${dir}/${path}`);
      console.log("hasdir");
      console.log(hasdir);
      if (hasdir) {
        console.log(`目录存在`);
        await client.rmdir(`${dir}/${path}`, true);
        return true;
      } else {
        return false;
      }
    } catch (error) {
      console.log(error);
    } finally {
      // 关闭连接
      await client.end();
    }
  }
  // 上传文件起名
  /**
   * 生成一个唯一的文件名
   * @param {string} extension - 文件扩展名(可选)
   * @returns {Promise<string>} 返回生成的唯一文件名
   */
  async generateUniqueFileName(extension) {
    // 获取当前时间戳
    const timestamp = new Date().getTime();
    // 生成一个0到99999999之间的随机数
    const random = Math.floor(Math.random() * 100000000);
    // 组合时间戳和随机数,并添加文件扩展名(如果提供)
    const fileName = `${timestamp}_${random}${
      extension ? "." + extension : ""
    }`;
    // 返回生成的唯一文件名
    return fileName;
  }

  // 上传文件
  async uploadfile(moduleName) {
    let client: any = null;
    try {
      client = await this.init();
      // 先创建目录
      const resultUrl: { status: boolean; url: string } | undefined =
        await this.mkdirftp(moduleName);
      if (!resultUrl) {
        return false;
      } else {
        // 我这里拿本地路径举例子
        const localFile = process.cwd() + "/public/ReadMe.txt";
        const fileName = await this.generateUniqueFileName("txt");
        const remoteFile = resultUrl.url + fileName;
        await client.put(localFile, remoteFile);
        return true;
      }
    } catch (error) {
      console.log(error);
      throw new Error(error);
    } finally {
      // 关闭连接
      await client.end();
    }
  }

  // 删除文件
  async delfile(path) {
    let client: any = null;
    try {
      client = await this.init();
      // 判断文件是否存在
      const dir = this.configService.get("ftp").root;
      //  存在就是d 不存在就是false
      const hasdir = await client.exists(`${dir}/${path}`);
      if (hasdir) {
        await client.delete(`${dir}/${path}`);
        return true;
      } else {
        return false;
      }
    } catch (error) {
      console.log(error);
    }
  }
}

使用

  • 我这里就在 user 模块里使用

user.module.ts

ts
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { UsersController } from "./users.controller";
import { FtpModule } from "../ftp/ftp.module";
@Module({
  imports: [FtpModule],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

users.controller.ts

ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  Req,
  UseGuards,
} from "@nestjs/common";
import { UsersService } from "./users.service";

@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get("config")
  async getconfig() {
    return this.usersService.getConfig();
  }

  // 获取目录
  @Post("check/ftp")
  async checkftp(@Req() req: any) {
    const result = await this.usersService.checkftp();
    return {
      data: result,
    };
  }

  // 创造目录
  @Post("mkdir/ftp")
  async mkdir(@Body() body: any) {
    // body.module 就是模块名
    const result = await this.usersService.mkdirftp(body.module);
    return {
      data: result,
      code: 200,
    };
  }

  // 删除目录
  @Post("deldir/ftp")
  async deldir(@Body() body: any) {
    // body.module 就是模块名
    const result = await this.usersService.deldirftp(body.path);
    return {
      data: result,
    };
  }

  // 删除文件
  @Post("delfile/ftp")
  async delfile(@Body() body: any) {
    // body.module 就是模块名
    const result = await this.usersService.delfile(body.path);
    return {
      data: result,
    };
  }

  // 上传FTP
  @Post("upload/ftp")
  async uploadftp(@Req() req: any) {
    const result = await this.usersService.uploadftp(req.body.module);
    return {
      data: result,
    };
  }
}

users.service.ts

ts
import { Inject, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { FtpService } from "../ftp/ftp.service";
@Injectable()
export class UsersService {
  @Inject()
  private readonly configService: ConfigService;

  @Inject()
  private readonly ftpService: FtpService;
  getConfig() {
    return this.configService.get("db");
  }

  // 获取ftp列表
  async checkftp() {
    await this.ftpService.getList();
  }

  // 上传文件
  async uploadftp(moduleName) {
    const result: any = await this.ftpService.uploadfile(moduleName);
    if (result) {
      return "upload success";
    } else {
      return "upload fail";
    }
  }

  // 制造目录
  async mkdirftp(moduleName) {
    const result: any = await this.ftpService.mkdirftp(moduleName);
    if (result.status) {
      return "目录创建成功";
    } else {
      return "目录已经存在";
    }
  }

  // 删除目录
  async deldirftp(path) {
    const result = await this.ftpService.deldirftp(path);
    if (result) {
      return "目录删除成功";
    } else {
      return "目录删除失败";
    }
  }

  // 删除文件
  async delfile(path) {
    const result = await this.ftpService.delfile(path);
    if (result) {
      return "文件删除成功";
    } else {
      return "文件删除失败";
    }
  }
}