Minio

  • 开源支持 S3 协议的对象存储
  • 支持多个客户端访问,支持分布式集群部署,容器化部署
  • 考虑集成在项目中,作为后端存储进行测试

Introduction

Features

  • Open source Object Storage: MINIO
  • Amazon S3, Apache License v2.0
  • Client/Server Mode
  • Support Erasure Code
  • Website: https://min.io/
  • Github Repo:https://github.com/minio/minio

Details

  • MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
  • MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
  • [Java Client API]
  • [Java Client API -zh]
  • 官方测试地址:https://play.minio.io:9000

Start

Docker

  • Pull Image
docker pull minio/minio
  • Run image (without access_key & secret_key)
# -it 运行参数
# -p 指定端口
# -d 后台运行
docker run -it -p 9000:9000 -d minio/minio server /data
  • Run image
docker run -p 9000:9000 --name minio \
-d --restart=always \
-e "MINIO_ACCESS_KEY=admin" \
-e "MINIO_SECRET_KEY=admin123456" \
-v /home/data:/data \
-v /home/config:/root/.minio \
minio/minio server /data

AWS S3 SDK for C++

Prerequisite

make -j `nproc` -C aws-cpp-sdk-core
make -j `nproc` -C aws-cpp-sdk-s3
  • 安装头文件和库到一个目录
make install DESTDIR=/c++/C-Example/linux/install -C ../build/aws-cpp-sdk-s3
make install DESTDIR=/c++/C-Example/linux/install -C ../build/aws-cpp-sdk-core

Sample Code: List Buckets

  • Test Code: 使用 Minio 示例测试。
#include <iostream>
#include <aws/s3/S3Client.h>
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>

using namespace Aws::S3;
using namespace Aws::S3::Model;
using namespace std;

int main(int argc, char* argv[]) {
    Aws::SDKOptions options;
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
    Aws::InitAPI(options);

    Aws::Client::ClientConfiguration cfg;
    cfg.endpointOverride = "https://play.minio.io:9000";  // S3服务器地址和端口
    cfg.scheme = Aws::Http::Scheme::HTTP;
    cfg.verifySSL = false;

    Aws::Auth::AWSCredentials cred("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");  // 认证的Key
    S3Client client(cred, cfg, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false);

    auto response = client.ListBuckets();
    if (response.IsSuccess()) {
        auto buckets = response.GetResult().GetBuckets();
        for (auto iter = buckets.begin(); iter != buckets.end(); ++iter) {
            cout << iter->GetName() << "\t" << iter->GetCreationDate().ToLocalTimeString(Aws::Utils::DateFormat::ISO_8601) << endl;
        }
    } else {
        cout << "Error while ListBuckets " << response.GetError().GetExceptionName()
            << " " << response.GetError().GetMessage() << endl;
    }

    Aws::ShutdownAPI(options);
    return 0;
}

IDE Configuration (VS Code Remote)

  • VS Code Remote 开发时可能缺少对 gcc 的相关配置,示例如下:
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/install/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++14",
            "intelliSenseMode": "clang-x64"
        },
        {
            "name": "huawei-cloud",
            "includePath": [
                "${workspaceFolder}/install/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++14",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

CMake Env Configuration

  • 编写 CMakeList.txt
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo)

add_compile_options( -std=c++11 )
include_directories(/c++/C-Example/linux/install/usr/local/include)
link_directories(/c++/C-Example/linux/install/usr/local/lib64)

# 指定生成目标
add_executable(Demo object_storage.cpp)
target_link_libraries(Demo -laws-cpp-sdk-core -laws-cpp-sdk-s3)

Build And Run

  • 运行 CMake 编译成可执行文件
cmake .
make
  • make 时可能报错,缺少依赖,如 aws-c-common, aws-checksums and aws-c-event-stream. 在安装 AWS SDK 时会默认将这些依赖包放在 .deps/install/lib,如果没有安装,自行 make,make 完成之后导出
export LD_LIBRARY_PATH=/c++/C-Example/linux/.deps/install/lib64
  • 重新编译我们的 demo
cmake .
make
  • 编译成功,运行可执行文件
./Demo

# 输出结果 只列出部分
ezz     2020-03-17T02:13:55Z
ezz1    2020-03-17T02:19:09Z
f0569eee-6888-4d7b-a40c-bf0ee4ce65ad    2020-03-10T06:09:44Z
f0953c46-45d2-4442-b0b4-24d4e34206ef    2020-03-14T00:46:38Z
f3d50918-fee0-438a-8d9d-5c80fab1e43c    2020-03-17T19:30:46Z
flink   2020-02-25T21:00:54Z
foobar  2020-03-12T19:16:42Z
foobbar 2020-03-12T19:00:40Z
form    2020-03-09T05:47:00Z
foundation-qa   2020-03-24T00:39:02Z
j2n1g7fjsa9rjhceprqhphfktreq5hjx        2020-02-26T02:21:04Z
jalgasdlkglskadg        2020-02-29T18:27:11Z
jayce   2020-03-03T20:56:08Z
jgjhghgj        2020-03-16T00:07:13Z
jh6-gateway     2020-03-23T10:19:00Z
jianmeng2       2020-03-02T09:16:31Z
jjj     2020-03-05T23:56:21Z
jvtest  2020-03-19T23:53:31Z
jyyy-enclosure  2020-03-03T15:10:44Z
kannappan300    2020-03-19T02:55:18Z
kdyotzwd9278ke3gvukngmuqovylerxu        2020-03-20T10:49:53Z
kek     2020-03-18T06:46:26Z
kgramlkxni5sbd116ep81gz3d38gysqd        2020-03-07T05:12:23Z
kkk     2020-03-05T23:56:12Z
kopia-test-00040bab8a787438     2020-03-21T12:20:30Z
kopia-test-fe2c2dc0cc28b253     2020-03-04T12:46:37Z
mikbucket       2020-03-19T21:16:31Z
minio-backup-bucket-name-here   2020-03-26T16:38:50Z
zzz01   2020-03-16T08:02:36Z
zzz02   2020-03-16T08:02:38Z

Sample Code 2 GET/PUT

#include <iostream>
#include <aws/s3/S3Client.h>
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>

// Include object request header
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <fstream>

using namespace Aws::S3;
using namespace Aws::S3::Model;
using namespace std;

int main(int argc, char* argv[]) {

    // Set log options and init aws sdk.
    Aws::SDKOptions options;
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
    Aws::InitAPI(options);

    // Create the client with configuration (endpoint, protocol, ssl, credential)
    Aws::Client::ClientConfiguration cfg;
    cfg.endpointOverride = "http://localhost:9000";  // S3服务器地址和端口
    cfg.scheme = Aws::Http::Scheme::HTTP;
    cfg.verifySSL = false;

    // Create credential with access key and secret key.
    Aws::Auth::AWSCredentials cred("admin", "admin123456");  // 认证的Key
    S3Client client(cred, cfg, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false);

    // Call api provided by aws sdk.
    // List buckets
    auto response = client.ListBuckets();
    if (response.IsSuccess()) {
        // Parse response
        auto buckets = response.GetResult().GetBuckets();
        for (auto iter = buckets.begin(); iter != buckets.end(); ++iter) {
            // cout bucket name and creation date.
            cout << iter->GetName() << "\t" << iter->GetCreationDate().ToLocalTimeString(Aws::Utils::DateFormat::ISO_8601) << endl;
        }
    } else {
        // Error handle.
        cout << "Error while ListBuckets " << response.GetError().GetExceptionName()
            << " " << response.GetError().GetMessage() << endl;
    }

    // Read Objects
    // Parameters
    std::string BucketName = "test";
    std::string KeyName = "key";
    
    // Local path to store the data returned by Cloud Storage
    std::string PathKey = "./test-data/key.png";

    // Create the object get request
    Aws::S3::Model::GetObjectRequest object_get_request;
    object_get_request.WithBucket(BucketName.c_str()).WithKey(KeyName.c_str());
    
    // Send Get Request
    auto get_response = client.GetObject(object_get_request);
    
    // Verify the status of response.
    if (get_response.IsSuccess())
    {
        // Read data and write into the file.
        Aws::OFStream local_file(PathKey.c_str(), std::ios::trunc | ::ios::out | std::ios::binary);
        if (local_file) {
            std::cout << "File exsits!" << std::endl;
        } else {
            std::cout << "File not exsits!" << std::endl;
        }
        local_file << get_response.GetResult().GetBody().rdbuf();
        if (local_file) {
            std::cout << "After write, File exsits!" << std::endl;
        } else {
            std::cout << "After write, File not exsits!" << std::endl;
        }
        std::cout << "Done!" << std::endl;
    }
    else
    {
        std::cout << "GetObject error: " <<
            get_response.GetError().GetExceptionName() << " " <<
            get_response.GetError().GetMessage() << std::endl;
    }

    // Write Objects
    // Parameters
    std::string writeKeyName = "keyw";    
    // Local path to store the data returned by Cloud Storage
    std::string writePathKey = "./test-data/key.png";

    PutObjectRequest putObjectRequest;
    putObjectRequest.WithBucket(BucketName.c_str()).WithKey(writeKeyName.c_str());
    auto input_data = Aws::MakeShared<Aws::FStream>("PutObjectInputStream", writePathKey.c_str(), std::ios_base::in | std::ios_base::binary);
    putObjectRequest.SetBody(input_data);
    auto putObjectResult = client.PutObject(putObjectRequest);
    if (putObjectResult.IsSuccess())
    {
        std::cout << "Upload Done!" << std::endl;
    }
    else
    {
        std::cout << "PutObject error: " <<
        putObjectResult.GetError().GetExceptionName() << " " <<
        putObjectResult.GetError().GetMessage() << std::endl;
    }

    // Deallocate AWS resources
    Aws::ShutdownAPI(options);
    return 0;
}
  • 注意路径的格式,相对路径和绝对路径的区分,上例使用的是相对路径。

将 aws-s3-cpp-sdk 封装为 C library

Create CPP Class

Project Tree
├── CMakeLists.txt
├── include
│   ├── S3Util.h
└── src
    ├── CMakeLists.txt
    ├── main.cpp
    └── S3Util.cpp

define header S3Util.h

#ifndef S3UTIL_H
#define S3UTIL_H
#include <iostream>
#include <aws/s3/S3Client.h>
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>

// Include object request header
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/s3/model/CreateBucketRequest.h>
#include <fstream>


using namespace Aws::S3;
using namespace Aws::S3::Model;
using namespace std;

class S3Util
{

private:
    static Aws::SDKOptions options;
    static S3Client* client;
public:
    S3Util();
    bool createBucket(char* BucketName);
    bool uploadfile(char* BucketName, char* objectKey, char* pathkey);
    bool downloadfile(char* BucketName, char* objectKey, char* pathkey);
    ~S3Util();
};
#endif

S3Util.cpp

#include "S3Util.h"

Aws::SDKOptions S3Util::options = {};
S3Client* S3Util::client = {};
S3Util::S3Util() {
    // Set log options and init aws sdk.
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
    Aws::InitAPI(options);

    // Create the client with configuration (endpoint, protocol, ssl, credential)
    Aws::Client::ClientConfiguration cfg;
    cfg.endpointOverride = "http://114.116.234.136:9000";  // S3服务器地址和端口
    cfg.scheme = Aws::Http::Scheme::HTTP;
    cfg.verifySSL = false;

    // Create credential with access key and secret key.
    Aws::Auth::AWSCredentials cred("admin", "admin123456");  // 认证的Key
    client = new S3Client(cred, cfg, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false);
}

bool S3Util::createBucket(char* BucketName) {
    CreateBucketRequest createBucketReq;
    createBucketReq.WithBucket(BucketName);
    auto createResponse = client->CreateBucket(createBucketReq);
    if (createResponse.IsSuccess())
    {
        std::cout << "Create Bucket Done!" << std::endl;
        return true;
    }
    else
    {
        std::cout << "Create Bucket error: " <<
        createResponse.GetError().GetExceptionName() << " " <<
        createResponse.GetError().GetMessage() << std::endl;
        return false;
    }
}

bool S3Util::uploadfile(char* BucketName, char* objectKey, char* pathkey) {
    PutObjectRequest putObjectRequest;
    putObjectRequest.WithBucket(BucketName).WithKey(objectKey);
    auto input_data = Aws::MakeShared<Aws::FStream>("PutObjectInputStream", pathkey, std::ios_base::in | std::ios_base::binary);
    putObjectRequest.SetBody(input_data);
    auto putObjectResult = client->PutObject(putObjectRequest);
    if (putObjectResult.IsSuccess())
    {
        std::cout << "Upload Done!" << std::endl;
        return true;
    }
    else
    {
        std::cout << "PutObject error: " <<
        putObjectResult.GetError().GetExceptionName() << " " <<
        putObjectResult.GetError().GetMessage() << std::endl;
        return false;
    }
}

bool S3Util::downloadfile(char* BucketName, char* objectKey, char* pathkey) {
    // Create the object get request
    Aws::S3::Model::GetObjectRequest object_get_request;
    object_get_request.WithBucket(BucketName).WithKey(objectKey);
    
    // Send Get Request
    auto get_response = client->GetObject(object_get_request);
    
    // Verify the status of response.
    if (get_response.IsSuccess())
    {
        // Read data and write into the file.
        Aws::OFStream local_file(pathkey, std::ios::trunc | ::ios::out | std::ios::binary);
        if (local_file) {
            std::cout << "File exsits!" << std::endl;
        } else {
            std::cout << "File not exsits!" << std::endl;
        }
        local_file << get_response.GetResult().GetBody().rdbuf();
        if (local_file) {
            std::cout << "After write, File exsits!" << std::endl;
        } else {
            std::cout << "After write, File not exsits!" << std::endl;
        }
        std::cout << "Done!" << std::endl;
        return true;
    }
    else
    {
        std::cout << "GetObject error: " <<
        get_response.GetError().GetExceptionName() << " " << get_response.GetError().GetMessage() << std::endl;
        return false;
    }
}

S3Util::~S3Util() {
    Aws::ShutdownAPI(options);
}

测试程序 main.cpp

#include "S3Util.h"
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
    std::cout << "Start!"<< std::endl;
    char *BucketName = "test";
    char *objectKey = "key";
    char *path = "./../test-data/key.png";
    S3Util* util = new S3Util();
    bool result = util->downloadfile(BucketName, objectKey, path);
    std::cout << "Download reslut " << result << std::endl;
    std::cout << "End!"<< std::endl;
    return 0;
}

CMake build

Outside CMakeLists.txt
#CMake最低版本号要求
cmake_minimum_required(VERSION 2.8)

#指定项目名称
project(s3util)

#指定版本信息
set(CMAKE_SYSTEM_VERSION 1)

#若是需要指定编译器路径
#set(CROSS_TOOLCHAIN_PREFIX "/path/arm-linux-")
#指定编译器
#set(CMAKE_C_COMPILER   "${CROSS_TOOLCHAIN_PREFIX}gcc")
#set(CMAKE_CXX_COMPILER "${CROSS_TOOLCHAIN_PREFIX}g++")

#使用默认路径的g++指定编译器  
#set(CMAKE_CXX_COMPILER "g++") 

#指定编译选项
set(CMAKE_BUILD_TYPE Debug )

#指定编译目录
set(PROJECT_BINARY_DIR ${PROJECT_SOURCE_DIR}/build)

#添加子目录,这样进入源码文件src目录可以继续构建  
add_subdirectory(${PROJECT_SOURCE_DIR}/src)
Inside CMakeLists.txt
#查找当前目录下的所有源文件,
#并将名称保存到DIR_LIB_SRCS目录
#aux_source_directory(. DIR_LIB_SRCS)

#指定头文件目录,PROJECT_SOURCE_DIR为工程的根目录  
include_directories(${PROJECT_SOURCE_DIR}/include)

add_compile_options( -std=c++11 )
include_directories(/c++/C-Example/linux/install/usr/local/include)
link_directories(/c++/C-Example/linux/install/usr/local/lib64)

#指定可执行文件的输出目录,输出到bin下面  
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#生成动态库    
add_library(s3_shared_demo SHARED S3Util.cpp)
#设置库输出名为 shared => libshared.so  
set_target_properties(s3_shared_demo PROPERTIES OUTPUT_NAME "s3shared")

#生成静态库  
add_library(s3_static_demo STATIC S3Util.cpp)
#设置输库出名为 static => libstatic.a  
set_target_properties(s3_static_demo PROPERTIES OUTPUT_NAME "s3static")

#指定库文件输出路径  
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

#在指定目录下查找库,并保存在LIBPATH变量中
find_library(LIBPATHS shared ${PROJECT_SOURCE_DIR}/lib)

#指定生成目标
add_executable(main main.cpp)

#链接共享库
target_link_libraries(main ${LIBPATHS})
target_link_libraries(main -laws-cpp-sdk-core -laws-cpp-sdk-s3 s3_shared_demo)

编写 Wrapper

Project Tree
├── CMakeLists.txt
├── include
│   ├── S3TestWrapper.h
└── src
    ├── CMakeLists.txt
    ├── main.cpp
    └── S3Util.cpp

define header

#ifndef S3TESTWRAPPER_H
#define S3TESTWRAPPER_H

#ifdef __cplusplus
extern "C" {
#endif 

    int createBucket(char* BucketName);
    int uploadfile(char* BucketName, char* objectKey, char* pathkey);
    int downloadfile(char* BucketName, char* objectKey, char* pathkey);

#ifdef __cplusplus
}
#endif 
#endif

define wrapper

#include "S3TestWrapper.h"
#include "S3Util.h"

#ifdef __cplusplus
extern "C" {
#endif 

    int createBucket(char* BucketName) {
        S3Util* util = new S3Util();
        return util->createBucket(BucketName);
    }
    int uploadfile(char* BucketName, char* objectKey, char* pathkey) {
        S3Util* util = new S3Util();
        return util->uploadfile(BucketName, objectKey, pathkey);
    }
    int downloadfile(char* BucketName, char* objectKey, char* pathkey) {
        S3Util* util = new S3Util();
        return util->downloadfile(BucketName, objectKey, pathkey);
    }

#ifdef __cplusplus
};
#endif 

测试程序 main.cpp

#include "S3TestWrapper.h"
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
    std::cout << "Start!"<< std::endl;
    char *BucketName = "test";
    char *objectKey = "key";
    char *path = "./../test-data/key.png";
    bool result = downloadfile(BucketName, objectKey, path);
    std::cout << "Download reslut " << result << std::endl;
    std::cout << "End!"<< std::endl;
    return 0;
}

Inside CMakeLists.txt

#查找当前目录下的所有源文件,
#并将名称保存到DIR_LIB_SRCS目录
aux_source_directory(. DIR_LIB_SRCS)

#指定头文件目录,PROJECT_SOURCE_DIR为工程的根目录  
include_directories(${PROJECT_SOURCE_DIR}/include)

add_compile_options( -std=c++11 )
include_directories(/c++/C-Example/linux/s3util/include)
link_directories(/c++/C-Example/linux/s3util/lib)
include_directories(/c++/C-Example/linux/install/usr/local/include)
link_directories(/c++/C-Example/linux/install/usr/local/lib64)


#指定可执行文件的输出目录,输出到bin下面  
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#生成动态库    
add_library(s3_shared_demo_c SHARED S3TestWrapper.cpp)
#设置库输出名为 shared => libshared.so  
set_target_properties(s3_shared_demo_c PROPERTIES OUTPUT_NAME "s3sharedC")

#生成静态库  
add_library(s3_static_demo_c STATIC S3TestWrapper.cpp)
#设置输库出名为 static => libstatic.a  
set_target_properties(s3_static_demo_c PROPERTIES OUTPUT_NAME "s3staticC")

#指定库文件输出路径  
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

#在指定目录下查找库,并保存在LIBPATH变量中
find_library(LIBPATHS shared ${PROJECT_SOURCE_DIR}/lib)

#指定生成目标
add_executable(main main.cpp)

#链接共享库
target_link_libraries(main ${LIBPATHS})
target_link_libraries(main -ls3shared -laws-cpp-sdk-core -laws-cpp-sdk-s3 s3_shared_demo_c)

#指定生成目标
add_executable(test test.c)

#链接共享库
target_link_libraries(test ${LIBPATHS})
target_link_libraries(test -ls3shared -laws-cpp-sdk-core -laws-cpp-sdk-s3 s3_shared_demo_c)

Outside CMakeLists.txt

#CMake最低版本号要求
cmake_minimum_required(VERSION 2.8)

#指定项目名称
project(s3wrapperC)

#指定版本信息
set(CMAKE_SYSTEM_VERSION 1)

#若是需要指定编译器路径
#set(CROSS_TOOLCHAIN_PREFIX "/path/arm-linux-")
#指定编译器
#set(CMAKE_C_COMPILER   "${CROSS_TOOLCHAIN_PREFIX}gcc")
#set(CMAKE_CXX_COMPILER "${CROSS_TOOLCHAIN_PREFIX}g++")

#使用默认路径的g++指定编译器  
#set(CMAKE_CXX_COMPILER "g++") 

#指定编译选项
set(CMAKE_BUILD_TYPE Debug )

#指定编译目录
set(PROJECT_BINARY_DIR ${PROJECT_SOURCE_DIR}/build)

#添加子目录,这样进入源码文件src目录可以继续构建  
add_subdirectory(${PROJECT_SOURCE_DIR}/src)

使用 C 语言测试 main.c

main.c
#include "S3TestWrapper.h"
#include<stdio.h>

int main(int argc, char* argv[]) {
    printf("start!\n");
    char *BucketName = "test";
    char *objectKey = "key";
    char *path = "./../test-data/key-test.png";
    int result = downloadfile(BucketName, objectKey, path);
    printf("Download Result: %d!\n", result);
    printf("end!\n");
    return 0;
}
CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo)

add_compile_options( -std=c++11 )
include_directories(/c++/C-Example/linux/install/usr/local/include)
link_directories(/c++/C-Example/linux/install/usr/local/lib64)
include_directories(/c++/C-Example/linux/s3util/include)
link_directories(/c++/C-Example/linux/s3util/lib)
include_directories(/c++/C-Example/linux/s3-c/test/include)
link_directories(/c++/C-Example/linux/s3-c/test/lib)

# 指定生成目标
add_executable(Demo main.c)
target_link_libraries(Demo -laws-cpp-sdk-core -laws-cpp-sdk-s3 -ls3shared -ls3sharedC)

集成 TCMU

参考资料