今天试一下React Native 七牛上传图片.坑也就随之开始了.以demo为例.
react-native:”0.55.4”,react-native-qiniu:’’0.3.0”

首先最大的坑就是七牛官方的这个库:react-native-qiniu,好像是该库的创始人离职了,导致了该库已经荒废,无人更新维护.
如果你只是单纯按照github的说明导入该库就使用的话,无论你以什么姿势撸.结果都是Rpc.uploadFilecatch输出错误,错误信息为null………..这个错误信息真让我头大.
可喜有人在网上做出了更新,更新代码(需要修改库中的两个源文件../react-native-qiniu/core)如下:
rpc.js

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
import conf from './conf.js';
import Auth from './auth';

//发送管理和fop命令,总之就是不上传文件
function post(uri, adminToken, content) {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
let payload = {
headers: headers,
method: 'POST',
dataType: 'json',
timeout: conf.RPC_TIMEOUT,
};
if (typeof content === 'undefined') {
payload.headers['Content-Length'] = 0;
} else {
//carry data
payload.body = content;
}

if (adminToken) {
headers['Authorization'] = adminToken;
}

return fetch(uri, payload);
}


/**
* 直传文件
* formInput对象如何配置请参考七牛官方文档“直传文件”一节
*/

function uploadFile(dataParams, policy, callbackUpDate = function () { }, callBackMethod = function () { }) {
let params = getParams(dataParams, policy);
let uri = params.uri;
let data = params.data;
let oloaded = null;
let responseObj = {};
return new Promise((resolve, reject) => {
if (typeof uri != 'string' || uri == '' || typeof data.key == 'undefined') {
reject && reject(null);
return;
}
if (uri[0] == '/') {
uri = "file://" + uri;
}
//创建xhr并open
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
responseObj.readyState = xhr.readyState; //状态0-4
responseObj.data = xhr.response;//返回值
responseObj.textData = xhr.responseText; //返回值Text
responseObj.status = xhr.status; //状态码
// responseObj.message = ""
switch (xhr.readyState) {
case 0:
callBackMethod(responseObj)
break;
case 1:
callBackMethod(responseObj)
break;
case 2:
callBackMethod(responseObj)
break;
case 3:
callBackMethod(responseObj)
break;
case 4:
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (xhr.status == 200) {
callBackMethod(responseObj)
}
} else {
callBackMethod(responseObj)
}
break;
}
};
xhr.open('POST', conf.UP_HOST);
xhr.onload = () => {
if (xhr.status !== 200) {
reject && reject(responseObj);
return;
}
resolve && resolve(JSON.parse(responseObj.data));
};
xhr.onerror = (evt) => {
reject && reject(evt);
return;
}; //请求失败
xhr.upload.onloadstart = () => {//上传开始执行方法
oloaded = 0;//设置上传开始时,以上传的文件大小为0
console("上传开始")
};
xhr.upload.onprogress = (evt) => {
oloaded = evt.loaded;//重新赋值已上传文件大小,用以下次计算
callbackUpDate(Math.round(oloaded / evt.total * 100), oloaded, evt.total)
};
xhr.upload.onloadend = (evt) => {
console("上传结束")
};
let formdata = creatFormData(params);
xhr.send(formdata);
});
}

//构造上传参数
function getParams(data, policy) {
let putPolicy = new Auth.Policy(
policy
);
let uptoken = putPolicy.token();
data.token = uptoken;
let params = {};
params.uri = data.uri;
delete data.uri;
params.data = data;
return params;
}

/**
* 创建一个表单对象,用于上传参数
* @param {*} params
*/
function creatFormData(params) {
let formdata = new FormData();
let uri = params.uri;
let formInput = creatFormInput(uri);
let data = params.data;
console.log(data)
for (let key of Object.keys(data)) {
let value = data[key];
if (key.charAt(0) === "_") {
formdata.append("x:" + key.substring(1, key.length), value);
} else {
formdata.append(key, value);
}
}
formdata.append("file", { uri: uri, type: formInput.type, name: formInput.name });
console.log(formdata)
return formdata;
}
/**
* 构造表单对象中file对象
* @param {*} params
*/
function creatFormInput(uri) {
let formInput = {};
if (typeof formInput.type == 'undefined')
formInput.type = 'application/octet-stream';
if (typeof formInput.name == 'undefined') {
var filePath = uri.split("/");
if (filePath.length > 0)
formInput.name = filePath[filePath.length - 1];
else
formInput.name = "";
}
return formInput;
}

export default { uploadFile, post }


auth.js

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
import base64 from 'base-64';
import CryptoJS from "crypto-js";
import conf from "./conf.js";
import parse from 'url-parse';

function urlsafeBase64Encode(jsonFlags) {
var encoded = base64.encode(jsonFlags);
return base64ToUrlSafe(encoded);
};

function base64ToUrlSafe(v) {
return v.replace(/\//g, '_').replace(/\+/g, '-');
};

function hmacSha1(encodedFlags, secretKey) {
var encoded = CryptoJS.HmacSHA1(encodedFlags, secretKey).toString(CryptoJS.enc.Base64);
return encoded;
};

function generateAccessToken(url, body) {
var u = parse(url, true);

var path = u.pathname;
var access = path + '\n';

if (body) {
access += body;
}

var digest = hmacSha1(access, conf.SECRET_KEY);
var safeDigest = base64ToUrlSafe(digest);
let token = 'QBox ' + conf.ACCESS_KEY + ':' + safeDigest;
//console.log(token);
return token;
};



class Policy {
constructor(policy) {
if (typeof (policy) == "undefined") {
} else {
this.policy = policy;
if (typeof (policy.deadline) == "undefined" || policy.deadline == null) {
this.policy.deadline = 3600 + Math.floor(Date.now() / 1000);
}
}
}

_parse2Str(putPolicy) {
let str = "{";
let keys = Object.keys(putPolicy);
keys.forEach((key, i) => {
let value = putPolicy[key];
if (typeof (value) == "object") {
str = `${str}"${key}":`
str = `${str}"{`
Object.keys(value).forEach((key2) => {
let value2 = value[key2];
let re = /(\$\(.*?\))/g;
if (re.test(value2)) {
str = `${str}\\\"${key2}\\\":${value2},`
} else {
str = `${str}\\\"${key2}\\\":"${value2}",`
}

})
console.log(keys.length + "::" + i)
if (i >= keys.length) {
str = `${str.substring(0, str.length - 1)}}"`
} else {
str = `${str.substring(0, str.length - 1)}}",`
}
}
else if (typeof (value) == "number") {
str = `${str}"${key}":${value},`
}
else if (typeof (value) == "string") {
str = `${str}"${key}":"${value}",`
}
else {
str = `${str}"${key}":"${value}",`
}
})
str = `${str.substring(0, str.length - 1)}}`;
return str;
}


// _creatStr = (policy) => {
// policy['deadline'] = this.expires + Math.floor(Date.now() / 1000);
// let policyStr = JSON.stringify(policy);
// let re = /(\"\$\(.*?\)\")/g;
// let newStr = policyStr.replace(re, (value) => {
// return value.substring(1, value.length - 1);
// })
// return newStr;
// }

token = () => {
policStr = this._parse2Str(this.policy);
console.log("policStr", policStr);
var encodedPutPolicy = this._urlsafeBase64Encode(policStr);
console.log("encodedPutPolicy", encodedPutPolicy);
var sign = this._hmacSha1(encodedPutPolicy, conf.SECRET_KEY);
var encodedSign = this._base64ToUrlSafe(sign);
console.log("encodedSign", encodedSign);
var uploadToken = conf.ACCESS_KEY + ':' + encodedSign + ':' + encodedPutPolicy;
console.log("uploadToken", uploadToken);
return uploadToken;
}

_urlsafeBase64Encode = (jsonFlags) => {
var encoded = base64.encode(jsonFlags);
return base64ToUrlSafe(encoded);
};

_base64ToUrlSafe = (v) => {
return v.replace(/\//g, '_').replace(/\+/g, '-');
};

_hmacSha1 = (encodedFlags, secretKey) => {
var encoded = CryptoJS.HmacSHA1(encodedFlags, secretKey).toString(CryptoJS.enc.Base64);
return encoded;
};

}

class GetPolicy {
constructor(expires) {
this.expires = expires || 3600;
}

makeRequest(baseUrl) {
var deadline = this.expires + Math.floor(Date.now() / 1000);

if (baseUrl.indexOf('?') >= 0) {
baseUrl += '&e=';
} else {
baseUrl += '?e=';
}
baseUrl += deadline;

var signature = hmacSha1(baseUrl, conf.SECRET_KEY);
var encodedSign = base64ToUrlSafe(signature);
var downloadToken = conf.ACCESS_KEY + ':' + encodedSign;

return baseUrl + '&token=' + downloadToken;
}
}

export default { urlsafeBase64Encode, generateAccessToken, Policy, GetPolicy }


ok,改完之后,你就可以愉快的撸自己的业务代码了.至于业务代码我就简单写个例子:
其中,Conf.ACCESS_KEY和Conf.SECRET_KEY从七牛账号里面获取,Conf.UP_HOST 从https://developer.qiniu.com/kodo/manual/1671/region-endpoint 里面获取,其中,scope就是七牛里面你自己建立的存储空间名

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
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/

import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
Image
} from 'react-native';
import Qiniu, { Auth, ImgOps, Conf, Rs, Rpc } from 'react-native-qiniu';
//对于七牛修改文件参考: https://blog.csdn.net/qq_33935895/article/details/78775819
Conf.ACCESS_KEY = "从七牛账号里面获取";
Conf.SECRET_KEY = "从七牛账号里面获取";
Conf.UP_HOST = '从七牛账号里面获取'; // https://developer.qiniu.com/kodo/manual/1671/region-endpoint


type Props = {};
export default class App extends Component<Props> {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
img: '图片url'

};
}

render() {
return (
<View style={styles.container}>
<Text style={styles.instructions} onPress={() => this.upload()}>
上传
</Text>
<Text>{this.state.img}</Text>
<Image
source={{ uri: this.state.img }}
style={{ width: 200, height: 400 }}
/>
</View>
);
}


/**
* 先上传七牛 获取url
* */
upload = () => {
var img = '/Users/shaotingzhou/Desktop/qiniuDemo/uploadImg.jpg' //图片路径 如果是从相册获取图片的话,其相册会返回
var myDate = new Date();
const key = myDate.getTime() + '.jpg'; //上传成功后该key就是图片的url路径
//上传参数
let params = {
uri: img,//图片路径 可以通过第三方工具 如:ImageCropPicker等获取本地图片路径
key: key,//要上传的key
}
//构建上传策略
let policy = {
scope: "demo",//记得这里如果格式为<bucket>:<key>形式的话,key要与params里的key保持一致,详见七牛上传策略
returnBody://returnBody 详见上传策略
{
name: "$(fname)",//获取文件名
size: "$(fsize)",//获取文件大小
w: "$(imageInfo.width)",//...
h: "$(imageInfo.height)",//...
hash: "$(etag)",//...
},
}

//进行文件上传
Rpc.uploadFile(params, policy).then((data) => {
console.log('上传成功')
var imgUrl = key //七牛上的图片URL 就是之前的key + 你公司域名
this.setState({
img: 'http://pax8cso07.bkt.clouddn.com/' + key
})
}).catch((err) => {
console.log(err)
});

}

}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

ok.下面是七牛里面的key对应图,修改后的输出图,最后的例子展示图:
七牛key对应图
七牛输出
Android与RN交互示意图
下面是源码.其中七牛的修改文件在0.3.0中.
源码