使用libhv创建客户端并推送图片到MinIo文件服务器 范例
minio_handler.h
/*** MinIO 对象存储处理类* * 功能:封装 MinIO 上传相关的所有操作* - 图片上传(支持多种格式)* - AWS Signature V4 签名认证* - Base64 解码* - SHA256/HMAC-SHA256 加密* * 作者:lijilei* 日期:2025-10-14*/#ifndef MINIO_HANDLER_H
#define MINIO_HANDLER_H#include <string>
#include <cstdint>namespace OHOS {
namespace ThingAccessManager {/*** MinIO 配置信息结构体*/
struct MinIOConfig {std::string endpoint; // MinIO 服务器地址,例如:http://14.22.85.23:9090std::string bucket; // 存储桶名称,例如:adplatformstd::string accessKey; // 访问密钥std::string secretKey; // 秘密密钥std::string region; // 区域,默认:us-east-1MinIOConfig(): endpoint("http://14.22.85.23:9090"),bucket("adplatform"),accessKey("I8IgMq8aHegL7Hm6kwSx"),secretKey("hrRIERpQXvbKOh64ssMLNUNS3Bg3voVXqsmP2ID5"),region("us-east-1") {}
};class MinIOHandler {
public:explicit MinIOHandler(const MinIOConfig& config = MinIOConfig());~MinIOHandler();/*** 上传截图到 MinIO* * @param screenShotId 屏幕ID* @param screenShotName 截图文件名(用于推断扩展名)* @param screenShotDataBase64 Base64 编码的截图数据* @param outImageUrl 输出:上传成功后的图片 URL* @return true-成功,false-失败*/bool UploadScreenshot(const std::string& screenShotId,const std::string& screenShotName,const std::string& screenShotDataBase64,std::string& outImageUrl);/*** 上传二进制图片数据到 MinIO* * @param screenShotId 屏幕ID* @param screenShotName 截图文件名* @param binaryData 二进制图片数据* @param outImageUrl 输出:上传成功后的图片 URL* @return true-成功,false-失败*/bool UploadBinaryData(const std::string& screenShotId,const std::string& screenShotName,const std::string& binaryData,std::string& outImageUrl);/*** 上传文件到 MinIO(通用方法)* * @param objectPath 对象路径,例如:adv/captureScreen/screen001/file.png* @param contentType MIME 类型,例如:image/png* @param fileData 文件二进制数据* @param outUrl 输出:上传成功后的完整 URL* @return true-成功,false-失败*/bool UploadFile(const std::string& objectPath,const std::string& contentType,const std::string& fileData,std::string& outUrl);void SetConfig(const MinIOConfig& config);MinIOConfig GetConfig() const;/*** Base64 解码(静态工具方法)* @param encoded Base64 编码的字符串* @return 解码后的二进制数据*/static std::string Base64Decode(const std::string& encoded);/*** Base64 编码(静态工具方法)* @param data 原始二进制数据* @return Base64 编码的字符串*/static std::string Base64Encode(const std::string& data);private:MinIOConfig config_; // MinIO 配置信息/*** SHA256 哈希计算(返回十六进制字符串)* @param data 输入数据* @return SHA256 哈希值(十六进制)*/static std::string Sha256Hex(const std::string& data);/*** HMAC-SHA256 计算(返回原始字节)* @param key 密钥* @param data 数据* @return HMAC-SHA256 结果(原始字节)*/static std::string HmacSha256Raw(const std::string& key, const std::string& data);/*** HMAC-SHA256 计算(返回十六进制字符串)* @param key 密钥* @param data 数据* @return HMAC-SHA256 结果(十六进制)*/static std::string HmacSha256Hex(const std::string& key, const std::string& data);/*** AWS Signature V4 - 派生签名密钥* @param secretKey 秘密密钥* @param dateStamp 日期戳(格式:20251014)* @param region 区域* @param service 服务名称* @return 签名密钥*/static std::string DeriveSigningKey(const std::string& secretKey,const std::string& dateStamp,const std::string& region,const std::string& service);/*** 获取当前 UTC 时间字符串* @param amzDate 输出:AMZ 日期格式(20251014T062740Z)* @param dateStamp 输出:日期戳(20251014)*/static void GetAmzDateStrings(std::string& amzDate, std::string& dateStamp);/*** 生成随机文件名* @param extension 文件扩展名* @return 随机文件名(格式:时间戳_随机数.扩展名)*/static std::string GenerateRandomFileName(const std::string& extension);/*** 从文件名提取扩展名* @param filename 文件名* @return 扩展名(小写,不含点号)*/static std::string GetFileExtension(const std::string& filename);/*** 根据扩展名获取 Content-Type* @param extension 文件扩展名* @return MIME 类型*/static std::string GetContentType(const std::string& extension);/*** 使用 libhv HttpClient 上传到 MinIO(核心方法)* 实现完整的 AWS Signature V4 签名认证* * @param endpoint MinIO 服务器地址* @param bucket 存储桶名称* @param accessKey 访问密钥* @param secretKey 秘密密钥* @param objectPath 对象路径* @param contentType MIME 类型* @param imageData 文件数据* @param region 区域* @param outUrl 输出:上传成功后的 URL* @return true-成功,false-失败*/static bool UploadToMinIOWithLibhv(const std::string& endpoint,const std::string& bucket,const std::string& accessKey,const std::string& secretKey,const std::string& objectPath,const std::string& contentType,const std::string& imageData,const std::string& region,std::string& outUrl);
};} // namespace ThingAccessManager
} // namespace OHOS#endif // MINIO_HANDLER_H
minio_handler.cpp
/*** MinIO 对象存储处理类实现*/#include "minio_handler.h"
#include "nanning_sub_constant.h"
#include "nanning_subway_handler.h"#include <iostream>
#include <chrono>
#include <random>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <cstring>
#include <algorithm>
#include <vector>
#include "hlog.h"
#include "HttpClient.h"
#include "HttpMessage.h"namespace OHOS {
namespace ThingAccessManager {class SHA256 {
private:uint32_t h[8];uint64_t bitlen;uint8_t data[64];uint32_t datalen;static constexpr uint32_t K[64] = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};static uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); }static uint32_t ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); }static uint32_t maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); }static uint32_t ep0(uint32_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }static uint32_t ep1(uint32_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }static uint32_t sig0(uint32_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); }static uint32_t sig1(uint32_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); }void transform() {uint32_t m[64];for (uint32_t i = 0, j = 0; i < 16; ++i, j += 4) {m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);}for (uint32_t i = 16; i < 64; ++i) {m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];}uint32_t a = h[0], b = h[1], c = h[2], d = h[3];uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];for (uint32_t i = 0; i < 64; ++i) {uint32_t t1 = hh + ep1(e) + ch(e, f, g) + K[i] + m[i];uint32_t t2 = ep0(a) + maj(a, b, c);hh = g; g = f; f = e; e = d + t1;d = c; c = b; b = a; a = t1 + t2;}h[0] += a; h[1] += b; h[2] += c; h[3] += d;h[4] += e; h[5] += f; h[6] += g; h[7] += hh;}public:SHA256() { init(); }void init() {h[0] = 0x6a09e667; h[1] = 0xbb67ae85; h[2] = 0x3c6ef372; h[3] = 0xa54ff53a;h[4] = 0x510e527f; h[5] = 0x9b05688c; h[6] = 0x1f83d9ab; h[7] = 0x5be0cd19;bitlen = 0;datalen = 0;}void update(const uint8_t* data, size_t len) {for (size_t i = 0; i < len; ++i) {this->data[datalen++] = data[i];if (datalen == 64) {transform();bitlen += 512;datalen = 0;}}}void final(uint8_t* hash) {uint32_t i = datalen;if (datalen < 56) {data[i++] = 0x80;while (i < 56) data[i++] = 0x00;} else {data[i++] = 0x80;while (i < 64) data[i++] = 0x00;transform();memset(data, 0, 56);}bitlen += datalen * 8;data[63] = bitlen; data[62] = bitlen >> 8; data[61] = bitlen >> 16; data[60] = bitlen >> 24;data[59] = bitlen >> 32; data[58] = bitlen >> 40; data[57] = bitlen >> 48; data[56] = bitlen >> 56;transform();for (i = 0; i < 4; ++i) {hash[i] = (h[0] >> (24 - i * 8)) & 0xff;hash[i + 4] = (h[1] >> (24 - i * 8)) & 0xff;hash[i + 8] = (h[2] >> (24 - i * 8)) & 0xff;hash[i + 12] = (h[3] >> (24 - i * 8)) & 0xff;hash[i + 16] = (h[4] >> (24 - i * 8)) & 0xff;hash[i + 20] = (h[5] >> (24 - i * 8)) & 0xff;hash[i + 24] = (h[6] >> (24 - i * 8)) & 0xff;hash[i + 28] = (h[7] >> (24 - i * 8)) & 0xff;}}
};MinIOHandler::MinIOHandler(const MinIOConfig& config): config_(config) {NN_LOGI("MinIOHandler created, endpoint: %{public}s", config_.endpoint.c_str());
}MinIOHandler::~MinIOHandler() {NN_LOGI("MinIOHandler destroyed");
}bool MinIOHandler::UploadScreenshot(const std::string& screenShotId,const std::string& screenShotName,const std::string& screenShotDataBase64,std::string& outImageUrl) {NN_LOGI("UploadScreenshot: screenId=%{public}s, name=%{public}s, base64Size=%{public}zu",screenShotId.c_str(), screenShotName.c_str(), screenShotDataBase64.size());if (screenShotDataBase64.empty()) {NN_LOGE("Base64 data is empty");return false;}// Base64 解码NN_LOGI("Decoding Base64 data...");std::string binaryData = Base64Decode(screenShotDataBase64);NN_LOGI("Decoded binary size: %{public}zu bytes", binaryData.size());if (binaryData.empty()) {NN_LOGE("Base64 decode failed");return false;}return UploadBinaryData(screenShotId, screenShotName, binaryData, outImageUrl);
}bool MinIOHandler::UploadBinaryData(const std::string& screenShotId,const std::string& screenShotName,const std::string& binaryData,std::string& outImageUrl) {NN_LOGI("UploadBinaryData: screenId=%{public}s, name=%{public}s, dataSize=%{public}zu",screenShotId.c_str(), screenShotName.c_str(), binaryData.size());// 提取文件扩展名std::string fileExt = GetFileExtension(screenShotName);std::string contentType = GetContentType(fileExt);// 生成随机文件名std::string randomFileName = GenerateRandomFileName(fileExt);// 构建对象路径: adv/captureScreen/屏幕ID/随机文件名称.扩展名std::string objectPath = "adv/captureScreen/" + screenShotId + "/" + randomFileName;NN_LOGI("ObjectPath: %{public}s, ContentType: %{public}s",objectPath.c_str(), contentType.c_str());return UploadFile(objectPath, contentType, binaryData, outImageUrl);
}bool MinIOHandler::UploadFile(const std::string& objectPath,const std::string& contentType,const std::string& fileData,std::string& outUrl) {return UploadToMinIOWithLibhv(config_.endpoint, config_.bucket,config_.accessKey, config_.secretKey,objectPath, contentType,fileData, config_.region, outUrl);
}void MinIOHandler::SetConfig(const MinIOConfig& config) {config_ = config;NN_LOGI("MinIO config updated");
}MinIOConfig MinIOHandler::GetConfig() const {return config_;
}std::string MinIOHandler::Sha256Hex(const std::string& data) {SHA256 sha;uint8_t hash[32];sha.update(reinterpret_cast<const uint8_t*>(data.data()), data.size());sha.final(hash);std::ostringstream oss;for (int i = 0; i < 32; ++i) {oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);}return oss.str();
}std::string MinIOHandler::HmacSha256Raw(const std::string& key, const std::string& data) {const size_t blockSize = 64;uint8_t k0[blockSize];memset(k0, 0, blockSize);if (key.size() > blockSize) {SHA256 sha;uint8_t hash[32];sha.update(reinterpret_cast<const uint8_t*>(key.data()), key.size());sha.final(hash);memcpy(k0, hash, 32);} else {memcpy(k0, key.data(), key.size());}uint8_t kipad[blockSize], kopad[blockSize];for (size_t i = 0; i < blockSize; ++i) {kipad[i] = k0[i] ^ 0x36;kopad[i] = k0[i] ^ 0x5c;}SHA256 sha1;sha1.update(kipad, blockSize);sha1.update(reinterpret_cast<const uint8_t*>(data.data()), data.size());uint8_t inner[32];sha1.final(inner);SHA256 sha2;sha2.update(kopad, blockSize);sha2.update(inner, 32);uint8_t result[32];sha2.final(result);return std::string(reinterpret_cast<const char*>(result), 32);
}std::string MinIOHandler::HmacSha256Hex(const std::string& key, const std::string& data) {std::string raw = HmacSha256Raw(key, data);std::ostringstream oss;for (size_t i = 0; i < raw.size(); ++i) {oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(static_cast<unsigned char>(raw[i]));}return oss.str();
}std::string MinIOHandler::DeriveSigningKey(const std::string& secretKey,const std::string& dateStamp,const std::string& region,const std::string& service) {std::string kDate = HmacSha256Raw("AWS4" + secretKey, dateStamp);std::string kRegion = HmacSha256Raw(kDate, region);std::string kService = HmacSha256Raw(kRegion, service);std::string kSigning = HmacSha256Raw(kService, "aws4_request");return kSigning;
}void MinIOHandler::GetAmzDateStrings(std::string& amzDate, std::string& dateStamp) {auto now = std::chrono::system_clock::now();std::time_t tt = std::chrono::system_clock::to_time_t(now);std::tm tm_utc;gmtime_r(&tt, &tm_utc);std::ostringstream oss1, oss2;oss1 << std::put_time(&tm_utc, "%Y%m%dT%H%M%SZ");oss2 << std::put_time(&tm_utc, "%Y%m%d");amzDate = oss1.str();dateStamp = oss2.str();
}std::string MinIOHandler::GenerateRandomFileName(const std::string& extension) {auto now = std::chrono::system_clock::now();auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<> dis(1000, 9999);int randomNum = dis(gen);std::ostringstream oss;oss << timestamp << "_" << randomNum << "." << extension;return oss.str();
}std::string MinIOHandler::GetFileExtension(const std::string& filename) {size_t dotPos = filename.find_last_of('.');if (dotPos != std::string::npos && dotPos < filename.size() - 1) {std::string ext = filename.substr(dotPos + 1);std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);return ext;}return "png"; // 默认png
}std::string MinIOHandler::GetContentType(const std::string& extension) {if (extension == "jpg" || extension == "jpeg") {return "image/jpeg";} else if (extension == "png") {return "image/png";} else if (extension == "bmp") {return "image/bmp";} else if (extension == "gif") {return "image/gif";}return "image/png";
}std::string MinIOHandler::Base64Decode(const std::string& encoded) {static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ""abcdefghijklmnopqrstuvwxyz""0123456789+/";std::string decoded;std::vector<int> T(256, -1);for (int i = 0; i < 64; i++) {T[base64_chars[i]] = i;}int val = 0, valb = -8;for (unsigned char c : encoded) {if (T[c] == -1) break;val = (val << 6) + T[c];valb += 6;if (valb >= 0) {decoded.push_back(char((val >> valb) & 0xFF));valb -= 8;}}return decoded;
}std::string MinIOHandler::Base64Encode(const std::string& data) {static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ""abcdefghijklmnopqrstuvwxyz""0123456789+/";std::string ret;int i = 0;int j = 0;unsigned char char_array_3[3];unsigned char char_array_4[4];const char* bytes_to_encode = data.c_str();size_t in_len = data.size();while (in_len--) {char_array_3[i++] = *(bytes_to_encode++);if (i == 3) {char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for(i = 0; i < 4; i++) {ret += base64_chars[char_array_4[i]];}i = 0;}}if (i) {for(j = i; j < 3; j++) {char_array_3[j] = '\0';}char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for (j = 0; j < i + 1; j++) {ret += base64_chars[char_array_4[j]];}while(i++ < 3) {ret += '=';}}return ret;
}bool MinIOHandler::UploadToMinIOWithLibhv(const std::string& endpoint,const std::string& bucket,const std::string& accessKey,const std::string& secretKey,const std::string& objectPath,const std::string& contentType,const std::string& imageData,const std::string& region,std::string& outUrl) {NN_LOGI("UploadToMinIOWithLibhv start");NN_LOGI("Endpoint: %{public}s, Bucket: %{public}s", endpoint.c_str(), bucket.c_str());NN_LOGI("ObjectPath: %{public}s, DataSize: %{public}zu", objectPath.c_str(), imageData.size());// 1. 构建完整URLstd::string url = endpoint;if (url.back() == '/') {url.pop_back();}url = url + "/" + bucket + "/" + objectPath;NN_LOGI("Upload URL: %{public}s", url.c_str());// 2. 提取 hoststd::string host = endpoint.substr(endpoint.find("://") + 3);// 3. 获取时间戳std::string amzDate, dateStamp;GetAmzDateStrings(amzDate, dateStamp);// 4. 计算 payload 哈希std::string payloadHash = Sha256Hex(imageData);NN_LOGD("Payload hash: %{public}s", payloadHash.c_str());// 5. 构建 Canonical Request(AWS Signature V4 标准)std::string method = "PUT";std::string canonicalUri = "/" + bucket + "/" + objectPath;std::string canonicalQueryString = "";// Canonical Headers(必须按字母顺序排列,且小写)std::string canonicalHeaders = "content-type:" + contentType + "\n" +"host:" + host + "\n" +"x-amz-content-sha256:" + payloadHash + "\n" +"x-amz-date:" + amzDate + "\n";std::string signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date";std::string canonicalRequest = method + "\n" +canonicalUri + "\n" +canonicalQueryString + "\n" +canonicalHeaders + "\n" +signedHeaders + "\n" +payloadHash;NN_LOGD("Canonical request hash: %{public}s", Sha256Hex(canonicalRequest).c_str());// 6. 构建 String to Signstd::string algorithm = "AWS4-HMAC-SHA256";std::string service = "s3";std::string credentialScope = dateStamp + "/" + region + "/" + service + "/aws4_request";std::string stringToSign = algorithm + "\n" +amzDate + "\n" +credentialScope + "\n" +Sha256Hex(canonicalRequest);NN_LOGD("String to sign: %{public}s", stringToSign.substr(0, 50).c_str());// 7. 计算签名std::string signingKey = DeriveSigningKey(secretKey, dateStamp, region, service);std::string signature = HmacSha256Hex(signingKey, stringToSign);NN_LOGD("Signature: %{public}s", signature.c_str());// 8. 构建 Authorization headerstd::string authorization = algorithm + " " +"Credential=" + accessKey + "/" + credentialScope + ", " +"SignedHeaders=" + signedHeaders + ", " +"Signature=" + signature;NN_LOGD("Authorization: %{public}s", authorization.substr(0, 100).c_str());// 9. 创建 libhv HttpClienthv::HttpClient* client = new hv::HttpClient();// 10. 构建 PUT 请求HttpRequest req;req.method = HTTP_PUT;req.url = url;// 11. 设置请求头(注意:header key 必须小写)req.headers["host"] = host;req.headers["content-type"] = contentType;req.headers["x-amz-date"] = amzDate;req.headers["x-amz-content-sha256"] = payloadHash;req.headers["authorization"] = authorization;// 12. 设置请求体req.body = imageData;NN_LOGI("Sending PUT request...");// 13. 发送请求HttpResponse resp;int ret = client->send(&req, &resp);delete client;if (ret != 0) {NN_LOGE("HttpClient send failed, ret: %{public}d", ret);return false;}NN_LOGI("Response status: %{public}d", resp.status_code);NN_LOGD("Response body: %{public}s", resp.body.c_str());// 14. 处理响应if (resp.status_code >= 200 && resp.status_code < 300) {outUrl = url;NN_LOGI("Upload success! URL: %{public}s", outUrl.c_str());return true;} else {NN_LOGE("Upload failed, status: %{public}d, body: %{public}s",resp.status_code, resp.body.c_str());return false;}
}} // namespace ThingAccessManager
} // namespace OHOS
调用方法:
std::string minioImageUrl;
MinIOHandler minioHandler;
minioHandler.UploadScreenshot(screenShotId, screenShotName, screenShotDataBase64, minioImageUrl)
相关日志: