解决方案实践 > 手机美图APP搭建
手机美图APP搭建
  • 概览
  • 需求场景
  • 美图 APP 示例
  • APP操作方法
  • 如何搭建美图APP
  • 如何部署应用服务器
  • 交互过程
  • 上传图片到 BOS
  • 从 BOS 下载图片
  • 从 BOS 下载缩放图
  • 代码示例
  • APP 客户端代码样例
  • APP Server端代码样例

手机美图APP搭建

更新时间:

概览

本教程能帮助用户快速搭建一个基于 BOS 的文件直传+图片处理的手机 APP,主要基于 STS 临时授权、Android SDK 和图片处理 API 三个模块实现。

  • 由于用户的移动端应用存在泄密的风险所以不可能直接存储 AK/SK 信息,必须使用 STS 临时授权模式访问 BOS 。STS 临时授权模式中会生成一个临时 Token,该 Token 具有一定的时效性,即 APP 应用只有在 Token 的时效性内访问才可以完成上传或下载图片服务,过了时效需要重新获取。
  • Andriod SDK 帮助用户实现新建 BOS 客户端并将文件存储到 BOS 或从 BOS 中下载文件。
  • 图片处理 API 主要实现图片的处理如缩略、裁剪、格式转换、旋转、加文字/图片水印等功能。

需求场景

移动互联时代手机上传数据的场景随处可见,为了方便开发者聚焦于产品的业务逻辑,用户可以直接将文件存储到 BOS 上。 BOS 产品基于 STS 授权方式为用户提供了安全的上传和下载方式,BOS 还支持图片处理服务。BOS 具有成本低、支持海量存储和弹性扩展的特性,能帮助开发者更方便实现移动 APP 业务的开发。手机美图 APP 的整体搭建逻辑如下:

image.png

美图 APP 示例

手机美图示例 APP 下载地址:

下载完 APP 并安装完成后可以直接通过应用服务器地址访问 BOS ,并进行图片处理。应用服务器地址是指搭建移动应用的后台服务器,默认开启的端口为8080。关于 BOS 的区域和 Bucket 设置都需要在应用服务器进行配置。

APP操作方法

该 APP 支持用户上传、下载和下载缩放图三个功能。

  • 上传:用户填写应用服务器地址后,选择本地需要上传的图片,图片会显示在操作界面下方,点击上传即可。上传成功会显示 “File Uploaded”。
  • 下载:用户填写需要下载的文件名称,点击下载按钮即可。下载成功会显示 “File Downloaded”。
  • 下载缩放图:下载缩放图时必须指定明确的图片后缀如 jpg ,然后设置下载图形的宽和高以及旋转角度,点击下载缩放图,则会获取经过处理的图形。下载成功会显示 “File Downloaded”。 Demo 版本下载后的图片都不会存储到本地。

如何搭建美图APP

搭建美图 APP 包含以下几个步骤:

  1. 开启 BOS 服务并创建 Bucket 用于存储图片,开通和创建 Bucket 的详细操作请参见创建 Bucket。如果要下载缩放图,需要保证指定的 Bucket 开启了图片处理服务。
  2. 开通 STS 服务,用于保证上传和下载图片的安全性。
  3. 部署应用服务器,实现和 BOS 及客户端的交互,美图 APP 代码请参考:BOS美图APP代码
  4. 下载安装美图APP。

如何部署应用服务器

  1. 从 github 上下载sample code的代码包,代码包主要包含 “bos_meitu_app” 和 “bos_meitu_app_server” 两部分,其中 “bos_meitu_app” 主要用于定义APP界面及相关动作,“bos_meitu_app_server” 为应用服务器相关配置。

  2. 修改 “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 = "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();
    
          // 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;
      }

    ```

  3. 将修改完成的服务器代码重新编译打包为 bos_meitu_app_server.jar,将 jar 包上传到应用服务器上并执行命令 java -jar bos_meitu_app_server.jar

    交互过程

上传图片到 BOS

上传图片到 BOS 过程中 APP、APP Server 和 BOS 的交互过程如下图所示:

  1. APP 上传图片时向 APP Server 发送获取上传方式请求。
  2. APP Server 向 STS 服务器请求 BOS 使用 STS 访问的 AK/SK,STS 服务器向 APP Server 返回 STS 凭证,包括临时 AK、SK 和 Session Token。
  3. APP Server 将 STS 凭证及上传方式参数返回,上传方式参数包含 Bucket 名称、Endpoint 等。
  4. APP 根据返回的信息将文件上传到 BOS 上,BOS 会将上传结果返回给 APP。
  5. APP 可以根据需要将上传结果提供给 APP Server。

从 BOS 下载图片

从 BOS 下载图片过程中 APP、APP Server 和 BOS 的交互过程如下图所示:

  1. APP 下载图片时向 APP Server 发送获取下载方式请求。
  2. APP Server 向STS服务器请求 BOS 使用 STS 访问的 AK/SK ,STS 服务器向 APP Server 返回 STS 凭证,包括临时 AK、SK 和Session Token。
  3. APP Server 将 STS 凭证及下载方式参数返回 APP,下载方式参数包含 Bucket 名称、Endpoint 等。
  4. APP 根据返回的信息
  5. 从 BOS 上下载文件,BOS 会将下载结果返回给 APP。
  6. APP 可以根据需要将下载结果提供给 APP Server。

从 BOS 下载缩放图

从 BOS 下载缩放图过程中 APP、APP Server 和 BOS 的交互过程如下图所示:

从 BOS 下载缩放图和从 BOS 上下载图片交互过程基本类似,只是在从 BOS 下载缩放图时需要携带 APP 上设定的图片处理参数,如图片宽、高和旋转角度等。

代码示例

示例代码以 Java 语言为例讲解美图 APP 的实现,代码分为 APP 客户端和应用服务器端两部分。

APP 客户端代码样例

APP 端代码主要包括 BOSClient 初始化、从 APP Server 端获取 BOS 信息、及上传文件到 BOS 三个功能模块。

BOSClient 初始化

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);
    }
}

上传文件到 BOS 代码实现

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<String, Object> 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();
}

从 APP Server 上获取 BOS 信息代码实现

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<String, Object> 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 URL(appServerUrl).getHost());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        httpGet.setHeader("Accept-Encoding", "identity");
    
        Map<String, Object> bosInfo = new HashMap<String, Object>();
        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<String> 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端代码样例

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<String, String[]> 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;
}