Skip to content

feat: resumable upload default use v2#446

Merged
YangSen-qn merged 1 commit intoqiniu:masterfrom
YangSen-qn:master
Jan 26, 2026
Merged

feat: resumable upload default use v2#446
YangSen-qn merged 1 commit intoqiniu:masterfrom
YangSen-qn:master

Conversation

@YangSen-qn
Copy link
Contributor

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @YangSen-qn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant upgrade to the resumable upload functionality by implementing a new version 2 API. It provides new classes and methods designed for improved resumable uploads, while gracefully deprecating the older API. The changes ensure that existing integrations continue to function by internally routing calls to the new V2 methods, making the transition seamless for users. The update also includes relevant examples and tests to reflect and validate the new V2 implementation.

Highlights

  • Resumable Upload V2 Implementation: Introduced a new PutExtraV2 class and corresponding putStreamV2, putFileV2, and putFileWithoutKeyV2 methods to support version 2 of resumable uploads. These new methods are now the default for resumable upload operations.
  • Backward Compatibility and Deprecation: Existing resumable upload methods (putStream, putFile, putFileWithoutKey) have been marked as deprecated. They now internally convert PutExtra to PutExtraV2 and call their respective V2 counterparts, ensuring backward compatibility while encouraging migration to the new V2 API.
  • Example and Test Updates: The resume_upload_simple.js example has been updated to use the new PutExtraV2 and putFileV2 methods. Corresponding tests in resume_up.test.js have also been modified to validate the new V2 resumable upload functionality.
  • New Authentication Example: A new example file, examples/a.js, was added, demonstrating the usage of qiniu/auth/digest.Mac for authentication purposes. This appears to be a standalone example unrelated to the resumable upload changes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qiniu-prow qiniu-prow bot added the size/L label Jan 21, 2026
@xgopilot
Copy link

xgopilot bot commented Jan 21, 2026

Code Quality Review Summary

Overall: The V2 API additions are well-structured and maintain backward compatibility through deprecation. However, there are issues that should be addressed.

Critical Issues:

  1. Debug file committed: examples/a.js appears to be a scratch file and should be removed
  2. Bug in getDefaultPutExtraV2: Creates PutExtra instead of PutExtraV2, causing wrong default version

Maintainability Concerns:

  • PutExtraV2 duplicates PutExtra entirely (only default version differs)
  • PutExtra-to-PutExtraV2 conversion logic is duplicated in putStream and putFile

Positive Notes:

  • Good use of JSDoc annotations
  • Appropriate deprecation markers on old methods
  • Tests updated to use new V2 methods

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request aims to make v2 the default for resumable uploads by introducing V2 versions of methods and classes. The changes are in the right direction, but there are a few critical issues. A new example file examples/a.js seems to have been added by mistake and contains placeholder credentials, which is a security risk. More importantly, the wrapper functions like putFile and putStream do not correctly default to v2 as intended, and a helper function getDefaultPutExtraV2 also has a bug that causes it to default to v1. I've left specific comments on these issues.

examples/a.js Outdated
Comment on lines 5 to 8
digest.Mac({
accessKey: '**',
secretKey: '**'
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This file appears to be a test or example file that was accidentally committed. It contains hardcoded credentials (even as placeholders **), which is a major security risk and should not be in the repository. Also, the usage of digest.Mac is incorrect. It's a constructor and should be called with new, and its arguments are accessKey and secretKey strings, not an object. For example: new digest.Mac('accessKey', 'secretKey');. Please remove this file from the pull request.

* @param {string} [fname] 请求体中的文件的名称
* @param {Object} [params] 额外参数设置,参数名称必须以x:开头
* @param {string | null} [mimeType] 指定文件的mimeType
* @param {string | null} [resumeRecordFile] DEPRECATED: 使用 `` 与 `` 代替;断点续传的已上传的部分信息记录文件路径
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Documentation] Empty backticks in deprecation notice

The deprecation message has empty backticks that should reference the replacement properties.

Suggested change
* @param {string | null} [resumeRecordFile] DEPRECATED: 使用 `` `` 代替;断点续传的已上传的部分信息记录文件路径
* @param {string | null} [resumeRecordFile] DEPRECATED: 使用 `resumeRecorder` `resumeKey` 代替;断点续传的已上传的部分信息记录文件路径

Same issue exists on line 68 for PutExtra.

@xgopilot
Copy link

xgopilot bot commented Jan 21, 2026

Code Review Summary

This PR introduces V2 upload API methods with improved defaults. I've identified one critical bug and several important issues:

Critical Issues:

  • Line 1128: getDefaultPutExtraV2() creates PutExtra instead of PutExtraV2, causing v1 defaults instead of v2
  • examples/a.js: Debug/test file accidentally committed - should be removed

Documentation:

  • Empty backticks in deprecation notices (lines 68, 108)
  • Wrong parameter type in JSDoc (line 1073)

Performance:

  • MD5 calculated twice per chunk in V2 uploads (resumeUploadV2 + uploadPart functions)

Overall structure is sound. Please address the critical bug on line 1128 before merging.

@YangSen-qn
Copy link
Contributor Author

@xgopilot 在这个 pr 中有没有更好的办法在保证版本兼容性前提下让 PutExtra 中的 version 默认从 v1 调整到 v2

@xgopilot
Copy link

xgopilot bot commented Jan 22, 2026

关于 PutExtra 默认版本的建议

@YangSen-qn 你好!我已经仔细分析了这个 PR 的实现方案。关于你提出的问题 "有没有更好的办法在保证版本兼容性前提下让 PutExtra 中的 version 默认从 v1 调整到 v2",我有以下建议:


当前实现的问题

当前方案通过创建 PutExtraV2 类来实现默认使用 v2,但存在以下问题:

  1. 代码重复严重: PutExtraV2PutExtra 几乎完全相同(只有默认 version 不同)
  2. 维护成本高: 未来如果需要添加新字段,需要同时修改两个类
  3. 已发现的 Bug: getDefaultPutExtraV2() 函数中错误地使用了 new PutExtra() 而不是 new PutExtraV2()(第 1118 行)

更好的解决方案

我建议采用以下几种方案之一,按推荐优先级排序:

方案 1: 使用工厂函数(推荐 ⭐)

优点:

  • 无需重复类定义
  • API 清晰明确
  • 向后兼容性好
  • 易于维护

实现:

function PutExtra(...args) {
    // 保持原有默认值 v1,确保向后兼容
    this.version = args[6] || 'v1';
    // ... 其他字段
}

// 新增工厂函数
PutExtra.createV2 = function(fname, params, mimeType, resumeRecordFile, 
                              progressCallback, partSize, version, 
                              metadata, resumeRecorder, resumeKey) {
    const extra = new PutExtra(fname, params, mimeType, resumeRecordFile,
                               progressCallback, partSize, version || 'v2',
                               metadata, resumeRecorder, resumeKey);
    return extra;
};

使用方式:

// 旧代码继续使用 PutExtra,默认 v1(向后兼容)
const putExtra = new PutExtra();

// 新代码使用工厂函数,默认 v2
const putExtraV2 = PutExtra.createV2();

方案 2: 使用选项对象模式

优点:

  • 参数更清晰
  • 易于扩展
  • 解决了构造函数参数过多的问题

实现:

function PutExtra(options) {
    options = options || {};
    this.fname = options.fname || '';
    this.params = options.params || {};
    this.mimeType = options.mimeType || null;
    this.resumeRecordFile = options.resumeRecordFile || null;
    this.progressCallback = options.progressCallback || null;
    this.partSize = options.partSize || conf.BLOCK_SIZE;
    // 允许显式指定默认版本,保持向后兼容
    this.version = options.version !== undefined ? options.version : 'v1';
    this.metadata = options.metadata || {};
    this.resumeRecorder = options.resumeRecorder || null;
    this.resumeKey = options.resumeKey || null;
}

PutExtra.createV2 = function(options) {
    options = options || {};
    if (options.version === undefined) {
        options.version = 'v2';
    }
    return new PutExtra(options);
};

方案 3: 继承方式(不太推荐)

缺点: JavaScript 原型继承可能引入额外复杂性

function PutExtraV2(...args) {
    PutExtra.call(this, ...args);
    // 只覆盖默认版本
    if (args[6] === undefined) {
        this.version = 'v2';
    }
}
PutExtraV2.prototype = Object.create(PutExtra.prototype);
PutExtraV2.prototype.constructor = PutExtraV2;

关于向后兼容性

重要考虑:

当前 PR 的方案虽然保留了旧的 API(putFile, putStream, putFileWithoutKey),但实际上已经是破坏性变更,因为:

  1. 测试代码显示旧方法现在也使用 putFileV2(第 203、273、570、609 行)
  2. 旧方法内部调用 convertPutExtraToPutExtraV2(),会保留用户传入的 version
  3. 如果用户没有显式指定 version,会得到 'v1'(来自 PutExtra 的默认值)

建议的兼容性策略:

如果要真正平滑过渡:

  • 保持旧 API (putFile 等) 默认使用 v1
  • 新 API (putFileV2 等) 默认使用 v2
  • 在文档中明确说明迁移路径
  • 考虑在后续的 major 版本中才将 v2 设为全局默认

总结建议

强烈推荐方案 1(工厂函数),因为它:

  1. ✅ 避免代码重复
  2. ✅ 保持向后兼容
  3. ✅ API 清晰(PutExtra.createV2() 明确表达意图)
  4. ✅ 易于维护和扩展
  5. ✅ 不需要创建完全重复的类

同时,请务必修复当前代码中的 Bug(qiniu/storage/resume.js:1118 行)。

希望这些建议对你有帮助!如果需要我详细展示某个方案的完整实现,请告诉我。

@YangSen-qn
Copy link
Contributor Author

YangSen-qn commented Jan 22, 2026

@xgopilot 在方案一中,怎么让用户感知到之前的构造方法已经废弃?这样用户可以使用新的构造方法。

方案 1: 使用工厂函数(推荐 ⭐)

@xgopilot
Copy link

xgopilot bot commented Jan 22, 2026

如何让用户感知 API 已废弃

@YangSen-qn 关于如何在方案一中让用户感知旧的构造方法已废弃,有以下几种最佳实践:


方案 1: 使用 util.deprecate (推荐 ⭐⭐⭐)

Node.js 内置了 util.deprecate 工具,这是最标准和专业的方式。

实现代码:

const util = require('util');

// 原有的 PutExtra 构造函数保持不变
function PutExtra(fname, params, mimeType, resumeRecordFile, 
                  progressCallback, partSize, version, 
                  metadata, resumeRecorder, resumeKey) {
    this.fname = fname || '';
    this.params = params || {};
    this.mimeType = mimeType || null;
    this.resumeRecordFile = resumeRecordFile || null;
    this.progressCallback = progressCallback || null;
    this.partSize = partSize || conf.BLOCK_SIZE;
    this.version = version || 'v1';
    this.metadata = metadata || {};
    this.resumeRecorder = resumeRecorder || null;
    this.resumeKey = resumeKey || null;
}

// 工厂函数(推荐使用)
PutExtra.createV2 = function(fname, params, mimeType, resumeRecordFile, 
                              progressCallback, partSize, version, 
                              metadata, resumeRecorder, resumeKey) {
    return new PutExtra(
        fname, params, mimeType, resumeRecordFile,
        progressCallback, partSize, version || 'v2',
        metadata, resumeRecorder, resumeKey
    );
};

// 导出时添加废弃警告
exports.PutExtra = util.deprecate(
    PutExtra,
    'PutExtra constructor with default v1 is deprecated. Use PutExtra.createV2() for v2 uploads or explicitly specify version parameter.'
);

用户体验:

// 当用户使用旧方式时,会在控制台看到警告:
const putExtra = new qiniu.PutExtra();
// (node:12345) DeprecationWarning: PutExtra constructor with default v1 is deprecated. 
// Use PutExtra.createV2() for v2 uploads or explicitly specify version parameter.

// 推荐的新方式(无警告):
const putExtraV2 = qiniu.PutExtra.createV2();

优点:

  • ✅ Node.js 官方标准方式
  • ✅ 只在第一次调用时警告(避免刷屏)
  • ✅ 可通过环境变量控制(--no-deprecation, --throw-deprecation
  • ✅ 不影响功能,仅提醒用户
  • ✅ 遵循 Node.js 生态最佳实践

方案 2: JSDoc 文档标记 + 运行时警告

结合文档和代码警告,给用户多重提示。

实现代码:

let hasWarnedDeprecation = false;

/**
 * @deprecated 此构造函数默认使用 v1 版本,将在未来版本中移除。
 * 请使用 PutExtra.createV2() 创建 v2 版本的实例,或显式传入 version 参数。
 * @param {string} [fname]
 * @param {Object} [params]
 * ... 其他参数
 */
function PutExtra(fname, params, mimeType, resumeRecordFile, 
                  progressCallback, partSize, version, 
                  metadata, resumeRecorder, resumeKey) {
    // 如果用户没有显式指定 version,发出警告
    if (version === undefined && !hasWarnedDeprecation) {
        console.warn(
            '\x1b[33m%s\x1b[0m', // 黄色文本
            '[qiniu-sdk] DeprecationWarning: ' +
            'PutExtra() 默认使用 v1 已废弃。' +
            '请使用 PutExtra.createV2() 或显式指定 version 参数。' +
            '\n详见: https://github.com/qiniu/nodejs-sdk/blob/master/CHANGELOG.md#v8-migration'
        );
        hasWarnedDeprecation = true;
    }
    
    this.fname = fname || '';
    this.params = params || {};
    this.mimeType = mimeType || null;
    this.resumeRecordFile = resumeRecordFile || null;
    this.progressCallback = progressCallback || null;
    this.partSize = partSize || conf.BLOCK_SIZE;
    this.version = version || 'v1';
    this.metadata = metadata || {};
    this.resumeRecorder = resumeRecorder || null;
    this.resumeKey = resumeKey || null;
}

PutExtra.createV2 = function(fname, params, mimeType, resumeRecordFile, 
                              progressCallback, partSize, version, 
                              metadata, resumeRecorder, resumeKey) {
    return new PutExtra(
        fname, params, mimeType, resumeRecordFile,
        progressCallback, partSize, version || 'v2',
        metadata, resumeRecorder, resumeKey
    );
};

优点:

  • ✅ IDE 中显示 @deprecated 标记(如 VSCode 会显示删除线)
  • ✅ 运行时有明确的警告信息
  • ✅ 可以添加迁移文档链接
  • ✅ 控制警告频率(只警告一次)

方案 3: TypeScript 类型定义(如果支持 TypeScript)

如果项目支持 TypeScript 或提供 .d.ts 文件:

/**
 * @deprecated 使用 PutExtra.createV2() 代替
 */
declare class PutExtra {
    constructor(
        fname?: string,
        params?: object,
        // ... 其他参数
    );
    
    /**
     * 创建使用 v2 版本的 PutExtra 实例(推荐)
     */
    static createV2(
        fname?: string,
        params?: object,
        // ... 其他参数
    ): PutExtra;
}

方案 4: 在 README 和 CHANGELOG 中明确说明

CHANGELOG.md:

## [8.0.0] - 2024-XX-XX

### Breaking Changes
- 分片上传默认版本将在下一个 major 版本改为 v2

### Deprecated
- `new PutExtra()` 默认使用 v1 已废弃
  - 迁移方式 1: 使用 `PutExtra.createV2()` 创建 v2 实例(推荐)
  - 迁移方式 2: 显式传入 `version: 'v2'` 参数
  - 示例:
    ```javascript
    // 旧方式(已废弃)
    const putExtra = new PutExtra();
    
    // 新方式
    const putExtra = PutExtra.createV2();
    ```

### Added
- 新增 `PutExtra.createV2()` 工厂函数,默认使用 v2 版本

README.md:

## 迁移指南

### 从 v7.x 迁移到 v8.x

**分片上传 API 变更**:

v8.0 开始,推荐使用新的 `PutExtra.createV2()` 创建 v2 版本的配置对象:

```javascript
// ❌ 旧方式(仍可用但已废弃)
const putExtra = new qiniu.PutExtra();
putExtra.version = 'v2'; // 需要手动设置

// ✅ 新方式(推荐)
const putExtra = qiniu.PutExtra.createV2();

---

### 综合推荐方案

**结合使用方案 1 + 方案 2 + 方案 4**,这样可以给用户最完整的提示:

1. **代码层面**: 使用 `util.deprecate` 包装导出
2. **文档层面**: JSDoc 添加 `@deprecated` 标记
3. **发布层面**: CHANGELOG 和 README 明确说明迁移路径

**完整实现示例**:

```javascript
const util = require('util');

/**
 * 上传可选参数
 * @deprecated 此构造函数默认使用 v1,将在 v9.0.0 移除默认值。
 * 请使用 PutExtra.createV2() 或显式指定 version 参数。
 * @see https://github.com/qiniu/nodejs-sdk#migration-guide
 */
function PutExtra(fname, params, mimeType, resumeRecordFile, 
                  progressCallback, partSize, version, 
                  metadata, resumeRecorder, resumeKey) {
    this.fname = fname || '';
    this.params = params || {};
    this.mimeType = mimeType || null;
    this.resumeRecordFile = resumeRecordFile || null;
    this.progressCallback = progressCallback || null;
    this.partSize = partSize || conf.BLOCK_SIZE;
    this.version = version || 'v1';
    this.metadata = metadata || {};
    this.resumeRecorder = resumeRecorder || null;
    this.resumeKey = resumeKey || null;
}

/**
 * 创建使用 v2 API 的 PutExtra 实例(推荐)
 * @returns {PutExtra}
 */
PutExtra.createV2 = function(fname, params, mimeType, resumeRecordFile, 
                              progressCallback, partSize, version, 
                              metadata, resumeRecorder, resumeKey) {
    return new PutExtra(
        fname, params, mimeType, resumeRecordFile,
        progressCallback, partSize, version || 'v2',
        metadata, resumeRecorder, resumeKey
    );
};

// 导出时添加废弃警告
exports.PutExtra = util.deprecate(
    PutExtra,
    'new PutExtra() 默认 v1 已废弃,请使用 PutExtra.createV2() 或显式指定 version'
);

迁移时间线建议

  1. v8.0: 添加 PutExtra.createV2(),标记旧方式为 deprecated
  2. v8.x: 在文档中持续提醒用户迁移
  3. v9.0: 将 PutExtra 默认版本改为 v2(breaking change)

这样可以给用户充足的时间适应新 API!

@YangSen-qn
Copy link
Contributor Author

YangSen-qn commented Jan 22, 2026

@xgopilot 上面的方案中为什么要使用 PutExtra.createV2 而不直接只用 PutExtra.create

@xgopilot
Copy link

xgopilot bot commented Jan 22, 2026

关于工厂函数命名的说明

@YangSen-qn 这是个很好的问题!让我详细解释为什么推荐使用 createV2 而不是 create


推荐使用 PutExtra.createV2 的原因

1. 未来扩展性考虑

如果现在只用 PutExtra.create(),那么:

  • 当前 create() 会默认创建 v2 实例
  • 但是将来如果有 v3、v4 版本时,API 会变得混乱

问题场景:

// 假设现在 create() 默认创建 v2
PutExtra.create(); // 创建 v2

// 一年后,推出 v3 版本
// create() 应该返回什么?v2 还是 v3?
// 如果改成 v3,会破坏向后兼容
// 如果保持 v2,又不符合"最新版本"的预期

使用 createV2() 就没有这个问题:

PutExtra.createV2(); // 明确创建 v2
PutExtra.createV3(); // 将来明确创建 v3
// 每个版本都有明确的创建方法

2. API 语义明确性

使用 createV2:

const putExtra = PutExtra.createV2();
// 一眼就知道:这是创建 v2 版本的实例

使用 create:

const putExtra = PutExtra.create();
// 疑问:这是什么版本?需要查文档才知道

3. 与现有 API 模式保持一致

您的 SDK 中已经有版本化的模式:

  • putFile (v1)
  • putFileV2 (v2)
  • putStream (v1)
  • putStreamV2 (v2)

使用 createV2 保持了这种命名一致性。


但是,create 也有它的优势

如果您更倾向于使用 create,也有合理的场景:

方案 A: create 始终返回"推荐版本"

// 定义:create() 始终返回当前推荐的版本
PutExtra.create = function(...args) {
    // 目前推荐 v2
    return PutExtra.createV2(...args);
};

// 将来推荐 v3 时,只需要修改这里
PutExtra.create = function(...args) {
    return PutExtra.createV3(...args);
};

优点:

  • 用户总是能用 create() 获取最新推荐版本
  • 简洁方便

缺点:

  • 用户代码在升级 SDK 后可能会意外改变行为
  • 不适合需要稳定版本的场景

方案 B: 同时提供两种方式

/**
 * 创建 v2 版本的 PutExtra 实例(明确版本)
 */
PutExtra.createV2 = function(...args) {
    return new PutExtra(...args, 'v2');
};

/**
 * 创建推荐版本的 PutExtra 实例(当前为 v2)
 * 注意:此方法返回的版本可能随 SDK 升级而变化
 */
PutExtra.create = function(...args) {
    return PutExtra.createV2(...args);
};

优点:

  • 灵活性最高
  • 既有明确版本的 API,也有便捷的 API
  • 给用户选择权

其他 SDK 的实践参考

许多知名 SDK 都使用版本化的工厂方法:

AWS SDK:

new AWS.S3({ apiVersion: '2006-03-01' });

Stripe SDK:

stripe.customers.create(); // 隐式版本
// 但 API 路径包含版本:/v1/customers

Google Cloud SDK:

// 明确指定版本
const {v1} = require('@google-cloud/storage');

我的最终推荐

综合建议:同时提供两种方式(方案 B)

/**
 * 创建使用 v2 API 的 PutExtra 实例
 * @returns {PutExtra}
 */
PutExtra.createV2 = function(fname, params, mimeType, resumeRecordFile, 
                              progressCallback, partSize, version, 
                              metadata, resumeRecorder, resumeKey) {
    return new PutExtra(
        fname, params, mimeType, resumeRecordFile,
        progressCallback, partSize, version || 'v2',
        metadata, resumeRecorder, resumeKey
    );
};

/**
 * 创建推荐版本的 PutExtra 实例(当前推荐 v2)
 * @deprecated 建议使用 createV2() 以明确版本
 * @returns {PutExtra}
 */
PutExtra.create = function(...args) {
    return PutExtra.createV2(...args);
};

理由:

  1. createV2() 提供明确的版本控制
  2. create() 提供便捷性
  3. ✅ 文档中推荐使用 createV2(),引导用户养成明确版本的习惯
  4. ✅ 向后兼容性和灵活性兼得

总结

如果只能选一个

  • 推荐 createV2 - 更明确、更稳定、更易维护

如果可以两个都提供

  • 同时提供 createcreateV2 - 给用户更多选择

具体选择哪个取决于您对 SDK 未来演进的规划和用户群体的偏好。希望这个解释对您有帮助!

@YangSen-qn
Copy link
Contributor Author

@xgopilot 重新 Review 下这个 pr,特别是接口兼容性问题。

@xgopilot
Copy link

xgopilot bot commented Jan 22, 2026

PR #446 Code Review - 接口兼容性专项分析

经过仔细审查,我发现这个 PR 在接口兼容性方面有以下关键问题:


已改进的地方

  1. 采用了工厂函数模式: 使用 PutExtra.create() 默认创建 v2 实例,这是很好的设计
  2. 添加了 deprecation 警告: 通过 util.deprecate() 包装 PutExtra 导出,用户会收到废弃提示
  3. 保留了旧的 API: putFile, putStream 等方法仍然存在,支持旧代码运行

兼容性问题(Critical)

1. 隐式行为变更 - 这是一个 BREAKING CHANGE

问题位置: qiniu/storage/resume.js:960qiniu/storage/resume.js:979

// putFileV2 方法中 (line 960)
putExtra = putExtra || PutExtra.create();  // 默认创建 v2

// putFileV2 继续调用 (line 972)
putExtra = getDefaultPutExtra(putExtra, { key });  // 这里保持 putExtra 的 version

影响分析:

用户现有代码如果使用 putFileV2,但没有传入 putExtra 或传入了没有设置 version 的 PutExtra 实例,行为会发生变化:

// 场景 1: 用户代码 (老版本 SDK)
const putExtra = new PutExtra();
// putExtra.version = 'v1' (默认)
resumeUploader.putFileV2(token, key, file, putExtra);  
// 老版本: 会使用 v1
// 新版本: 仍会使用 v1 (因为 getDefaultPutExtra 保留了 version)

// 场景 2: 用户代码 (不传 putExtra)
resumeUploader.putFileV2(token, key, file);  
// 老版本: 如果老版本有 putFileV2,会使用 v1 (因为 getDefaultPutExtra 设置 v1)
// 新版本: 会使用 v2 (因为 PutExtra.create() 默认 v2)
// ⚠️ 这是 BREAKING CHANGE!

这意味着: 任何不传 putExtra 参数调用 putFileV2 的代码,会从 v1 变成 v2!


2. 废弃警告位置不准确

问题位置: qiniu/storage/resume.js:34-36

exports.PutExtra = libUtil.deprecate(
    PutExtra,
    'PutExtra constructor is deprecated. Use PutExtra.create() for v2 uploads or explicitly specify version parameter.'
);

问题:

  1. 这个警告在所有使用 new PutExtra() 的地方都会触发
  2. 但实际上,如果用户显式指定 version: 'v1',是不应该被警告的
  3. 警告信息不够精确 - 没有说明 new PutExtra() 默认使用 v1

建议的警告信息:

'PutExtra constructor defaults to v1 which is deprecated. Use PutExtra.create() for v2 (recommended), or explicitly pass version parameter.'

3. JSDoc 文档与实际行为不一致

问题位置:

  • qiniu/storage/resume.js:111 - JSDoc 说默认 v2
  • qiniu/storage/resume.js:74 - PutExtra 构造函数 JSDoc 说默认 v1

这会造成用户困惑。

现状:

// Line 74: PutExtra 构造函数
 * @param {'v1' | 'v2'} [version]               分片上传版本 目前支持v1/v2版本 默认v1

// Line 111: PutExtra.create JSDoc
 * @param {'v1' | 'v2'} [version]               分片上传版本 目前支持v1/v2版本 默认v2

这是正确的,但用户需要非常仔细阅读文档才能理解两者差异。


4. 测试使用 putFileV2 但传入 new PutExtra()

问题位置: test/resume_up.test.js:185-208test/resume_up.test.js:260-279

// Line 185
const putExtra = new qiniu.resume_up.PutExtra();  // 默认 v1
if (version !== undefined) {
    putExtra.version = version;
}
// ...
resumeUploader.putFileWithoutKeyV2(  // 调用 V2 方法
    uploadToken,
    testFilePath,
    putExtra,  // 传入的是 v1 的 PutExtra (如果 version === undefined)
    callback
)

问题分析:

  • 测试中使用 new PutExtra() 创建实例(默认 v1)
  • 但调用的是 putFileV2 系列方法
  • 只有当 version !== undefined 时才显式设置 version
  • version === undefined 时,测试实际使用的是 v1 版本,但调用的是 V2 API

这不符合 V2 API 的设计意图。


5. putFileputStream 仍会触发 v1 上传

问题位置: qiniu/storage/resume.js:938

ResumeUploader.prototype.putFile = function (
    uploadToken,
    key,
    localFile,
    putExtra,
    callbackFunc
) {
    // PutExtra
    putExtra = putExtra || new PutExtra();  // ⚠️ 默认 v1
    return this.putFileV2(uploadToken, key, localFile, putExtra, callbackFunc);
};

分析:

  • putFile 被标记为 @deprecated,这很好
  • 但它内部调用 putFileV2,并传入默认 v1 的 PutExtra
  • 这意味着即使用户调用旧的 putFile 方法,也不会自动升级到 v2
  • 用户必须显式使用 putFileV2 才能获得 v2 的好处

向后兼容性: ✅ 很好,不会破坏现有代码


📊 兼容性影响总结

场景 旧代码 新代码行为 是否兼容 风险级别
new PutExtra() + putFile() v1 v1 ✅ 兼容
new PutExtra() + putFileV2() v1 v1 ✅ 兼容
不传 putExtra + putFile() v1 v1 ✅ 兼容
不传 putExtra + putFileV2() v1 (假设) v2 破坏性变更
PutExtra.create() + 任何方法 N/A (新 API) v2 ✅ 新功能

🔧 建议的改进方案

方案 A: 保守方案(推荐用于 minor 版本)

目标: 完全向后兼容,v2 是选择性加入

// putFileV2 的修改
ResumeUploader.prototype.putFileV2 = function (
    uploadToken,
    key,
    localFile,
    putExtra,
    callbackFunc
) {
    const preferredScheme = this.config.useHttpsDomain ? 'https' : 'http';

    // 改为: 如果用户没有传 putExtra,使用 PutExtra.create()
    // 如果用户传了 putExtra,尊重用户的选择
    if (!putExtra) {
        putExtra = PutExtra.create();  // 默认 v2
    }
    
    // 其余代码保持不变
    // ...
}

优点:

  • 明确的迁移路径
  • 不破坏任何现有代码
  • V2 API 行为符合预期

方案 B: 渐进方案(推荐用于 major 版本)

在新的 major 版本(如 8.0.0)中:

  1. PutExtra 构造函数默认改为 v2
  2. 提供 PutExtra.createV1() 供需要 v1 的用户使用
  3. 在 CHANGELOG 中明确标注为 BREAKING CHANGE
function PutExtra(...args) {
    // ...
    this.version = args[6] || 'v2';  // 改为默认 v2
}

PutExtra.createV1 = function(...args) {
    return new PutExtra(...args, 'v1');
};

PutExtra.create = function(...args) {
    return new PutExtra(...args, 'v2');
};

🎯 当前 PR 的建议

鉴于这是一个 feature PR(从 PR 标题看),我建议:

  1. 修改 putFileV2 的默认行为(按方案 A)

    • putExtraundefined/null 时,使用 PutExtra.create()
    • 这样 V2 API 默认使用 v2,符合用户预期
  2. 修改测试代码

    • 在测试 V2 API 时,应该使用 PutExtra.create() 或显式设置 version
    • 明确测试 v1 和 v2 的行为差异
  3. 更新 deprecation 警告

    • 使警告信息更精确
    • 考虑只在未指定 version 时才警告
  4. 在 PR 描述中明确说明兼容性

    • 列出所有可能的 breaking changes
    • 说明迁移路径
  5. 补充文档

    • 更新 README 说明新旧 API 的区别
    • 提供迁移示例

📝 其他发现的小问题

  1. Line 71, 111: JSDoc 中的空反引号 `` 应该填充为 `resumeRecorder` 与 `resumeKey`
  2. Example 文件: examples/resume_upload_simple.js:18 仍使用 new PutExtra(),应改为展示新API的用法

总结

这个 PR 的核心设计(factory function + deprecation)是很好的,但在接口兼容性方面存在一个关键问题:

  • putFileV2 不传 putExtra 时会使用 v2,这可能是 BREAKING CHANGE
  • 需要明确这是否是预期行为
  • 如果是,需要在文档中明确说明
  • 如果不是,需要按照上面的方案 A 修改

建议在合并前明确兼容性策略,并相应调整实现。

👉 Want me to re-run this review? Just type /review in the comments! For more usage examples, visit CodeAgent GitHub Repository.

@YangSen-qn YangSen-qn merged commit 236da61 into qiniu:master Jan 26, 2026
8 of 9 checks passed
@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 37.50000% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 23.45%. Comparing base (5188200) to head (8d86f19).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
qiniu/storage/resume.js 37.50% 10 Missing ⚠️

❌ Your project status has failed because the head coverage (23.45%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           master     #446       +/-   ##
===========================================
- Coverage   86.14%   23.45%   -62.70%     
===========================================
  Files          27       33        +6     
  Lines        2173     2294      +121     
  Branches      474        0      -474     
===========================================
- Hits         1872      538     -1334     
- Misses        301     1756     +1455     
Files with missing lines Coverage Δ
qiniu/storage/resume.js 13.37% <37.50%> (-71.54%) ⬇️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant