fcs
2025-04-10 0163870d662dc5b7d69ceff977490ede594f7d82
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*!
 * qn - lib/up.js
 * Copyright(c) 2013 fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
 * MIT Licensed
 */
 
"use strict";
 
/**
 * Module dependencies.
 */
 
var utility = require('utility');
var debug = require('debug')('qn:up');
var fs = require('fs');
var path = require('path');
var FormStream = require('formstream');
var bt = require('buffer-type');
var utils = require('./utils');
var version = require('../package.json').version;
 
/**
 * Create uploadToken
 * @see http://docs.qiniu.com/api/v6/put.html#uploadToken
 * 
 * @param {Object} options
 *  - {String} scope 一般指文件要上传到的目标存储空间(Bucket)。
 *                   若为”Bucket”,表示限定只能传到该Bucket(仅限于新增文件);若为”Bucket:Key”,表示限定特定的文件,可修改该文件。
 *  - {Number} [deadline] 定义 uploadToken 的失效时间,Unix时间戳,精确到秒,缺省为 3600 秒
 *  - {String} [endUser] 给上传的文件添加唯一属主标识,特殊场景下非常有用,比如根据终端用户标识给图片或视频打水印
 *  - {String} [returnUrl] 设置用于浏览器端文件上传成功后,浏览器执行301跳转的URL,一般为 HTML Form 上传时使用。
 *                         文件上传成功后会跳转到 returnUrl?query_string, query_string 会包含 returnBody 内容。
 *                         returnUrl 不可与 callbackUrl 同时使用。 
 *  - {String} [returnBody] 文件上传成功后,自定义从 Qiniu-Cloud-Server 最终返回給终端 App-Client 的数据。
 *                          支持 魔法变量,不可与 callbackBody 同时使用。
 *  - {String} [callbackBody] 文件上传成功后,Qiniu-Cloud-Server 向 App-Server 发送POST请求的数据。
 *                            支持 魔法变量 和 自定义变量,不可与 returnBody 同时使用。
 *  - {String} [callbackUrl] 文件上传成功后,Qiniu-Cloud-Server 向 App-Server 发送POST请求的URL,
 *                           必须是公网上可以正常进行POST请求并能响应 HTTP Status 200 OK 的有效 URL
 *  - {String} [asyncOps] 指定文件(图片/音频/视频)上传成功后异步地执行指定的预转操作。
 *                        每个预转指令是一个API规格字符串,多个预转指令可以使用分号“;”隔开
 * @return {String} upload token string
 */
exports.uploadToken = function uploadToken(options) {
  options = options || {};
  options.scope = options.scope || this.options.bucket;
  options.deadline = options.deadline || (utility.timestamp() + 3600);
  var flags = options;
  // 步骤2:将 Flags 进行安全编码
  var encodedFlags = utility.base64encode(JSON.stringify(flags), true);
 
  // 步骤3:将编码后的元数据混入私钥进行签名
  // 步骤4:将签名摘要值进行安全编码
  var encodedSign = this.signData(encodedFlags);
 
  // 步骤5:连接各字符串,生成上传授权凭证
  return this.options.accessKey + ':' + encodedSign + ':' + encodedFlags;
};
 
/**
 * Upload file content
 * 
 * @param {String|Buffer|Stream} file content string or buffer, or a Stream instance.
 * @param {Object} [options]
 *  - {String} [key] 标识文件的索引,所在的存储空间内唯一。key可包含斜杠,但不以斜杠开头,比如 a/b/c.jpg 是一个合法的key。
 *                   若不指定 key,缺省使用文件的 etag(即上传成功后返回的hash值)作为key;
 *                   此时若 UploadToken 有指定 returnUrl 选项,则文件上传成功后跳转到 returnUrl?query_string, 
 *                   query_string 包含key={FileID}
 *  - {String} [x:custom_field_name] 自定义变量,必须以 x: 开头命名,不限个数。
 *                                   可以在 uploadToken 的 callbackBody 选项中使用 $(x:custom_field_name) 求值。
 *  - {String} [filename]
 *  - {String} [contentType]
 *  - {Number} [size]
 * @param {Function(err, result)} callback
 *  - {Object} result
 *   - {String} hash
 *   - {String} key
 *   - {String} url
 */
exports.upload = function upload(content, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  if (typeof content === 'string') {
    content = new Buffer(content);
  }
 
  options = options || {};
  options.filename = options.filename || options.key;
  if (Buffer.isBuffer(content) && !options.filename && !options.contentType) {
    // try to guess contentType from buffer
    var info = bt(content);
    if (info) {
      options.contentType = info.type;
      options.filename = 'file' + info.extension;
    }
  }
 
  // if (!options.filename) {
  //   options.filename = 'unknowfile';
  // }
 
  var form = content;
  if (!(content instanceof FormStream)) {
    form = new FormStream();
    if (Buffer.isBuffer(content)) {
      form.buffer('file', content, options.filename, options.contentType);
    } else {
      // stream
      if (content.path) {
        // try to get filename in `stream.path`
        options['x:filename'] = options['x:filename'] || path.basename(content.path);
        if (!options.filename) {
          options.filename = options['x:filename'];
        }
      }
      form.stream('file', content, options.filename, options.contentType, options.size);
    }
  }
 
  var that = this;
  form.field('token', that.uploadToken());
 
  if (options.key) {
    form.field('key', this.resourceKey(String(options.key)));
  }
 
  for (var k in options) {
    if (k.indexOf('x:') === 0) {
      form.field(k, String(options[k]));
    }
  }
  var headers = form.headers();
  headers['User-Agent'] = 'qn/' + version;
  var reqOptions = {
    method: 'POST',
    dataType: 'json',
    headers: headers,
    timeout: options.timeout || that.options.timeout,
    stream: form,
  };
 
  that._request(that._uploadURL, reqOptions, function (err, data, res) {
    if (err) {
      return callback(err, data, res);
    }
    
    if (data) {
      data.url = that.resourceURL(data.key);
    }
    callback(null, data, res);
  });
};
 
/**
 * Upload a file
 * 
 * @param {String} filepath file full path
 * @param {Object} [options] options use on `upload()`
 * @param {Function(err, result)} callback
 */
exports.uploadFile = function (filepath, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
 
  var that = this;
  fs.stat(filepath, function (err, stat) {
    if (err) {
      return callback(err);
    }
 
    var form = new FormStream();
    form.file('file', filepath, stat.size);
    options = options || {};
    options['x:filename'] = options['x:filename'] || path.basename(filepath);
    options['x:size'] = options['x:size'] || stat.size;
    if (stat.mtime) {
      options['x:mtime'] = options['x:mtime'] || stat.mtime.getTime() / 1000;
    }
    if (stat.ctime) {
      options['x:ctime'] = options['x:ctime'] || stat.ctime.getTime() / 1000;
    }
    that.upload(form, options, callback);
  });
};