本教程能帮助用户快速搭建一个基于 bos 的文件直传 图片处理的手机 app,主要基于 sts 临时授权、android sdk 和图片处理 api 三个模块实现。
移动互联时代手机上传数据的场景随处可见,为了方便开发者聚焦于产品的业务逻辑,用户可以直接将文件存储到 bos 上。 bos 产品基于 sts 授权方式为用户提供了安全的上传和下载方式,bos 还支持图片处理服务。bos 具有成本低、支持海量存储和弹性扩展的特性,能帮助开发者更方便实现移动 app 业务的开发。手机美图 app 的整体搭建逻辑如下:
手机美图示例 app 下载地址:
下载完 app 并安装完成后可以直接通过应用服务器地址访问 bos ,并进行图片处理。应用服务器地址是指搭建移动应用的后台服务器,默认开启的端口为8080。关于 bos 的区域和 bucket 设置都需要在应用服务器进行配置。
该 app 支持用户上传、下载和下载缩放图三个功能。
搭建美图 app 包含以下几个步骤:
从 github 上下载的代码包,代码包主要包含 “bos_meitu_app” 和 “bos_meitu_app_server” 两部分,其中 “bos_meitu_app” 主要用于定义app界面及相关动作,“bos_meitu_app_server” 为应用服务器相关配置。
修改 “bos_meitu_app_server” 中的 “meituappserverhandler.java” 文件,其中定义了 ak/sk、bos 服务器对应的 endpoint 和 bucket 名称等信息。 ``` public string getbosinfo(string bosrequesttype) { //配置ak、sk string bosak = "开发者的ak"; string bossk = "开发者的sk"; //百度智能云提供的stsendpoint string stsendpoint = "";
bcecredentials credentials = new defaultbcecredentials(bosak, bossk);
bceclientconfiguration clientconfig = new bceclientconfiguration();
clientconfig.setcredentials(credentials);
clientconfig.setendpoint(stsendpoint);
stsclient stsclient = new stsclient(clientconfig);
getsessiontokenrequest stsreq = new getsessiontokenrequest();
// request expiration time
stsreq.setdurationseconds(1800);
getsessiontokenresponse ststoken = stsclient.getsessiontoken(stsreq);
string ststokenak = ststoken.getcredentials().getaccesskeyid();
string ststokensk = ststoken.getcredentials().getsecretaccesskey();
string ststokensessiontoken = ststoken.getcredentials().getsessiontoken();
// bos服务的endpoint地址,及bucket名称。
string bosendpoint = "http://bj.bcebos.com";
string bucketname = "bucket名称";
if (bosrequesttype.equalsignorecase("download-processed")) {
// the binded image processing domain set by app developer on bce console
bosendpoint = "http://" bucketname ".bj.bcebos.com";
}
// prefix is the bucket name, and does not specify the object name
bosinfo bosinfo = new bosinfo(ststokenak, ststokensk, ststokensessiontoken, bosendpoint,
bucketname, "", bucketname);
string res = "";
objectmapper mapper = new objectmapper();
try {
res = mapper.writerwithdefaultprettyprinter().writevalueasstring(bosinfo);
} catch (jsonprocessingexception e) {
// todo auto-generated catch block
e.printstacktrace();
res = "";
}
system.out.println(res);
try {
res = new string(res.getbytes(), "utf8");
} catch (unsupportedencodingexception e) {
// todo auto-generated catch block
e.printstacktrace();
res = "";
}
return res;
}
```
将修改完成的服务器代码重新编译打包为 bos_meitu_app_server.jar,将 jar 包上传到应用服务器上并执行命令 java -jar bos_meitu_app_server.jar
。
上传图片到 bos 过程中 app、app server 和 bos 的交互过程如下图所示:
从 bos 下载图片过程中 app、app server 和 bos 的交互过程如下图所示:
从 bos 下载缩放图过程中 app、app server 和 bos 的交互过程如下图所示:
从 bos 下载缩放图和从 bos 上下载图片交互过程基本类似,只是在从 bos 下载缩放图时需要携带 app 上设定的图片处理参数,如图片宽、高和旋转角度等。
示例代码以 java 语言为例讲解美图 app 的实现,代码分为 app 客户端和应用服务器端两部分。
app 端代码主要包括 bosclient 初始化、从 app server 端获取 bos 信息、及上传文件到 bos 三个功能模块。
public class bos {
private string ak = null;
private string sk = null;
private string endpoint = null;
private string ststoken = null;
private bosclient client = null;
public bos(string ak, string sk, string endpoint, string ststoken) {
this.ak = ak;
this.sk = sk;
this.endpoint = endpoint;
this.ststoken = ststoken;
client = createclient();
}
public bosclient createclient() {
bosclientconfiguration config = new bosclientconfiguration();
bcecredentials credentials = null;
if (ststoken != null && !ststoken.equalsignorecase("")) {
credentials = new defaultbcesessioncredentials(ak, sk, ststoken);
} else {
credentials = new defaultbcecredentials(ak, sk);
}
config.setendpoint(endpoint);
config.setcredentials(credentials);
return new bosclient(config);
}
public void uploadfile(string bucket, string object, file file) {
client.putobject(bucket, object, file);
}
public void uploadfile(string bucket, string object, inputstream inputstream) {
client.putobject(bucket, object, inputstream);
}
public void uploadfile(string bucket, string object, byte[] data) {
client.putobject(bucket, object, data);
}
public byte[] downloadfilecontent(string bucket, string object) {
return client.getobjectcontent(bucket, object);
}
}
public void uploadpictobos() {
// 1. get pic params from ui: file name, file location uri etc
// 2. send params to app server and get sts, bucket name and region
// 3. upload selected pic to bos with sts etc, which bos client needs
edittext et = (edittext) findviewbyid(r.id.app_server_addr_edittext);
final string appserveraddr = et.gettext().tostring();
new thread(new runnable() {
@override
public void run() {
map bosinfo = appserver.getbosinfofromappserver(appserveraddr, "user-demo",
appserver.bosoperationtype.upload);
if (bosinfo == null) {
return;
}
showtoast(bosinfo.tostring(), toast.length_long);
string ak = (string) bosinfo.get("ak");
string sk = (string) bosinfo.get("sk");
string ststoken = (string) bosinfo.get("ststoken");
string endpoint = (string) bosinfo.get("endpoint");
string bucketname = (string) bosinfo.get("bucketname");
string objectname = (string) bosinfo.get("objectname");
string prefix = (string) bosinfo.get("prefix");
log.i("uploadfiletobos", bosinfo.tostring());
// specify a object name if the app server does not specify one
if (objectname == null || objectname.equalsignorecase("")) {
objectname = ((edittext) findviewbyid(r.id.bos_object_name_edittext)).gettext().tostring();
if (prefix != null && !prefix.equalsignorecase("")) {
objectname = prefix "/" objectname;
}
}
bos bos = new bos(ak, sk, endpoint, ststoken);
try {
byte[] data = utils.readallfromstream(mainactivity.this.getcontentresolver().openinputstream(selectedpicuri));
bos.uploadfile(bucketname, objectname, data);
} catch (throwable e) {
log.e("mainactivity/upload", "failed to upload file to bos: " e.getmessage());
showtoast("failed to upload file: " e.getmessage());
return;
}
// finished uploading file, send a message to inform ui
handler.sendemptymessage(upload_file_finished);
}
}).start();
}
public class appserver {
/**
* get info from app server for the file to upload to or download from bos
*
* @param appserverendpoint app server
* @param username the app user's name, registered in app server
* @param bosoperationtype download? upload? or?
* @return sts, and bos endpoint, bucketname, prefix, path, object name etc
*/
public static map getbosinfofromappserver(string appserverendpoint, string username, bosoperationtype bosoperationtype) {
string type = "";
switch (bosoperationtype) {
// to simplify
case upload: {
type = "upload";
break;
}
case download: {
type = "download";
break;
}
case download_processed: {
type = "download-processed";
break;
}
default:{
break;
}
}
// todo: this url should be url encoded
string appserverurl = appserverendpoint "/?" "username=" username "&command=ststoken&type=" type;
// create a http client to contact app server to get sts
httpparams httpparameters = new basichttpparams();
httpclient httpclient = new defaulthttpclient(httpparameters);
httpget httpget = new httpget(appserverurl);
httpget.addheader("user-agent", "bos-meitu-app/demo");
httpget.setheader("accept", "*/*");
try {
httpget.setheader("host", new .gethost());
} catch (malformedurlexception e) {
e.printstacktrace();
}
httpget.setheader("accept-encoding", "identity");
map bosinfo = new hashmap();
try {
httpresponse response = httpclient.execute(httpget);
if (response.getstatusline().getstatuscode() != 200) {
return null;
}
httpentity entity = response.getentity();
long len = entity.getcontentlength();
inputstream is = entity.getcontent();
int off = 0;
byte[] b = new byte[(int) len];
while (true) {
int readcount = is.read(b, off, (int) len);
if (readcount < 0) {
break;
}
off = readcount;
}
log.d("appserver", new string(b, "utf8"));
jsonobject jsonobject = new jsonobject(new string(b, "utf8"));
iterator keys = jsonobject.keys();
while (keys.hasnext()) {
string key = keys.next();
bosinfo.put(key, jsonobject.get(key));
}
} catch (ioexception e) {
e.printstacktrace();
return null;
} catch (jsonexception e) {
e.printstacktrace();
return null;
}
return bosinfo;
}
public enum bosoperationtype {
upload,
download,
download_processed,
}
}
app server 端基于 jetty 框架,接口主要处理 android app 获取 bos 信息的请求。app server 端会返回临时 ak、sk、session token、bucket 名称、资源路径和资源请求的 endpoint 等参数。以下为 jetty 处理的代码示例。
@override
public void handle(string target, request baserequest, httpservletrequest request, httpservletresponse response)
throws ioexception, servletexception {
// inform jetty that this request has now been handled
baserequest.sethandled(true);
if (!request.getmethod().equalsignorecase("get")) {
response.setstatus(httpservletresponse.sc_not_implemented);
return;
}
// expected url example: localhost:8080/?command=ststoken&type=download
map parammap = request.getparametermap();
if (parammap.get("command") == null || parammap.get("type") == null) {
// invalid request
response.setstatus(httpservletresponse.sc_bad_request);
return;
}
if (!parammap.get("command")[0].equalsignorecase("ststoken")) {
response.setstatus(httpservletresponse.sc_not_implemented);
return;
}
string responsebody = "";
responsebody = getbosinfo(parammap.get("type")[0]);
// declare response encoding and types
response.setcontenttype("application/json; charset=utf-8");
// declare response status code
response.setstatus(httpservletresponse.sc_ok);
// write back response, utf8 encoded
response.getwriter().println(responsebody);
}
/**
* generates bos info needed by app according to requset type(upload, download etc)
* this is the key part for uploading file to bos with sts token
* @param bosrequesttype
* @return utf8 encoded json string
*/
public string getbosinfo(string bosrequesttype) {
// configuration for getting ststoken
// bce bos credentials ak sk
string bosak = "your_bos_ak";
string bossk = "your_bos_sk";
// bce sts service endpoint
string stsendpoint = "http://sts.bj.baidubce.com";
bcecredentials credentials = new defaultbcecredentials(bosak, bossk);
bceclientconfiguration clientconfig = new bceclientconfiguration();
clientconfig.setcredentials(credentials);
clientconfig.setendpoint(stsendpoint);
stsclient stsclient = new stsclient(clientconfig);
getsessiontokenrequest stsreq = new getsessiontokenrequest();
// request expiration time
stsreq.setdurationseconds(1800);
getsessiontokenresponse ststoken = stsclient.getsessiontoken(stsreq);
string ststokenak = ststoken.getcredentials().getaccesskeyid();
string ststokensk = ststoken.getcredentials().getsecretaccesskey();
string ststokensessiontoken = ststoken.getcredentials().getsessiontoken();
// **to simplify this demo there is no difference between "download" and "upload"**
// parts of bos info
string bosendpoint = "http://bj.bcebos.com";
string bucketname = "bos-android-sdk-app";
if (bosrequesttype.equalsignorecase("download-processed")) {
// the binded image processing domain set by app developer on bce console
bosendpoint = "http://" bucketname ".bj.bcebos.com";
}
// prefix is the bucket name, and does not specify the object name
bosinfo bosinfo = new bosinfo(ststokenak, ststokensk, ststokensessiontoken, bosendpoint,
bucketname, "", bucketname);
string res = "";
objectmapper mapper = new objectmapper();
try {
res = mapper.writerwithdefaultprettyprinter().writevalueasstring(bosinfo);
} catch (jsonprocessingexception e) {
e.printstacktrace();
res = "";
}
try {
res = new string(res.getbytes(), "utf8");
} catch (unsupportedencodingexception e) {
// todo auto-generated catch block
e.printstacktrace();
res = "";
}
return res;
}