# C++ SDK

> ### *在使用前，请先阅读*[*数据模型*](https://54td.gitbook.io/shence/technical_guide/data_import/data_model)*的介绍。*

## 1. 概述

本文档所介绍的 C++ SDK 是用于记录客户端埋点的（而不是服务端），例如在 MFC 程序中集成，以收集用户在程序界面上的操作。若需要服务端埋点，请使用 [神策分析 C SDK](https://54td.gitbook.io/shence/technical_guide/detailed_guide_server/c_sdk)。

本 SDK 区别于其他 SDK 在于将数据通过网络发送到服务端需要在代码适当的位置显式调用 `Flush()` 函数：

1. 将数据发送到服务端需要有可用的网络连接；
2. 在适当的位置调用，如后台线程，避免阻塞界面操作等。

进程退出时，若内存中仍有未 Flush 发送到服务端的数据，则会将数据保存到指定的暂存文件里，下次 Flush 时会从文件加载一起发送。可以通过参数调整最多暂存的数据条数，若未发送的数据条数超过该值，则从最早的数据开始淘汰。

* 对暂存文件的读写未使用文件锁，请避免多进程操作同一个文件。

## 2. 集成神策分析 SDK

SDK 源文件可从 <https://github.com/sensorsdata/sa-sdk-cpp> 获取。使用时可以将代码直接集成到目标项目中，或先编译为库再引入，SDK 源代码包括：

```
./include/sensors_analytics_sdk.h
./src/sensors_analytics_sdk.cpp
```

SDK 依赖的第三方库包括：

1. [curl](https://curl.haxx.se/): 用于网络请求；
2. [zlib](http://www.zlib.net/): 用于 gzip 压缩。

其他说明：

* github 上的 [vs2005](https://github.com/sensorsdata/sa-sdk-cpp/tree/vs2005) 分支代码支持在 Visual Studio 2005 版本的 C++ 下编译使用。
* 若直接引入源代码文件并使用 Visual Studio 编译报错，请看最开始的报错是否是“warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失”，可以将引入的几个代码文件“文件->高级保存选项->编码->Unicode-代码页1200”然后保存再尝试编译。VS2017 及以上可以搜索 “Visual Studio 2017隐藏高级保存选项” 找到该功能。
* 字符串类型的属性值（主要是使用中文值时）需要是合法的 UTF-8 编码，否则会在 stderr 报错无法导入字段，这时请检查是否使用了 GBK 编码（例如这个字符串所在源代码文件是用 GBK 编码保存的）。将 GBK 编码的 string 转成 UTF-8 的方法，Windows 下可以用 MultiByteToWideChar、WideCharToMultiByte；Linux 下可以用 iconv，具体方法可以搜索。

### 2.1 Windows 下 SDK 依赖

您可以直接尝试使用我们编译好的依赖文件，或自行编译。

#### 2.1.1 使用我们编译的 curl 和 zlib

我们在 Visual Studio 2005 上编译了适用 Windows XP 的 curl 和 zlib。使用方法如下：

1. 下载 [curl 源代码](https://curl.haxx.se/download/curl-7.61.1.zip)，在项目名上右键 -> 属性 -> C/C++ -> 常规 -> “附加包含目录”添加 curl 源代码目录下的 include 目录；
2. 下载 [zlib 源代码](https://zlib.net/zlib-1.2.11.tar.gz)，在项目名上右键 -> 属性 -> C/C++ -> 常规 -> “附加包含目录”添加 zlib 源代码目录；
3. 下载编译好的 [curl 和 zlib 库文件](http://download.sensorsdata.cn/release/sdk/cpp/deps_vs2005_xp32_20181030.zip)，在项目的“资源文件”右键 -> 添加 -> 现有项，选择 libcurl.dll、libcurl.lib、zlib.lib；

更高版本的 Visual Studio 也可以引入并使用这个预编译的库。

#### 2.1.2 自行编译 curl

1. 下载 curl 源码：<https://curl.haxx.se/download/curl-7.61.1.zip> 并解压；
2. 开始菜单中打开 Visual Studio 目录中的 `VS2017的开发人员命令提示符`，并切换到 curl 解压目录下的 `winbuild` 目录下；
3. 执行命令开始编译：

   ```
   nmake /f Makefile.vc mode=dll
   ```

   如需要静态编译：

   ```
   nmake /f Makefile.vc mode=static ENABLE_IDN=no
   ```
4. 编译输出在 curl 目录 `builds` 下。

如果需要静态链接 curl，需要在项目配置中：

1. 链接器 -> 常规 -> 链接库依赖，添加 libcurl\_a.lib;Ws2\_32.lib;Wldap32.lib;winmm.lib;Crypt32.lib
2. 在C/C++ -> 预处理器 -> 预处理器定义中，加入 CURL\_STATICLIB

另外，若希望上报数据使用 HTTPS 做传输加密，建议编译 curl 时配置使用 OpenSSL 以获取更好的兼容性，或使用我们编译好的 curl。

#### 2.1.3 自行编译 zlib

1. 下载 zlib 源码：<https://zlib.net/zlib-1.2.11.tar.gz>
2. 开始菜单中打开 Visual Studio 目录中的 `VS2017的开发人员命令提示符`，并切换到 zlib 解压目录下；
3. 执行命令开始编译：

   ```
   nmake -f win32/Makefile.msc
   ```
4. 编译输出在 zlib 目录下。

## 3. 初始化神策分析 SDK

在程序中使用

```c
// 初始化 SDK
// data_file_path: 暂存文件路径，用于将未发送的数据临时保存在磁盘
// server_url: 神策服务器地址
// distinct_id: 标识一个用户的 ID
// is_login_id: distinct_id 参数传值是否是一个“登录 ID”
// max_staging_record_count: 在发送队列中最多保存的数据条数，若当前未发送数据条数达到该值，
//                           新埋点记录将淘汰最早的一个记录
sensors_analytics::Sdk::Init(staging_file_path,
                             server_url,
                             distinct_id,
                             is_login_id,
                             max_staging_record_size);
```

* `distinct_id` 是标识用户的 ID，若初始化 SDK 时无可用 ID，可以随机生成一个 `UUID` 作为 `distinct_id`，并且 `is_login_id` 传值为 `false`，并将这个 ID 保存起来，下次初始化 SDK 时传入相同的值；若有可用的“登录 ID”，可以传入“登录 ID”的值，并且 `is_login_id` 传值为 `true`。更多关于标识用户的介绍请见[如何准确的标识用户](https://54td.gitbook.io/shence/technical_guide/data_import/user_identify)。

## 4. 追踪事件

首次接入神策分析时，建议先追踪 3\~5 个关键的事件，只需要几行代码，便能体验神策分析的分析功能。例如：

* 图片社交产品，可以追踪用户浏览图片和评论事件
* 电商产品，可以追踪用户注册、浏览商品和下订单等事件

用户通过

```c
// 跟踪一个用户的行为
sensors_analytics::Sdk::Track(event_name);
sensors_analytics::Sdk::Track(event_name, event_properties);
```

接口记录事件。以 App 产品为例，可以这样追踪一次启动行为：

```c
sensors_analytics::PropertiesNode event_properties;
event_properties.SetString("computer_name", "MyComputer");
sensors_analytics::Sdk::Track("OpenApp", event_properties);
```

### 4.1 事件属性

如前文中的样例，开发者追踪的事件可以自定义事件的属性，例如购买商品事件中，将商品 ID、商品分类等信息作为事件属性。在后续的分析工作中，事件属性可以作为统计过滤条件使用，也可以作为维度进行多维分析。对于事件属性，神策分析有一些约束:

* 事件属性是一个 `sensors_analytics::PropertiesNode` 对象；
* 通过 `PropertiesNode` 的方法设置属性，属性名称必需是字符串类型，以大小写字母开头，由字母和数字组成，长度不超过100个字符；
* `PropertiesNode` 属性值支持 `String`、`Number`、`Bool`、`List` 和 `DateTime`，对于神策分析中事件属性的更多约束，请参考 [数据格式](https://54td.gitbook.io/shence/technical_guide/data_import/data_schema)。

开发者可以通过以下接口，向 `PropertiesNode` 对象加入属性值，如加入 Bool 类型的属性:

```c
sensors_analytics::PropertiesNode event_properties;
event_properties.SetBool("use_ticket", true);
```

其中属性名称必须为大小写字母开头、长度不超过 100 的由字母和数字组成的字符串。加入 Number 类型的属性：

```c
event_properties.SetNumber("price", 100.0);
```

加入 DateTime 类型的属性，其中第一个参数为 time\_t 类型，如系统函数 `time(NULL)` 的返回值，第二个参数为毫秒部分。或者直接用一个字符串作为 DateTime 类型属性的值：

```c
event_properties.SetDateTime("view_from_time", time(NULL), 0);
event_properties.SetDateTime("view_to_time", "2018-09-12 18:02:15.234");
```

加入 String 类型的属性，字符串必须为 UTF-8 编码，字符串长度为实际的 UTF-8 长度，如一个汉字占 3 个字节：

```c
event_properties.SetString("view_page_title", "title1");

std::string page_name = "page-1";
event_properties.SetString("view_page_name", page_name);
```

向 List 类型的属性中添加 String 对象：

```c
std::vector<string> item_list;
item_list.push_back("Book1");
item_list.push_back("Movie2");
event_properties.SetList("view_items", item_list);
```

具体使用方式，可以参考上一节中的样例。

## 5. 追踪登录与 ID 关联

若 `Init` 时使用了随机生成的“设备 ID”，当获取到“登录 ID”后，可调用 SDK 的 `Login` 方法将“设备 ID”与“登录 ID”关联：

```c
sensors_analytics::Sdk::Login("123456");
```

更详细的说明请参考 [如何准确的标识用户](https://54td.gitbook.io/shence/technical_guide/data_import/user_identify)，并在必要时联系我们的技术支持人员。

## 6. 设置用户属性

为了更准确地提供针对人群的分析服务，神策分析 SDK 可以设置用户属性，如年龄、性别等。用户可以在留存分析、分布分析等功能中，使用用户属性作为过滤条件或以用户属性作为维度进行多维分析。使用

```c
sensors_analytics::Sdk::ProfileSet(const PropertiesNode& properties);
```

设置用户属性，例如在电商应用中，用户注册时，填充了一些个人信息，可以用 Profile 接口记录下来:

```c
sensors_analytics::PropertiesNode profile_properties;
profile_properties.SetString("vip_level", "3");
sensors_analytics::Sdk::ProfileSet(profile_properties);

// 设置单个用户属性也可以通过:
sensors_analytics::Sdk::ProfileSetNumber("Age", 26);
```

用户属性中，属性名称与属性值的约束条件与事件属性相同，详细说明请参考 [数据格式](https://54td.gitbook.io/shence/technical_guide/data_import/data_schema)。

### 6.1 记录初次设定的属性

对于只在首次设置时有效的属性，我们可以使用

```c
sensors_analytics::Sdk::ProfileSetOnce(const PropertiesNode& properties);
```

接口记录这些属性。与 `ProfileSet` 接口不同的是，如果被设置的用户属性已存在，则这条记录会被忽略而不会覆盖已有数据，如果属性不存在则会自动创建。因此，`ProfileSetOnce` 比较适用于为用户设置首次相关属性。例如：

```c
sensors_analytics::Sdk::ProfileSetOnceString("first_visit_page", "Page567");
```

## 7. 其他

### 7.1 使用 HTTPS 数据接收地址

使用 HTTPS 数据接收地址需要 curl 配置了 OpenSSL（建议）或 WinSSL（默认），我们提供的编译好的 curl 使用了 OpenSSL。

另外在某些操作系统里，curl 无法正确获取 ca-bundle，有两种方法处理：

1. 不添加 ca-bundle，不校验服务端 SSL 证书（会降低传输安全性，建议仅测试时使用）。具体做法是在 `sensors_analytics_sdk.cpp` 文件中搜索 `CURLOPT_SSL_VERIFYPEER`，取消相关两行代码的注释；
2. 附带 ca-bundle（[下载地址](http://curl.haxx.se/ca/cacert.pem)）。具体做法是在 `sensors_analytics_sdk.cpp` 文件中搜索 `CURLOPT_CAINFO`，取消该行注释，并指定证书文件的路径。
