NestJS实战:调用第三方网盘API的完整指南(以某网盘为例)

NestJS实战:调用第三方网盘API的完整指南(以某网盘为例)

在构建企业级文件管理系统时,集成第三方网盘API已成为常见需求。本文将以某主流网盘服务商的开放API为例,系统讲解如何在NestJS项目中实现安全、高效的API调用,涵盖从认证到功能实现的全流程。

一、技术架构设计

1.1 模块化分层设计

建议采用三层架构:

  • API层:封装网盘服务的具体实现
  • Service层:处理业务逻辑
  • Controller层:暴露HTTP接口
  1. // 典型目录结构
  2. src/
  3. ├── netdisk/
  4. ├── netdisk.module.ts
  5. ├── controllers/
  6. └── file.controller.ts
  7. ├── services/
  8. ├── auth.service.ts
  9. └── file.service.ts
  10. └── interceptors/
  11. └── error.interceptor.ts

1.2 认证机制选择

主流网盘API通常支持两种认证方式:

  1. OAuth2.0授权码模式(推荐)
  2. Access Token直传

建议优先使用OAuth2.0,其安全性更高且支持自动刷新令牌。

二、认证流程实现

2.1 OAuth2.0集成步骤

  1. 创建应用:在网盘开放平台注册应用,获取Client ID和Client Secret
  2. 配置重定向URI:设置授权回调地址(如http://your-domain/auth/callback
  3. 实现授权流程
  1. // auth.service.ts 示例
  2. import { Injectable } from '@nestjs/common';
  3. import axios from 'axios';
  4. @Injectable()
  5. export class NetdiskAuthService {
  6. private readonly authUrl = 'https://openapi.netdisk.com/oauth2.0/authorize';
  7. private readonly tokenUrl = 'https://openapi.netdisk.com/oauth2.0/token';
  8. async getAuthorizationUrl(state: string): Promise<string> {
  9. const params = new URLSearchParams({
  10. response_type: 'code',
  11. client_id: process.env.NETDISK_CLIENT_ID,
  12. redirect_uri: process.env.NETDISK_REDIRECT_URI,
  13. state: state,
  14. });
  15. return `${this.authUrl}?${params.toString()}`;
  16. }
  17. async getAccessToken(code: string): Promise<string> {
  18. const response = await axios.post(
  19. this.tokenUrl,
  20. new URLSearchParams({
  21. grant_type: 'authorization_code',
  22. code: code,
  23. client_id: process.env.NETDISK_CLIENT_ID,
  24. client_secret: process.env.NETDISK_CLIENT_SECRET,
  25. redirect_uri: process.env.NETDISK_REDIRECT_URI,
  26. }),
  27. { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
  28. );
  29. return response.data.access_token;
  30. }
  31. }

2.2 令牌管理最佳实践

  • 使用cache-manager存储令牌
  • 实现令牌自动刷新机制
  • 设置合理的过期时间检查
  1. // 令牌存储示例
  2. import { Cache } from 'cache-manager';
  3. @Injectable()
  4. export class TokenManager {
  5. constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
  6. async setToken(token: string, expiresIn: number) {
  7. await this.cacheManager.set('netdisk_token', token, expiresIn * 1000);
  8. }
  9. async getToken(): Promise<string | null> {
  10. return this.cacheManager.get('netdisk_token');
  11. }
  12. }

三、API调用实现

3.1 基础请求封装

创建统一的API请求类,处理认证和错误:

  1. // netdisk.client.ts
  2. import axios from 'axios';
  3. export class NetdiskClient {
  4. private baseUrl = 'https://openapi.netdisk.com/rest/2.0';
  5. constructor(private accessToken: string) {}
  6. async callApi(method: string, path: string, params: any = {}) {
  7. const url = `${this.baseUrl}/${path}`;
  8. const fullParams = {
  9. access_token: this.accessToken,
  10. ...params
  11. };
  12. try {
  13. const response = await axios.get(url, { params: fullParams });
  14. return response.data;
  15. } catch (error) {
  16. this.handleError(error);
  17. }
  18. }
  19. private handleError(error: any) {
  20. if (error.response?.data?.error_code === 'invalid_token') {
  21. throw new Error('需要重新认证');
  22. }
  23. throw error;
  24. }
  25. }

3.2 文件上传实现

文件上传需要特殊处理:

  1. // file.service.ts
  2. import { Injectable } from '@nestjs/common';
  3. import axios from 'axios';
  4. import FormData from 'form-data';
  5. @Injectable()
  6. export class NetdiskFileService {
  7. constructor(private netdiskClient: NetdiskClient) {}
  8. async uploadFile(filePath: string, destPath: string): Promise<any> {
  9. const form = new FormData();
  10. form.append('path', destPath);
  11. form.append('file', createReadStream(filePath));
  12. const response = await axios.post(
  13. 'https://openapi.netdisk.com/rest/2.0/files/upload',
  14. form,
  15. {
  16. headers: {
  17. ...form.getHeaders(),
  18. Authorization: `Bearer ${await this.netdiskClient.getToken()}`
  19. }
  20. }
  21. );
  22. return response.data;
  23. }
  24. }

四、高级功能实现

4.1 分块上传实现

对于大文件,建议使用分块上传:

  1. async chunkUpload(filePath: string, destPath: string, chunkSize = 5 * 1024 * 1024) {
  2. const fileStat = await stat(filePath);
  3. const totalChunks = Math.ceil(fileStat.size / chunkSize);
  4. for (let i = 0; i < totalChunks; i++) {
  5. const start = i * chunkSize;
  6. const end = Math.min(start + chunkSize, fileStat.size);
  7. const chunk = createReadStream(filePath, { start, end });
  8. const form = new FormData();
  9. form.append('path', destPath);
  10. form.append('partseq', i.toString());
  11. form.append('uploadid', this.generateUploadId());
  12. form.append('file', chunk);
  13. await axios.post('https://openapi.netdisk.com/rest/2.0/files/upload_part', form, {
  14. headers: form.getHeaders()
  15. });
  16. }
  17. // 完成上传
  18. await this.completeUpload(destPath, totalChunks);
  19. }

4.2 目录递归操作

实现目录的递归创建和删除:

  1. async createDirRecursive(path: string) {
  2. const paths = path.split('/').filter(Boolean);
  3. let currentPath = '';
  4. for (const segment of paths) {
  5. currentPath = `${currentPath}/${segment}`.replace(/\/+/g, '/');
  6. try {
  7. await this.netdiskClient.callApi('POST', 'file/create_folder', { path: currentPath });
  8. } catch (error) {
  9. if (error.response?.data?.error_code !== 'file_already_exists') {
  10. throw error;
  11. }
  12. }
  13. }
  14. }

五、性能优化建议

  1. 请求合并:批量操作时使用Promise.all
  2. 缓存策略:对频繁访问的文件列表进行缓存
  3. 重试机制:实现指数退避重试
  4. 并发控制:限制同时上传/下载的文件数
  1. // 带重试的请求示例
  2. async retryableRequest(fn: () => Promise<any>, maxRetries = 3) {
  3. let lastError;
  4. for (let i = 0; i < maxRetries; i++) {
  5. try {
  6. return await fn();
  7. } catch (error) {
  8. lastError = error;
  9. await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
  10. }
  11. }
  12. throw lastError;
  13. }

六、安全实践

  1. 敏感信息保护

    • 使用环境变量存储Client Secret
    • 禁止在前端代码中暴露认证信息
  2. 输入验证

    1. @Post('upload')
    2. @UsePipes(new ValidationPipe())
    3. async handleUpload(@Body() uploadDto: UploadDto) {
    4. // 处理上传
    5. }
  3. 速率限制

    1. @Module({
    2. imports: [
    3. ThrottlerModule.forRoot({
    4. ttl: 60,
    5. limit: 100,
    6. }),
    7. ],
    8. })

七、常见问题解决方案

  1. 跨域问题

    • 确保回调地址在网盘开放平台配置
    • 后端接口配置CORS
  2. 令牌过期处理

    1. async refreshTokenIfNeeded() {
    2. const token = await this.tokenManager.getToken();
    3. if (!token) {
    4. await this.reauthenticate();
    5. }
    6. // 检查令牌有效期
    7. }
  3. 大文件处理

    • 使用流式处理避免内存溢出
    • 显示上传进度

八、完整示例:文件上传控制器

  1. @Controller('files')
  2. export class FileController {
  3. constructor(
  4. private readonly fileService: NetdiskFileService,
  5. private readonly authService: NetdiskAuthService
  6. ) {}
  7. @Get('auth')
  8. async authenticate(@Res() res: Response) {
  9. const state = generateRandomState();
  10. const authUrl = await this.authService.getAuthorizationUrl(state);
  11. res.redirect(authUrl);
  12. }
  13. @Get('callback')
  14. async handleCallback(@Query('code') code: string, @Res() res: Response) {
  15. const token = await this.authService.getAccessToken(code);
  16. // 存储token并重定向到前端
  17. }
  18. @Post('upload')
  19. @UseGuards(AuthGuard)
  20. @UseInterceptors(FileInterceptor('file'))
  21. async uploadFile(
  22. @UploadedFile() file: Express.Multer.File,
  23. @Body('path') path: string
  24. ) {
  25. return this.fileService.uploadFile(file.path, path);
  26. }
  27. }

九、总结与展望

通过本文的实践指南,开发者可以掌握在NestJS中集成第三方网盘API的核心技术:

  1. 完善的OAuth2.0认证流程
  2. 稳健的API调用封装
  3. 高效的文件操作实现
  4. 全面的错误处理和性能优化

未来发展方向包括:

  • 支持更多网盘服务商的多云适配
  • 实现更精细的权限控制
  • 增加Webhook通知机制

建议开发者在实际项目中,根据具体业务需求调整实现细节,并持续关注网盘API的版本更新。