├── SensorsAnalyticsSDK ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── sensorsdata │ │ │ └── analytics │ │ │ └── javasdk │ │ │ ├── consumer │ │ │ ├── LogSplitMode.java │ │ │ ├── Consumer.java │ │ │ ├── Callback.java │ │ │ ├── ConsoleConsumer.java │ │ │ ├── InstantHttpConsumer.java │ │ │ ├── DebugConsumer.java │ │ │ ├── InnerLoggingConsumer.java │ │ │ ├── ConcurrentLoggingConsumer.java │ │ │ ├── HttpConsumer.java │ │ │ ├── BatchConsumer.java │ │ │ └── FastBatchConsumer.java │ │ │ ├── common │ │ │ ├── SchemaTypeEnum.java │ │ │ ├── SensorsDataTypeEnum.java │ │ │ └── Pair.java │ │ │ ├── exceptions │ │ │ ├── InvalidArgumentException.java │ │ │ └── DebugModeException.java │ │ │ ├── bean │ │ │ ├── FailedData.java │ │ │ ├── SensorsAnalyticsIdentity.java │ │ │ ├── schema │ │ │ │ ├── IdentitySchema.java │ │ │ │ ├── ItemSchema.java │ │ │ │ ├── UserSchema.java │ │ │ │ ├── ItemEventSchema.java │ │ │ │ ├── UserEventSchema.java │ │ │ │ └── DetailSchema.java │ │ │ ├── SuperPropertiesRecord.java │ │ │ ├── ItemRecord.java │ │ │ ├── IDMUserRecord.java │ │ │ ├── UserRecord.java │ │ │ ├── IDMEventRecord.java │ │ │ └── EventRecord.java │ │ │ ├── SensorsConst.java │ │ │ ├── SensorsAnalyticsWorker.java │ │ │ ├── util │ │ │ └── Base64Coder.java │ │ │ ├── SensorsSchemaData.java │ │ │ ├── SensorsData.java │ │ │ └── HelloSensorsAnalytics.java │ └── test │ │ └── java │ │ └── com.sensorsdata.analytics.javasdk │ │ ├── SchemaItemTest.java │ │ ├── InstantServlet.java │ │ ├── DebugConsumerTest.java │ │ ├── SchemaItemEventTest.java │ │ ├── SensorsLogsUtil.java │ │ ├── TestServlet.java │ │ ├── IDMBindTest.java │ │ ├── NormalModelTest.java │ │ ├── SensorsAnalyticsUtilTest.java │ │ ├── SchemaUserTest.java │ │ ├── SchemaDetailTest.java │ │ ├── ConcurrentLoggingConsumerTest.java │ │ ├── SchemaUserEventTest.java │ │ ├── InstantEventTest.java │ │ ├── IDMappingProfileTest.java │ │ └── IDMappingModel1TrackIdDebugConsumer.java └── pom.xml ├── LICENSE ├── .gitignore └── README.md /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/LogSplitMode.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | public enum LogSplitMode { 4 | DAY, HOUR 5 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import java.util.Map; 4 | 5 | public interface Consumer { 6 | void send(Map message); 7 | 8 | void flush(); 9 | 10 | void close(); 11 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/common/SchemaTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.common; 2 | 3 | /** 4 | * schema 类型枚举 5 | * 6 | * @author fangzhuo 7 | * @version 1.0.0 8 | * @since 2022/06/14 14:55 9 | */ 10 | public enum SchemaTypeEnum { 11 | USER_EVENT, 12 | ITEM_EVENT, 13 | USER, 14 | USER_ITEM, 15 | ITEM, 16 | DETAIL 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/exceptions/InvalidArgumentException.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.exceptions; 2 | 3 | /** 4 | * 非法的DistinctID 5 | */ 6 | public class InvalidArgumentException extends Exception { 7 | 8 | public InvalidArgumentException(String message) { 9 | super(message); 10 | } 11 | 12 | public InvalidArgumentException(Throwable error) { 13 | super(error); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/Callback.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.FailedData; 4 | 5 | /** 6 | * 异常数据回调 7 | * 8 | * @author fangzhuo 9 | * @version 1.0.0 10 | * @since 2021/11/05 23:47 11 | */ 12 | public interface Callback { 13 | /** 14 | * 返回发送失败的数据 15 | * 16 | * @param failedData 失败数据 17 | */ 18 | void onFailed(FailedData failedData); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/exceptions/DebugModeException.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.exceptions; 2 | 3 | /** 4 | * Debug模式下的错误 5 | */ 6 | public class DebugModeException extends RuntimeException { 7 | 8 | public DebugModeException(String message) { 9 | super(message); 10 | } 11 | 12 | public DebugModeException(Throwable error) { 13 | super(error); 14 | } 15 | 16 | public DebugModeException(String message, Throwable error) { 17 | super(message, error); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/FailedData.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * 发送异常数据包装类 15 | * 16 | * @author fangzhuo 17 | * @version 1.0.0 18 | * @since 2021/11/06 10:35 19 | */ 20 | @Setter 21 | @Getter 22 | @AllArgsConstructor 23 | @ToString 24 | public class FailedData { 25 | /** 26 | * 失败原因 27 | */ 28 | private String failedMessage; 29 | /** 30 | * 失败数据 31 | */ 32 | private List> failedData; 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 软件名称:神策分析 SDK 2 | 版本号:所有版本 3 | 许可协议版本:1.0 4 | 5 | 1. 商业许可协议(用于商业用途需购买许可) 6 | 任何商业用途必须获得商业许可。 7 | 8 | 商业许可协议条款: 9 | 10 | - 商业用途:任何直接或间接产生收入的用途都需要购买商业许可。 11 | - 付款条款:在使用本软件用于商业用途之前,您必须支付全额许可费用。具体的付款方式将在双方联系后提供。 12 | - 商业支持:购买商业许可后,您将获得一年的技术支持和软件更新服务。 13 | - 禁止再许可:商业用户不得再许可、转售或转让本软件。每份商业许可仅适用于单一实体或公司。 14 | - 源代码访问:购买商业许可的用户将获得本软件的代码访问权限,并可根据业务需求进行内部修改。但不得公开发布或再分发修改后的版本。 15 | - 使用范围限制:商业许可仅限于购买者的内部使用,不得与第三方共享或用于为第三方提供服务。任何超出许可范围的使用行为均需额外授权,并可能产生额外费用。 16 | - 联系信息:如需购买商业许可,请联系 dv@sensorsdata.com。 17 | - 知识产权声明:本软件的版权归神策网络科技(北京)有限公司所有。购买商业许可仅授予您使用权,所有权仍归属本公司。 18 | - 终止条款: 如果您未支付相关费用或违反本协议的任何条款,商业许可将自动终止。您必须立即停止所有商业用途,并销毁或删除所有软件副本。 19 | 20 | 2. 附加授权规则条款 21 | 授权规则条款: 22 | 23 | - 功能限制:未经本软件作者的明确书面许可,您不得移除、绕过或规避本软件中的任何功能限制或试用限制。 24 | - 商标使用:未经授权,您不得在宣传、市场推广或销售产品时使用本软件的名称、商标或品牌标识。任何商标使用必须得到明确的书面许可。 25 | - 修改条款:本协议的条款可能会不时更新,用户有责任定期检查最新版本。任何重大更改将通过项目主页或电子邮件通知用户。 26 | 27 | 3. 联系方式 28 | 如需更多信息或申请商业许可,请联系 dv@sensorsdata.com。 29 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SchemaItemTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.schema.ItemSchema; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | 6 | import org.junit.Test; 7 | 8 | /** 9 | * TODO 10 | * 11 | * @author fangzhuo 12 | * @version 1.0.0 13 | * @since 2022/06/16 15:28 14 | */ 15 | public class SchemaItemTest extends SensorsBaseTest { 16 | 17 | private static final String ITEM_ID = "test11"; 18 | 19 | private static final String SCHEMA = "item_schema"; 20 | 21 | /** 22 | * 生成 itemSchema 格式数据 23 | */ 24 | @Test 25 | public void checkItemSet() throws InvalidArgumentException { 26 | ItemSchema itemSchema = ItemSchema.init() 27 | .setItemId(ITEM_ID) 28 | .setSchema(SCHEMA) 29 | .addProperty("key1", "value1") 30 | .addProperty("key2", 22) 31 | .start(); 32 | sa.itemSet(itemSchema); 33 | assertISData(data); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/common/SensorsDataTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.common; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | /** 7 | * 神策数据类型枚举 8 | * 9 | * @author fangzhuo 10 | * @version 1.0.0 11 | * @since 2022/06/15 17:34 12 | */ 13 | public enum SensorsDataTypeEnum { 14 | NUMBER, 15 | STRING, 16 | BOOLEAN, 17 | DATE, 18 | LIST, 19 | UNKNOWN; 20 | 21 | 22 | public static SensorsDataTypeEnum getDataType(Object value) { 23 | if (value instanceof Number) { 24 | return SensorsDataTypeEnum.NUMBER; 25 | } else if (value instanceof String) { 26 | return SensorsDataTypeEnum.STRING; 27 | } else if (value instanceof Boolean) { 28 | return SensorsDataTypeEnum.BOOLEAN; 29 | } else if (value instanceof Date) { 30 | return SensorsDataTypeEnum.DATE; 31 | } else if (value instanceof List) { 32 | return SensorsDataTypeEnum.LIST; 33 | } else { 34 | return SensorsDataTypeEnum.UNKNOWN; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/InstantServlet.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 8 | 9 | import com.fasterxml.jackson.databind.JsonNode; 10 | import com.fasterxml.jackson.databind.node.ArrayNode; 11 | import org.junit.Assert; 12 | import sun.misc.BASE64Decoder; 13 | 14 | import java.io.ByteArrayInputStream; 15 | import java.io.ByteArrayOutputStream; 16 | import java.io.IOException; 17 | import java.util.zip.GZIPInputStream; 18 | 19 | import javax.servlet.http.HttpServlet; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | 23 | public class InstantServlet extends TestServlet { 24 | @Override 25 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { 26 | super.doPost(request, response); 27 | System.out.println(request.getParameter("instant_event")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | git.properties 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 6 | 7 | *.iml 8 | 9 | ## Directory-based project format: 10 | .idea/ 11 | 12 | ## File-based project format: 13 | *.ipr 14 | *.iws 15 | 16 | ## Plugin-specific files: 17 | 18 | # IntelliJ 19 | /out/ 20 | 21 | # mpeltonen/sbt-idea plugin 22 | .idea_modules/ 23 | 24 | # JIRA plugin 25 | atlassian-ide-plugin.xml 26 | 27 | # Python 28 | __pycache__ 29 | *.pyc 30 | 31 | # Crashlytics plugin (for Android Studio and IntelliJ) 32 | com_crashlytics_export_strings.xml 33 | crashlytics.properties 34 | crashlytics-build.properties 35 | 36 | # java build files 37 | target 38 | ui/node_modules 39 | gulpfile.js 40 | 41 | # debug scripts. 42 | deploy.sh 43 | ftpsync.settings 44 | 45 | *.swp 46 | .DS_Store 47 | 48 | # Xcode 49 | build/* 50 | *.pbxuser 51 | !default.pbxuser 52 | *.mode1v3 53 | !default.mode1v3 54 | *.mode2v3 55 | !default.mode2v3 56 | *.perspectivev3 57 | !default.perspectivev3 58 | *.xcworkspace 59 | !default.xcworkspace 60 | xcuserdata 61 | profile 62 | *.moved-aside 63 | *.cer 64 | *.p12 65 | *.mobileprovision 66 | 67 | # AppCode 68 | .idea 69 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/DebugConsumerTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 4 | import com.sensorsdata.analytics.javasdk.consumer.DebugConsumer; 5 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 6 | 7 | import org.junit.Test; 8 | 9 | import java.util.Calendar; 10 | 11 | /** 12 | * debugConsumer 单元测试 13 | * 14 | * @author fangzhuo 15 | * @version 1.0.0 16 | * @since 2022/01/18 18:55 17 | */ 18 | public class DebugConsumerTest extends SensorsBaseTest { 19 | 20 | @Test 21 | public void checkDataSend() throws InvalidArgumentException { 22 | DebugConsumer consumer = new DebugConsumer("http://localhost:8888/sa", true); 23 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 24 | EventRecord firstRecord = EventRecord.builder().setDistinctId("a123").isLoginId(Boolean.FALSE) 25 | .setEventName("track") 26 | .addProperty("$time", Calendar.getInstance().getTime()) 27 | .addProperty("Channel", "baidu") 28 | .addProperty("$project", "abc") 29 | .addProperty("$token", "123") 30 | .build(); 31 | sa.track(firstRecord); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/common/Pair.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.common; 2 | 3 | /** 4 | * 生成一个 KV 对象 5 | * 6 | * @author fangzhuo 7 | * @version 1.0.0 8 | * @since 2022/03/12 16:37 9 | */ 10 | public class Pair { 11 | 12 | private final K key; 13 | 14 | private final V value; 15 | 16 | public K getKey() { 17 | return key; 18 | } 19 | 20 | public V getValue() { 21 | return value; 22 | } 23 | 24 | private Pair(K key, V value) { 25 | this.key = key; 26 | this.value = value; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return key + "=" + value; 32 | } 33 | 34 | 35 | public boolean equals(Object var1) { 36 | return var1 instanceof Pair 37 | && ((Pair) var1).getKey() == this.key 38 | && this.value == ((Pair) var1).getValue(); 39 | } 40 | 41 | public int hashCode() { 42 | if (this.key == null) { 43 | return this.value == null ? 0 : this.value.hashCode() + 1; 44 | } else { 45 | return this.value == null ? this.key.hashCode() + 2 : this.key.hashCode() * 17 + this.value.hashCode(); 46 | } 47 | } 48 | 49 | public static Pair of(K key, V value) { 50 | return new Pair<>(key, value); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # 神策简介 5 | 6 | 神策数据 (Sensors Data),隶属于神策网络科技(北京)有限公司,是一家专业的大数据分析服务公司,大数据分析行业开拓者,为客户提供深度用户行为分析平台、以及专业的咨询服务和行业解决方案,致力于帮助客户实现数据驱动。神策数据立足大数据及用户行为分析的技术与实践前沿,业务现已覆盖以互联网、金融、零售快消、高科技、制造等为代表的十多个主要行业、并可支持企业多个职能部门。公司总部在北京,并在上海、深圳、合肥、武汉等地拥有本地化的服务团队,覆盖东区及南区市场;公司拥有专业的服务团队,为客户提供一对一的客户服务。公司在大数据领域积累的核心关键技术,包括在海量数据采集、存储、清洗、分析挖掘、可视化、智能应用、安全与隐私保护等领域。 [More](https://www.sensorsdata.cn/about/aboutus.html) 7 | 8 | # SDK 简介 9 | 10 | 神策数据官方 Java 埋点 SDK,是一款轻量级用于 Java 端的数据采集埋点 SDK 11 | 12 | ## 神策埋点 SDK 官网 13 | 如需了解神策埋点 SDK 的更多商业授权信息,请访问[神策埋点 SDK 官网](https://jssdk.debugbox.sensorsdata.cn/)获取更多详细信息。 14 | 15 | ## 联系我们 16 | 若您有商业合作或产品集成需求,请通过下面的渠道联系我们获取专业服务与支持。 17 | 18 | | 加微信号:skycode008,或扫码添加联系人 | 扫码关注「神策埋点 SDK」公众号 ![gzh](https://github.com/sensorsdata/sa-sdk-android/blob/master/gzh.jpeg) | 19 | | ------ | ------ | 20 | 21 | # 使用说明 22 | 使用方法请参考文档 https://manual.sensorsdata.cn/sa/latest/java-sdk-1573929.html 23 | 24 | ## 新书推荐 25 | 26 | | [《数据驱动:从方法到实践》](https://item.jd.com/12322322.html) | [《Android 全埋点解决方案》](https://item.jd.com/12574672.html) | [《iOS 全埋点解决方案》](https://item.jd.com/12867068.html) 27 | | ------ | ------ | ------ | 28 | 29 | ## License 30 | [License 协议](https://github.com/sensorsdata/sa-sdk-java/blob/master/LICENSE) 31 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SchemaItemEventTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | import static org.junit.Assert.fail; 5 | 6 | import com.sensorsdata.analytics.javasdk.bean.schema.ItemEventSchema; 7 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 8 | 9 | import org.junit.Test; 10 | 11 | /** 12 | * TODO 13 | * 14 | * @author fangzhuo 15 | * @version 1.0.0 16 | * @since 2022/06/16 17:44 17 | */ 18 | public class SchemaItemEventTest extends SensorsBaseTest { 19 | 20 | private static final String SCHEMA = "product_event"; 21 | 22 | private static final String EVENT_NAME = "cart"; 23 | 24 | @Test 25 | public void checkItemEvent() throws InvalidArgumentException { 26 | ItemEventSchema itemEventSchema = ItemEventSchema.init() 27 | .setSchema(SCHEMA) 28 | .setEventName(EVENT_NAME) 29 | .setItemPair("production", "car") 30 | .addProperty("key1", "value1") 31 | .start(); 32 | sa.track(itemEventSchema); 33 | assertIESData(data); 34 | } 35 | 36 | @Test 37 | public void checkInvalidEventItemKey() { 38 | try { 39 | ItemEventSchema itemEventSchema = ItemEventSchema.init() 40 | .setEventName("aa") 41 | .setSchema("sss") 42 | .setItemPair("production", "car") 43 | .addProperty("11age", "value") 44 | .start(); 45 | sa.track(itemEventSchema); 46 | fail("生成异常数据"); 47 | } catch (InvalidArgumentException e) { 48 | assertTrue(e.getMessage().contains("is invalid.")); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/ConsoleConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 4 | 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.io.IOException; 9 | import java.io.Writer; 10 | import java.util.Map; 11 | 12 | @Slf4j 13 | public class ConsoleConsumer implements Consumer { 14 | private final ObjectMapper jsonMapper; 15 | private final Writer writer; 16 | 17 | public ConsoleConsumer(final Writer writer) { 18 | this.jsonMapper = SensorsAnalyticsUtil.getJsonObjectMapper(); 19 | this.writer = writer; 20 | log.info("Initialize ConsoleConsumer."); 21 | } 22 | 23 | @Override 24 | public void send(Map message) { 25 | try { 26 | synchronized (writer) { 27 | writer.write(jsonMapper.writeValueAsString(message)); 28 | writer.write("\n"); 29 | } 30 | } catch (IOException e) { 31 | log.error("Failed to dump message with ConsoleConsumer.", e); 32 | throw new RuntimeException("Failed to dump message with ConsoleConsumer.", e); 33 | } 34 | } 35 | 36 | @Override 37 | public void flush() { 38 | synchronized (writer) { 39 | try { 40 | writer.flush(); 41 | } catch (IOException e) { 42 | log.error("Failed to flush with ConsoleConsumer.", e); 43 | throw new RuntimeException("Failed to flush with ConsoleConsumer.", e); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public void close() { 50 | flush(); 51 | } 52 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/SensorsAnalyticsIdentity.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | @Getter 13 | @Setter(AccessLevel.PROTECTED) 14 | @AllArgsConstructor(access = AccessLevel.PROTECTED) 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | public class SensorsAnalyticsIdentity { 17 | // 用户登录 ID 18 | public static final String LOGIN_ID = "$identity_login_id"; 19 | // 手机号 20 | public static final String MOBILE = "$identity_mobile"; 21 | // 邮箱 22 | public static final String EMAIL = "$identity_email"; 23 | /** 24 | * 用户纬度标识集合 25 | */ 26 | protected Map identityMap; 27 | 28 | public static Builder builder() { 29 | return new Builder(); 30 | } 31 | 32 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 33 | public static class Builder { 34 | private final Map idMap = new LinkedHashMap<>(); 35 | 36 | public SensorsAnalyticsIdentity.Builder identityMap(Map identityMap) { 37 | if (identityMap != null) { 38 | this.idMap.putAll(identityMap); 39 | } 40 | return this; 41 | } 42 | 43 | public SensorsAnalyticsIdentity.Builder addIdentityProperty(String key, String value) { 44 | this.idMap.put(key, value); 45 | return this; 46 | } 47 | 48 | public SensorsAnalyticsIdentity build() { 49 | return new SensorsAnalyticsIdentity(idMap); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/InstantHttpConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import org.apache.http.NameValuePair; 4 | import org.apache.http.client.entity.UrlEncodedFormEntity; 5 | import org.apache.http.impl.client.HttpClientBuilder; 6 | import org.apache.http.message.BasicNameValuePair; 7 | 8 | import java.io.IOException; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class InstantHttpConsumer extends HttpConsumer{ 13 | 14 | public InstantHttpConsumer(String serverUrl, int timeoutSec) { 15 | super(serverUrl, timeoutSec); 16 | } 17 | 18 | public InstantHttpConsumer(String serverUrl, Map httpHeaders) { 19 | super(serverUrl, httpHeaders); 20 | } 21 | 22 | InstantHttpConsumer(String serverUrl, Map httpHeaders, int timeoutSec) { 23 | super(serverUrl, httpHeaders, timeoutSec); 24 | } 25 | 26 | public InstantHttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, 27 | Map httpHeaders) { 28 | super(httpClientBuilder, serverUrl, httpHeaders); 29 | } 30 | 31 | public InstantHttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, int timeoutSec) { 32 | super(httpClientBuilder, serverUrl, timeoutSec); 33 | } 34 | 35 | InstantHttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, Map httpHeaders, 36 | int timeoutSec) { 37 | super(httpClientBuilder, serverUrl, httpHeaders, timeoutSec); 38 | } 39 | 40 | @Override 41 | UrlEncodedFormEntity getHttpEntry(final String data) throws IOException { 42 | List nameValuePairs = getNameValuePairs(data); 43 | nameValuePairs.add(new BasicNameValuePair("instant_event", "true")); 44 | return new UrlEncodedFormEntity(nameValuePairs); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/IdentitySchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import lombok.Getter; 4 | import lombok.NonNull; 5 | 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * identitySchema 14 | * 15 | * @author fangzhuo 16 | * @version 1.0.0 17 | * @since 2022/07/25 17:30 18 | */ 19 | @Getter 20 | public class IdentitySchema { 21 | 22 | private Map idMap; 23 | 24 | private Map properties; 25 | 26 | protected IdentitySchema(Map idMap, Map properties) { 27 | this.idMap = idMap; 28 | this.properties = properties; 29 | } 30 | 31 | public static IdentitySchema.Builder init() { 32 | return new IdentitySchema.Builder(); 33 | } 34 | 35 | 36 | public static class Builder { 37 | 38 | private Map idMap = new LinkedHashMap<>(); 39 | 40 | private Map properties = new HashMap<>(); 41 | 42 | public Builder identityMap(Map identityMap) { 43 | if (identityMap != null) { 44 | this.idMap.putAll(identityMap); 45 | } 46 | return this; 47 | } 48 | 49 | public Builder addIdentityProperty(String key, String value) { 50 | this.idMap.put(key, value); 51 | return this; 52 | } 53 | 54 | 55 | public Builder addProperties(@NonNull Map properties) { 56 | this.properties.putAll(properties); 57 | return this; 58 | } 59 | 60 | public Builder addProperty(@NonNull String key, @NonNull String property) { 61 | addPropertyObject(key, property); 62 | return this; 63 | } 64 | 65 | public Builder addProperty(@NonNull String key, boolean property) { 66 | addPropertyObject(key, property); 67 | return this; 68 | } 69 | 70 | public Builder addProperty(@NonNull String key, @NonNull Number property) { 71 | addPropertyObject(key, property); 72 | return this; 73 | } 74 | 75 | public Builder addProperty(@NonNull String key, @NonNull Date property) { 76 | addPropertyObject(key, property); 77 | return this; 78 | } 79 | 80 | public Builder addProperty(@NonNull String key, @NonNull List property) { 81 | addPropertyObject(key, property); 82 | return this; 83 | } 84 | 85 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 86 | this.properties.put(key, property); 87 | } 88 | 89 | public IdentitySchema build() { 90 | return new IdentitySchema(idMap, properties); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SensorsLogsUtil.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.FailedData; 4 | import com.sensorsdata.analytics.javasdk.consumer.FastBatchConsumer; 5 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 6 | 7 | import com.fasterxml.jackson.core.JsonProcessingException; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ScheduledExecutorService; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class SensorsLogsUtil { 16 | 17 | //定义一个定时线程,用于定时检查容器中是否存在失败数据 18 | private static final ScheduledExecutorService resendService = Executors.newSingleThreadScheduledExecutor(); 19 | 20 | //失败数据容器,(此处用于模式测试,生产中可能会有大量数据,因此不建议使用内存容器,建议持久化) 21 | private static final List failedDataList = new ArrayList<>(); 22 | 23 | //往容器中保存数据 24 | public static void setFailedData(FailedData failedData) { 25 | failedDataList.add(failedData); 26 | for (FailedData fd : failedDataList) { 27 | System.out.println("failed data." + fd); 28 | System.out.println("failed data." + fd.getFailedData()); 29 | } 30 | } 31 | 32 | //初始化重发送操作 33 | public static void resend(FastBatchConsumer consumer) { 34 | resendService.scheduleWithFixedDelay(new ResendTask(consumer), 0, 1, TimeUnit.SECONDS); 35 | } 36 | 37 | static class ResendTask implements Runnable { 38 | 39 | private final FastBatchConsumer consumer; 40 | 41 | public ResendTask(FastBatchConsumer consumer) { 42 | this.consumer = consumer; 43 | } 44 | 45 | @Override 46 | public void run() { 47 | System.out.println("start schedule task."); 48 | if (!failedDataList.isEmpty()) { 49 | for (FailedData failedData : failedDataList) { 50 | System.out.println("resend failed data." + failedData); 51 | System.out.println("resend failed data." + failedData.getFailedData()); 52 | boolean isSend = false; 53 | try { 54 | isSend = consumer.resendFailedData(failedData); 55 | } catch (InvalidArgumentException e) { 56 | e.printStackTrace(); 57 | } catch (JsonProcessingException e) { 58 | e.printStackTrace(); 59 | } 60 | System.out.println("resend success:" + isSend); 61 | if(isSend){ 62 | failedDataList.remove(failedData); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/TestServlet.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 8 | 9 | import com.fasterxml.jackson.databind.JsonNode; 10 | import com.fasterxml.jackson.databind.node.ArrayNode; 11 | import sun.misc.BASE64Decoder; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.IOException; 16 | import java.util.zip.GZIPInputStream; 17 | 18 | import javax.servlet.http.HttpServlet; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | /** 23 | * 模拟服务端接收数据 24 | * 25 | * @author fangzhuo 26 | * @version 1.0.0 27 | * @since 2022/01/18 18:33 28 | */ 29 | public class TestServlet extends HttpServlet { 30 | 31 | @Override 32 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { 33 | String gzip = request.getParameter("gzip"); 34 | assertEquals("1", gzip); 35 | String dataList = request.getParameter("data_list"); 36 | BASE64Decoder base64Decoder = new BASE64Decoder(); 37 | byte[] bytes = base64Decoder.decodeBuffer(dataList); 38 | byte[] data = decompressGzip(bytes); 39 | ArrayNode arrayNode = (ArrayNode) SensorsAnalyticsUtil.getJsonObjectMapper().readTree(data); 40 | for (JsonNode jsonNode : arrayNode) { 41 | assertNotNull("数据为空!", jsonNode); 42 | assertTrue("数据中没有 type 节点!", jsonNode.has("type")); 43 | assertTrue("数据中没有 actionType 节点!", jsonNode.has("event")); 44 | if (jsonNode.get("event").asText().startsWith("item")) { 45 | assertTrue("item 数据没有 item_id 节点!", jsonNode.has("item_id")); 46 | assertTrue("item 数据没有 item_type 节点!", jsonNode.has("item_type")); 47 | } else { 48 | assertTrue("event or profile 数据没有 _track_id 节点!", jsonNode.has("_track_id")); 49 | assertTrue("event or profile 数据没有 lib 节点!", jsonNode.has("lib")); 50 | assertTrue("event or profile 数据没有 time 节点!", jsonNode.has("time")); 51 | assertTrue("event or profile 数据没有 distinct_id 节点!", jsonNode.has("distinct_id")); 52 | } 53 | } 54 | response.setStatus(200); 55 | } 56 | 57 | protected byte[] decompressGzip(byte[] gzipData) throws IOException { 58 | byte[] bytes1 = new byte[1024]; 59 | GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(gzipData)); 60 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 61 | int n; 62 | while ((n = gis.read(bytes1)) != -1) { 63 | bos.write(bytes1, 0, n); 64 | } 65 | bos.close(); 66 | return bos.toByteArray(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/SuperPropertiesRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * 公共属性信息实体 13 | * 14 | * @author fz 15 | * @version 1.0.0 16 | * @since 2021/05/26 11:09 17 | */ 18 | public class SuperPropertiesRecord implements Serializable { 19 | 20 | private static final long serialVersionUID = 6526668600748384833L; 21 | 22 | private final Map propertyMap; 23 | 24 | private SuperPropertiesRecord(Map propertyMap) { 25 | this.propertyMap = propertyMap; 26 | } 27 | 28 | public Map getPropertyMap() { 29 | return propertyMap; 30 | } 31 | 32 | public static Builder builder() { 33 | return new Builder(); 34 | } 35 | 36 | public static class Builder { 37 | private Map propertyMap = new HashMap(); 38 | 39 | private Builder() { 40 | } 41 | 42 | public SuperPropertiesRecord build() throws InvalidArgumentException { 43 | if (propertyMap.size() == 0) { 44 | throw new InvalidArgumentException("The propertyMap is empty."); 45 | } 46 | return new SuperPropertiesRecord(propertyMap); 47 | } 48 | 49 | 50 | public SuperPropertiesRecord.Builder addProperties(Map properties) { 51 | if (properties != null) { 52 | propertyMap.putAll(properties); 53 | } 54 | return this; 55 | } 56 | 57 | public SuperPropertiesRecord.Builder addProperty(String key, String property) { 58 | addPropertyObject(key, property); 59 | return this; 60 | } 61 | 62 | public SuperPropertiesRecord.Builder addProperty(String key, boolean property) { 63 | addPropertyObject(key, property); 64 | return this; 65 | } 66 | 67 | public SuperPropertiesRecord.Builder addProperty(String key, Number property) { 68 | addPropertyObject(key, property); 69 | return this; 70 | } 71 | 72 | public SuperPropertiesRecord.Builder addProperty(String key, Date property) { 73 | addPropertyObject(key, property); 74 | return this; 75 | } 76 | 77 | public SuperPropertiesRecord.Builder addProperty(String key, List property) { 78 | addPropertyObject(key, property); 79 | return this; 80 | } 81 | 82 | private void addPropertyObject(String key, Object property) { 83 | if (key != null) { 84 | propertyMap.put(key, property); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/SensorsConst.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | /** 4 | * java SDK 全局变量 5 | * 6 | * @author fz 7 | * @version 1.0.0 8 | * @since 2021/05/25 10:29 9 | */ 10 | public class SensorsConst { 11 | 12 | private SensorsConst() { 13 | } 14 | 15 | /** 16 | * 当前JDK版本号,注意要和pom文件里面的version保持一致 17 | */ 18 | public static final String SDK_VERSION = "3.6.8"; 19 | /** 20 | * 当前语言类型 21 | */ 22 | public static final String LIB = "Java"; 23 | /** 24 | * 当前数据协议版本 25 | */ 26 | public static final String PROTOCOL_VERSION = "2.0"; 27 | /** 28 | * user events 所属 schema 29 | */ 30 | public static final String USER_EVENT_SCHEMA = "events"; 31 | /** 32 | * user 所属 schema 33 | */ 34 | public static final String USER_SCHEMA = "users"; 35 | 36 | public static final String USER_ID_PREFIX = "SENSORS_ID:"; 37 | 38 | /** 39 | * 事件上报类型 40 | */ 41 | public static final String TRACK_ACTION_TYPE = "track"; 42 | public static final String TRACK_SIGN_UP_ACTION_TYPE = "track_signup"; 43 | public static final String PROFILE_SET_ACTION_TYPE = "profile_set"; 44 | public static final String PROFILE_SET_ONCE_ACTION_TYPE = "profile_set_once"; 45 | public static final String PROFILE_APPEND_ACTION_TYPE = "profile_append"; 46 | public static final String PROFILE_INCREMENT_ACTION_TYPE = "profile_increment"; 47 | public static final String PROFILE_UNSET_ACTION_TYPE = "profile_unset"; 48 | public static final String PROFILE_DELETE_ACTION_TYPE = "profile_delete"; 49 | public static final String ITEM_SET_ACTION_TYPE = "item_set"; 50 | public static final String ITEM_DELETE_ACTION_TYPE = "item_delete"; 51 | public static final String DETAIL_SET_ACTION_TYPE = "detail_set"; 52 | public static final String DETAIL_DELETE_ACTION_TYPE = "detail_delete"; 53 | 54 | /** 55 | * ID-Mapping 56 | */ 57 | public static final String BIND_ID_ACTION_TYPE = "track_id_bind"; 58 | public static final String UNBIND_ID_ACTION_TYPE = "track_id_unbind"; 59 | 60 | public static final String ITEM_TYPE = "Item Type"; 61 | public static final String ITEM_ID = "Item Id"; 62 | /** 63 | * 绑定事件名称 64 | */ 65 | public static final String BIND_ID = "$BindID"; 66 | /** 67 | * 解绑事件名称 68 | */ 69 | public static final String UNBIND_ID = "$UnbindID"; 70 | 71 | public static final String PROPERTIES = "properties"; 72 | /** 73 | * 系统预置属性 74 | */ 75 | public static final String TRACK_ID = "$track_id"; 76 | public static final String PROJECT_SYSTEM_ATTR = "$project"; 77 | public static final String TIME_SYSTEM_ATTR = "$time"; 78 | public static final String TOKEN_SYSTEM_ATTR = "$token"; 79 | public static final String LOGIN_SYSTEM_ATTR = "$is_login_id"; 80 | public static final String APP_VERSION_SYSTEM_ATTR = "$app_version"; 81 | public static final String LIB_SYSTEM_ATTR = "$lib"; 82 | public static final String LIB_VERSION_SYSTEM_ATTR = "$lib_version"; 83 | public static final String LIB_METHOD_SYSTEM_ATTR = "$lib_method"; 84 | public static final String LIB_DETAIL_SYSTEM_ATTR = "$lib_detail"; 85 | public static final String SIGN_UP_SYSTEM_ATTR = "$SignUp"; 86 | public static final String TIME_FREE_ATTR = "$time_free"; 87 | 88 | public static final String DEFAULT_LIB_DETAIL="JavaSDK##generateLibInfo"; 89 | } 90 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/DebugConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.exceptions.DebugModeException; 4 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.http.client.utils.URIBuilder; 10 | 11 | import org.apache.http.impl.client.HttpClientBuilder; 12 | import org.apache.http.impl.client.HttpClients; 13 | 14 | import java.io.IOException; 15 | import java.net.MalformedURLException; 16 | import java.net.URI; 17 | import java.net.URISyntaxException; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | @Slf4j 24 | public class DebugConsumer implements Consumer { 25 | final HttpConsumer httpConsumer; 26 | final ObjectMapper jsonMapper; 27 | 28 | public DebugConsumer(final String serverUrl, final boolean writeData) { 29 | this(HttpClients.custom(), serverUrl, writeData); 30 | } 31 | 32 | public DebugConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, final boolean writeData) { 33 | String debugUrl; 34 | try { 35 | // 将 URI Path 替换成 Debug 模式的 '/debug' 36 | URIBuilder builder = new URIBuilder(new URI(serverUrl)); 37 | String[] urlPathes = builder.getPath().split("/"); 38 | urlPathes[urlPathes.length - 1] = "debug"; 39 | builder.setPath(SensorsAnalyticsUtil.strJoin(urlPathes, "/")); 40 | debugUrl = builder.build().toURL().toString(); 41 | } catch (URISyntaxException | MalformedURLException e) { 42 | log.error("Failed build debug url:[{}].", serverUrl, e); 43 | throw new DebugModeException(e); 44 | } 45 | 46 | Map headers = new HashMap<>(); 47 | if (!writeData) { 48 | headers.put("Dry-Run", "true"); 49 | } 50 | this.httpConsumer = new HttpConsumer(httpClientBuilder, debugUrl, headers); 51 | this.jsonMapper = SensorsAnalyticsUtil.getJsonObjectMapper(); 52 | log.info("Initialize DebugConsumer with params:[writeData:{}].", writeData); 53 | } 54 | 55 | @Override 56 | public void send(Map message) { 57 | List> messageList = new ArrayList<>(); 58 | messageList.add(message); 59 | String sendingData; 60 | try { 61 | sendingData = jsonMapper.writeValueAsString(messageList); 62 | } catch (JsonProcessingException e) { 63 | log.error("Failed to process json.", e); 64 | throw new RuntimeException("Failed to serialize data.", e); 65 | } 66 | try { 67 | synchronized (httpConsumer) { 68 | httpConsumer.consume(sendingData); 69 | } 70 | log.debug("Successfully send data:[{}].", sendingData); 71 | } catch (IOException e) { 72 | log.error("Failed to send message with DebugConsumer,message:[{}].", sendingData, e); 73 | throw new DebugModeException("Failed to send message with DebugConsumer.", e); 74 | } catch (HttpConsumer.HttpConsumerException e) { 75 | log.error("Failed send message with server occur error,message:[{}].", sendingData, e); 76 | throw new DebugModeException(e); 77 | } 78 | } 79 | 80 | @Override 81 | public void flush() { 82 | // do NOTHING 83 | } 84 | 85 | @Override 86 | public void close() { 87 | httpConsumer.close(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/ItemSchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 4 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 5 | 6 | import lombok.Getter; 7 | import lombok.NonNull; 8 | 9 | import java.util.Date; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * item schema 16 | * 17 | * @author fangzhuo 18 | * @version 1.0.0 19 | * @since 2022/06/13 16:53 20 | */ 21 | @Getter 22 | public class ItemSchema { 23 | 24 | private String schema; 25 | 26 | private String itemId; 27 | 28 | private Map properties; 29 | 30 | private Integer trackId; 31 | 32 | protected ItemSchema(Integer trackId, String schema, String itemId, Map properties) { 33 | this.trackId = trackId; 34 | this.schema = schema; 35 | this.itemId = itemId; 36 | this.properties = properties; 37 | } 38 | 39 | public static ISBuilder init() { 40 | return new ISBuilder(); 41 | } 42 | 43 | public static class ISBuilder { 44 | private String schema; 45 | 46 | private String itemId; 47 | 48 | private Map properties = new HashMap<>(); 49 | 50 | private Integer trackId; 51 | 52 | private ISBuilder() { 53 | } 54 | 55 | public ItemSchema start() throws InvalidArgumentException { 56 | if (itemId == null) { 57 | throw new InvalidArgumentException("The item id can not set null."); 58 | } 59 | SensorsAnalyticsUtil.assertValue("itemId", itemId); 60 | SensorsAnalyticsUtil.assertSchema(schema); 61 | SensorsAnalyticsUtil.assertSchemaProperties(properties, null); 62 | this.trackId = 63 | SensorsAnalyticsUtil.getTrackId(properties, String.format("[itemId=%s,schema=%s]", itemId, schema)); 64 | return new ItemSchema(trackId, schema, itemId, properties); 65 | } 66 | 67 | public ISBuilder setSchema(@NonNull String schema) { 68 | this.schema = schema; 69 | return this; 70 | } 71 | 72 | public ISBuilder setItemId(@NonNull String itemId) { 73 | this.itemId = itemId; 74 | return this; 75 | } 76 | 77 | public ISBuilder addProperties(@NonNull Map properties) { 78 | this.properties.putAll(properties); 79 | return this; 80 | } 81 | 82 | public ISBuilder addProperty(@NonNull String key, @NonNull String property) { 83 | addPropertyObject(key, property); 84 | return this; 85 | } 86 | 87 | public ISBuilder addProperty(@NonNull String key, boolean property) { 88 | addPropertyObject(key, property); 89 | return this; 90 | } 91 | 92 | public ISBuilder addProperty(@NonNull String key, @NonNull Number property) { 93 | addPropertyObject(key, property); 94 | return this; 95 | } 96 | 97 | public ISBuilder addProperty(@NonNull String key, @NonNull Date property) { 98 | addPropertyObject(key, property); 99 | return this; 100 | } 101 | 102 | public ISBuilder addProperty(@NonNull String key, @NonNull List property) { 103 | addPropertyObject(key, property); 104 | return this; 105 | } 106 | 107 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 108 | this.properties.put(key, property); 109 | } 110 | 111 | } 112 | 113 | public String getSchema() { 114 | return schema; 115 | } 116 | 117 | public String getItemId() { 118 | return itemId; 119 | } 120 | 121 | public Map getProperties() { 122 | return properties; 123 | } 124 | 125 | public Integer getTrackId() { 126 | return trackId; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/UserSchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 4 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 5 | 6 | import lombok.Getter; 7 | import lombok.NonNull; 8 | 9 | import java.util.Date; 10 | import java.util.HashMap; 11 | import java.util.LinkedHashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * 用户相关 schema 17 | * 18 | * @author fangzhuo 19 | * @version 1.0.0 20 | * @since 2022/06/13 15:49 21 | */ 22 | @Getter 23 | public class UserSchema { 24 | 25 | private Long userId; 26 | 27 | private Map propertyMap; 28 | 29 | private String distinctId; 30 | 31 | private Integer trackId; 32 | 33 | private Map identityMap; 34 | 35 | protected UserSchema(Map identityMap, Map propertyMap, String distinctId, 36 | Integer trackId, Long userId) { 37 | this.userId = userId; 38 | this.propertyMap = propertyMap; 39 | this.distinctId = distinctId; 40 | this.trackId = trackId; 41 | this.identityMap = identityMap; 42 | } 43 | 44 | public static USBuilder init() { 45 | return new USBuilder(); 46 | } 47 | 48 | public static class USBuilder { 49 | private Map idMap = new LinkedHashMap<>(); 50 | private String distinctId; 51 | private Integer trackId; 52 | private Long userId; 53 | private Map properties = new HashMap<>(); 54 | 55 | private USBuilder() { 56 | } 57 | 58 | public UserSchema start() throws InvalidArgumentException { 59 | this.distinctId = SensorsAnalyticsUtil.checkUserInfo(userId, idMap, distinctId); 60 | this.trackId = 61 | SensorsAnalyticsUtil.getTrackId(properties, String.format("[distinct_id=%s,user_id=%s]", distinctId, userId)); 62 | return new UserSchema(idMap, properties, distinctId, trackId, userId); 63 | } 64 | 65 | 66 | public USBuilder setUserId(@NonNull Long userId) { 67 | this.userId = userId; 68 | return this; 69 | } 70 | 71 | public USBuilder identityMap(@NonNull Map identityMap) { 72 | this.idMap.putAll(identityMap); 73 | return this; 74 | } 75 | 76 | public USBuilder addIdentityProperty(@NonNull String key, @NonNull String value) { 77 | this.idMap.put(key, value); 78 | return this; 79 | } 80 | 81 | 82 | public USBuilder setDistinctId(@NonNull String distinctId) { 83 | this.distinctId = distinctId; 84 | // IDM3.0 设置 distinctId,设置 $is_login_id = false,其实也可不设置 85 | return this; 86 | } 87 | 88 | public USBuilder addProperties(@NonNull Map properties) { 89 | this.properties.putAll(properties); 90 | return this; 91 | } 92 | 93 | public USBuilder addProperty(@NonNull String key, @NonNull String property) { 94 | addPropertyObject(key, property); 95 | return this; 96 | } 97 | 98 | public USBuilder addProperty(@NonNull String key, boolean property) { 99 | addPropertyObject(key, property); 100 | return this; 101 | } 102 | 103 | public USBuilder addProperty(@NonNull String key, @NonNull Number property) { 104 | addPropertyObject(key, property); 105 | return this; 106 | } 107 | 108 | public USBuilder addProperty(@NonNull String key, @NonNull Date property) { 109 | addPropertyObject(key, property); 110 | return this; 111 | } 112 | 113 | public USBuilder addProperty(@NonNull String key, @NonNull List property) { 114 | addPropertyObject(key, property); 115 | return this; 116 | } 117 | 118 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 119 | this.properties.put(key, property); 120 | } 121 | 122 | } 123 | 124 | public Long getUserId() { 125 | return userId; 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/ItemEventSchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import com.sensorsdata.analytics.javasdk.common.Pair; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 6 | 7 | import lombok.Getter; 8 | import lombok.NonNull; 9 | 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * item event schema 17 | * 18 | * @author fangzhuo 19 | * @version 1.0.0 20 | * @since 2022/06/13 17:03 21 | */ 22 | @Getter 23 | public class ItemEventSchema { 24 | 25 | private String schema; 26 | 27 | private String eventName; 28 | 29 | private Map properties; 30 | 31 | private Integer trackId; 32 | 33 | private Pair itemPair; 34 | 35 | protected ItemEventSchema(Integer trackId, String schema, String eventName, Pair itemPair, 36 | Map properties) { 37 | this.trackId = trackId; 38 | this.schema = schema; 39 | this.eventName = eventName; 40 | this.properties = properties; 41 | this.itemPair = itemPair; 42 | } 43 | 44 | public static IEBuilder init() { 45 | return new IEBuilder(); 46 | } 47 | 48 | public static class IEBuilder { 49 | 50 | private String schema; 51 | private String eventName; 52 | private Integer trackId; 53 | private Map properties = new HashMap<>(); 54 | private Pair itemPair; 55 | 56 | public ItemEventSchema start() throws InvalidArgumentException { 57 | SensorsAnalyticsUtil.assertSchema(schema); 58 | SensorsAnalyticsUtil.assertEventItemPair(itemPair); 59 | SensorsAnalyticsUtil.assertKey("event_name", eventName); 60 | SensorsAnalyticsUtil.assertSchemaProperties(properties, null); 61 | this.trackId = SensorsAnalyticsUtil.getTrackId(properties, String.format("[event=%s,schema=%s]", 62 | eventName, schema)); 63 | 64 | return new ItemEventSchema(trackId, schema, eventName, itemPair, properties); 65 | } 66 | 67 | public IEBuilder setSchema(@NonNull String schema) { 68 | this.schema = schema; 69 | return this; 70 | } 71 | 72 | public IEBuilder setEventName(@NonNull String eventName) { 73 | this.eventName = eventName; 74 | return this; 75 | } 76 | 77 | public IEBuilder setItemPair(@NonNull String itemId, @NonNull String value) { 78 | this.itemPair = Pair.of(itemId, value); 79 | return this; 80 | } 81 | 82 | public IEBuilder addProperties(@NonNull Map properties) { 83 | this.properties.putAll(properties); 84 | return this; 85 | } 86 | 87 | public IEBuilder addProperty(@NonNull String key, @NonNull String property) { 88 | addPropertyObject(key, property); 89 | return this; 90 | } 91 | 92 | public IEBuilder addProperty(@NonNull String key, boolean property) { 93 | addPropertyObject(key, property); 94 | return this; 95 | } 96 | 97 | public IEBuilder addProperty(@NonNull String key, @NonNull Number property) { 98 | addPropertyObject(key, property); 99 | return this; 100 | } 101 | 102 | public IEBuilder addProperty(@NonNull String key, @NonNull Date property) { 103 | addPropertyObject(key, property); 104 | return this; 105 | } 106 | 107 | public IEBuilder addProperty(@NonNull String key, @NonNull List property) { 108 | addPropertyObject(key, property); 109 | return this; 110 | } 111 | 112 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 113 | this.properties.put(key, property); 114 | } 115 | } 116 | 117 | public String getSchema() { 118 | return schema; 119 | } 120 | 121 | public String getEventName() { 122 | return eventName; 123 | } 124 | 125 | public Map getProperties() { 126 | return properties; 127 | } 128 | 129 | public Integer getTrackId() { 130 | return trackId; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/IDMBindTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 4 | import com.sensorsdata.analytics.javasdk.bean.schema.IdentitySchema; 5 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * bind/unbind 接口支持自定义属性 15 | * 16 | * @author fangzhuo 17 | * @version 1.0.0 18 | * @since 2023/10/18 15:02 19 | */ 20 | public class IDMBindTest extends SensorsBaseTest { 21 | 22 | @Test 23 | public void testIdMappingBind() throws InvalidArgumentException { 24 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 25 | .addIdentityProperty("$identity_mobile", "123") 26 | .addIdentityProperty("$identity_email", "fz@163.com") 27 | .build(); 28 | Map properties = new HashMap<>(); 29 | properties.put("$project", "abc"); 30 | properties.put("$time_free", true); 31 | properties.put("$token", "12345"); 32 | sa.bind(properties, identity); 33 | assertIDM3EventData(data); 34 | Assert.assertTrue(data.containsKey("project")); 35 | Assert.assertTrue(data.containsKey("time_free")); 36 | Assert.assertTrue(data.containsKey("token")); 37 | Assert.assertEquals("abc", data.get("project")); 38 | Assert.assertEquals(true, data.get("time_free")); 39 | Assert.assertEquals("12345", data.get("token")); 40 | } 41 | 42 | 43 | @Test 44 | public void testBind() throws InvalidArgumentException { 45 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 46 | .addIdentityProperty("$identity_mobile", "123") 47 | .addIdentityProperty("$identity_email", "fz@163.com") 48 | .build(); 49 | IdentitySchema schema = IdentitySchema.init() 50 | .identityMap(identity.getIdentityMap()) 51 | .addProperty("$project", "abc") 52 | .addProperty("$time_free", true) 53 | .addProperty("$token", "12345") 54 | .build(); 55 | sa.bind(schema); 56 | assertUESData(data); 57 | Assert.assertTrue(data.containsKey("project")); 58 | Assert.assertTrue(data.containsKey("time_free")); 59 | Assert.assertTrue(data.containsKey("token")); 60 | Assert.assertEquals("abc", data.get("project")); 61 | Assert.assertEquals(true, data.get("time_free")); 62 | Assert.assertEquals("12345", data.get("token")); 63 | } 64 | 65 | @Test 66 | public void testUnbind() throws InvalidArgumentException { 67 | Map properties = new HashMap<>(); 68 | properties.put("$project", "abc"); 69 | properties.put("$time_free", true); 70 | properties.put("$token", "12345"); 71 | sa.unbind("key1", "value1", properties); 72 | assertIDM3EventData(data); 73 | Assert.assertTrue(data.containsKey("project")); 74 | Assert.assertTrue(data.containsKey("time_free")); 75 | Assert.assertTrue(data.containsKey("token")); 76 | Assert.assertEquals("abc", data.get("project")); 77 | Assert.assertEquals(true, data.get("time_free")); 78 | Assert.assertEquals("12345", data.get("token")); 79 | } 80 | 81 | @Test 82 | public void testUnbindSchema() throws InvalidArgumentException { 83 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 84 | .addIdentityProperty("$identity_email", "fz@163.com") 85 | .build(); 86 | IdentitySchema schema = IdentitySchema.init() 87 | .identityMap(identity.getIdentityMap()) 88 | .addProperty("$project", "abc") 89 | .addProperty("$time_free", true) 90 | .addProperty("$token", "12345") 91 | .build(); 92 | sa.unbind(schema); 93 | assertUESData(data); 94 | Assert.assertTrue(data.containsKey("project")); 95 | Assert.assertTrue(data.containsKey("time_free")); 96 | Assert.assertTrue(data.containsKey("token")); 97 | Assert.assertEquals("abc", data.get("project")); 98 | Assert.assertEquals(true, data.get("time_free")); 99 | Assert.assertEquals("12345", data.get("token")); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/NormalModelTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 9 | import com.sensorsdata.analytics.javasdk.bean.UserRecord; 10 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 11 | 12 | import org.junit.Test; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Date; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * 普通模式校验 22 | * 23 | * @author fangzhuo 24 | * @version 1.0.0 25 | * @since 2021/11/18 23:36 26 | */ 27 | public class NormalModelTest extends SensorsBaseTest { 28 | 29 | /** 30 | * 校验调用 track 方法生成事件节点数是否完整 31 | */ 32 | @Test 33 | public void checkTrackEvent() throws InvalidArgumentException { 34 | Map properties = new HashMap<>(); 35 | properties.put("test", "test"); 36 | properties.put("$project", "abc"); 37 | properties.put("$token", "123"); 38 | sa.track("123", true, "test", properties); 39 | assertEventData(data); 40 | } 41 | 42 | /** 43 | * 校验 event Builder 模式生成数据用户属性是否正常 44 | */ 45 | @Test 46 | public void checkTrackEventBuilder() throws InvalidArgumentException { 47 | EventRecord eventRecord = EventRecord.builder() 48 | .setDistinctId("abc") 49 | .isLoginId(false) 50 | .setEventName("test") 51 | .build(); 52 | sa.track(eventRecord); 53 | assertEquals("abc", data.get("distinct_id")); 54 | assertNull(data.get("$is_login_id")); 55 | Map result = (Map) data.get("properties"); 56 | assertNull(result.get("$is_login_id")); 57 | } 58 | 59 | /** 60 | * 校验 is_login_id 为 true 的事件属性 61 | */ 62 | @Test 63 | public void checkTrackEventBuilderLoginIdIsTrue() throws InvalidArgumentException { 64 | EventRecord eventRecord = EventRecord.builder() 65 | .setDistinctId("abc") 66 | .isLoginId(true) 67 | .setEventName("test") 68 | .build(); 69 | sa.track(eventRecord); 70 | assertEquals("abc", data.get("distinct_id")); 71 | assertNull(data.get("$is_login_id")); 72 | Map result = (Map) data.get("properties"); 73 | assertTrue((Boolean) result.get("$is_login_id")); 74 | } 75 | 76 | /** 77 | * 校验自定义属性格式是否正常 78 | */ 79 | @Test 80 | public void checkProfileSetDataType() throws InvalidArgumentException { 81 | List list = new ArrayList<>(); 82 | Date date = new Date(); 83 | list.add("aaa"); 84 | list.add("bbb"); 85 | UserRecord userRecord = UserRecord.builder() 86 | .setDistinctId("123") 87 | .isLoginId(true) 88 | .addProperty("number1", 1234) 89 | .addProperty("date1", date) 90 | .addProperty("String1", "str") 91 | .addProperty("boolean1", false) 92 | .addProperty("list1", list) 93 | .build(); 94 | sa.profileSet(userRecord); 95 | Map result = (Map) data.get("properties"); 96 | assertEquals(1234, result.get("number1")); 97 | assertEquals(date, result.get("date1")); 98 | assertEquals("str", result.get("String1")); 99 | assertFalse((Boolean) result.get("boolean1")); 100 | assertTrue(result.get("list1") instanceof List); 101 | } 102 | 103 | /** 104 | * 校验 trackSignup 记录节点 105 | */ 106 | @Test 107 | public void checkTrackSignUp() throws InvalidArgumentException { 108 | sa.trackSignUp("123", "345"); 109 | assertEventData(data); 110 | } 111 | 112 | @Test 113 | public void checkCommonProperties() throws InvalidArgumentException { 114 | Map map = new HashMap<>(); 115 | map.put("key1", "value1"); 116 | sa.registerSuperProperties(map); 117 | Map properties = new HashMap<>(); 118 | properties.put("key1", "value2"); 119 | sa.track("123", true, "test1", properties); 120 | assertEventData(data); 121 | assertEquals("value2", ((Map) data.get("properties")).get("key1").toString()); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/SensorsAnalyticsWorker.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static com.sensorsdata.analytics.javasdk.SensorsConst.BIND_ID_ACTION_TYPE; 4 | import static com.sensorsdata.analytics.javasdk.SensorsConst.DEFAULT_LIB_DETAIL; 5 | import static com.sensorsdata.analytics.javasdk.SensorsConst.LIB; 6 | import static com.sensorsdata.analytics.javasdk.SensorsConst.LIB_DETAIL_SYSTEM_ATTR; 7 | import static com.sensorsdata.analytics.javasdk.SensorsConst.LIB_METHOD_SYSTEM_ATTR; 8 | import static com.sensorsdata.analytics.javasdk.SensorsConst.LIB_SYSTEM_ATTR; 9 | import static com.sensorsdata.analytics.javasdk.SensorsConst.LIB_VERSION_SYSTEM_ATTR; 10 | import static com.sensorsdata.analytics.javasdk.SensorsConst.SDK_VERSION; 11 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_ACTION_TYPE; 12 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_SIGN_UP_ACTION_TYPE; 13 | import static com.sensorsdata.analytics.javasdk.SensorsConst.UNBIND_ID_ACTION_TYPE; 14 | 15 | import com.sensorsdata.analytics.javasdk.consumer.Consumer; 16 | 17 | import lombok.NonNull; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | @Slf4j 24 | class SensorsAnalyticsWorker { 25 | 26 | private final Consumer consumer; 27 | 28 | private boolean timeFree = false; 29 | 30 | private boolean enableCollectMethodStack = true; 31 | 32 | public SensorsAnalyticsWorker(Consumer consumer) { 33 | this.consumer = consumer; 34 | Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 35 | @Override 36 | public void run() { 37 | log.info("Triggered flush when the program is closed."); 38 | flush(); 39 | } 40 | })); 41 | } 42 | 43 | void doAddData(@NonNull SensorsData sensorsData) { 44 | Map data = SensorsData.generateData(sensorsData); 45 | data.put("lib", generateLibInfo()); 46 | if (timeFree && (TRACK_ACTION_TYPE.equals(sensorsData.getType()) 47 | || TRACK_SIGN_UP_ACTION_TYPE.equals(sensorsData.getType())) 48 | || BIND_ID_ACTION_TYPE.equals(sensorsData.getType()) 49 | || UNBIND_ID_ACTION_TYPE.equals(sensorsData.getType())) { 50 | data.put("time_free", true); 51 | } 52 | this.consumer.send(data); 53 | } 54 | 55 | void flush() { 56 | this.consumer.flush(); 57 | } 58 | 59 | void shutdown() { 60 | this.consumer.close(); 61 | } 62 | 63 | 64 | public void doSchemaData(@NonNull SensorsSchemaData schemaData) { 65 | Map sensorsData = schemaData.generateData(); 66 | sensorsData.put("lib", generateLibInfo()); 67 | if (timeFree && (TRACK_ACTION_TYPE.equals(schemaData.getType()) 68 | || TRACK_SIGN_UP_ACTION_TYPE.equals(schemaData.getType()) 69 | || BIND_ID_ACTION_TYPE.equals(schemaData.getType()) 70 | || UNBIND_ID_ACTION_TYPE.equals(schemaData.getType()))) { 71 | sensorsData.put("time_free", true); 72 | } 73 | this.consumer.send(sensorsData); 74 | } 75 | 76 | public void setEnableTimeFree(boolean enableTimeFree) { 77 | this.timeFree = enableTimeFree; 78 | } 79 | 80 | public void setEnableCollectMethodStack(boolean enableCollectMethodStack) { 81 | this.enableCollectMethodStack = enableCollectMethodStack; 82 | } 83 | 84 | public Map generateLibInfo() { 85 | Map libProperties = new HashMap<>(); 86 | libProperties.put(LIB_SYSTEM_ATTR, LIB); 87 | libProperties.put(LIB_VERSION_SYSTEM_ATTR, SDK_VERSION); 88 | libProperties.put(LIB_METHOD_SYSTEM_ATTR, "code"); 89 | if (enableCollectMethodStack) { 90 | StackTraceElement[] trace = (new Exception()).getStackTrace(); 91 | if (trace.length > 3) { 92 | StackTraceElement traceElement = trace[3]; 93 | libProperties.put(LIB_DETAIL_SYSTEM_ATTR, 94 | String.format("%s##%s##%s##%s", traceElement.getClassName(), traceElement.getMethodName(), 95 | traceElement.getFileName(), traceElement.getLineNumber())); 96 | } 97 | } else { 98 | libProperties.put(LIB_DETAIL_SYSTEM_ATTR, DEFAULT_LIB_DETAIL); 99 | } 100 | return libProperties; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/ItemRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | import static com.sensorsdata.analytics.javasdk.SensorsConst.ITEM_ID; 4 | import static com.sensorsdata.analytics.javasdk.SensorsConst.ITEM_TYPE; 5 | 6 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 7 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 8 | 9 | import java.io.Serializable; 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * 纬度表信息实体对象 17 | * 18 | * @author fz 19 | * @version 1.0.0 20 | * @since 2021/05/24 17:25 21 | */ 22 | public class ItemRecord implements Serializable { 23 | 24 | private static final long serialVersionUID = -3294038187552297656L; 25 | 26 | private final Map propertyMap; 27 | 28 | private final String itemId; 29 | 30 | private final String itemType; 31 | 32 | private final Integer trackId; 33 | 34 | private ItemRecord(Map propertyMap, String itemId, String itemType, Integer trackId) { 35 | this.propertyMap = propertyMap; 36 | this.itemId = itemId; 37 | this.itemType = itemType; 38 | this.trackId = trackId; 39 | } 40 | 41 | public Map getPropertyMap() { 42 | return propertyMap; 43 | } 44 | 45 | public String getItemId() { 46 | return itemId; 47 | } 48 | 49 | public String getItemType() { 50 | return itemType; 51 | } 52 | 53 | public Integer getTrackId() { return trackId; } 54 | 55 | public static Builder builder() { 56 | return new Builder(); 57 | } 58 | 59 | public static class Builder { 60 | 61 | private Map propertyMap = new HashMap<>(); 62 | 63 | private String itemId; 64 | 65 | private String itemType; 66 | 67 | private Integer trackId; 68 | 69 | private Builder() { 70 | } 71 | 72 | public ItemRecord build() throws InvalidArgumentException { 73 | if (null == itemId) { 74 | throw new InvalidArgumentException("The itemId is empty."); 75 | } 76 | if (null == itemType) { 77 | throw new InvalidArgumentException("The itemType is empty."); 78 | } 79 | SensorsAnalyticsUtil.assertKey(ITEM_TYPE, itemType); 80 | SensorsAnalyticsUtil.assertValue(ITEM_ID, itemId); 81 | this.trackId = null; 82 | return new ItemRecord(propertyMap, itemId, itemType, trackId); 83 | } 84 | 85 | public ItemRecord.Builder setItemId(String itemId) { 86 | this.itemId = itemId; 87 | return this; 88 | } 89 | 90 | public ItemRecord.Builder setItemType(String itemType) { 91 | this.itemType = itemType; 92 | return this; 93 | } 94 | 95 | public ItemRecord.Builder addProperties(Map properties) { 96 | if (properties != null) { 97 | propertyMap.putAll(properties); 98 | } 99 | return this; 100 | } 101 | 102 | public ItemRecord.Builder addProperty(String key, String property) { 103 | addPropertyObject(key, property); 104 | return this; 105 | } 106 | 107 | public ItemRecord.Builder addProperty(String key, boolean property) { 108 | addPropertyObject(key, property); 109 | return this; 110 | } 111 | 112 | public ItemRecord.Builder addProperty(String key, Number property) { 113 | addPropertyObject(key, property); 114 | return this; 115 | } 116 | 117 | public ItemRecord.Builder addProperty(String key, Date property) { 118 | addPropertyObject(key, property); 119 | return this; 120 | } 121 | 122 | public ItemRecord.Builder addProperty(String key, List property) { 123 | addPropertyObject(key, property); 124 | return this; 125 | } 126 | 127 | private void addPropertyObject(String key, Object property) { 128 | if (key != null) { 129 | propertyMap.put(key, property); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/IDMUserRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | 4 | import com.sensorsdata.analytics.javasdk.SensorsConst; 5 | import com.sensorsdata.analytics.javasdk.common.Pair; 6 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 7 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 8 | 9 | import lombok.AccessLevel; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.NonNull; 13 | 14 | import java.util.Date; 15 | import java.util.HashMap; 16 | import java.util.LinkedHashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * IDM3.0 用户参数信息对象,用于构建 IDM3.0 profile 相关接口请求 22 | * 使用示例: 23 | *

24 | * IDMUserRecord.starter().identityMap(identityMap).addProperties(properties).build(); 25 | *

26 | * 27 | * @author fangzhuo 28 | * @version 1.0.0 29 | * @since 2022/03/09 14:44 30 | */ 31 | @Getter 32 | public class IDMUserRecord { 33 | 34 | private Map propertyMap; 35 | 36 | private String distinctId; 37 | 38 | private Integer trackId; 39 | 40 | private Map identityMap; 41 | 42 | 43 | protected IDMUserRecord(Map identityMap, Map propertyMap, String distinctId, 44 | Integer trackId) { 45 | this.identityMap = identityMap; 46 | this.propertyMap = propertyMap; 47 | this.distinctId = distinctId; 48 | this.trackId = trackId; 49 | } 50 | 51 | public static IDMBuilder starter() { 52 | return new IDMBuilder(); 53 | } 54 | 55 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 56 | public static class IDMBuilder { 57 | private final Map idMap = new LinkedHashMap<>(); 58 | private String distinctId; 59 | private final Map propertyMap = new HashMap<>(); 60 | private Integer trackId; 61 | 62 | public IDMUserRecord build() throws InvalidArgumentException { 63 | Pair resPair = 64 | SensorsAnalyticsUtil.checkIdentitiesAndGenerateDistinctId(distinctId, idMap); 65 | if (resPair.getValue()) { 66 | propertyMap.put(SensorsConst.LOGIN_SYSTEM_ATTR, true); 67 | } 68 | // 填充 distinct_id 和 目标表 69 | String message = String.format("[distinct_id=%s,target_table=user]",distinctId); 70 | trackId = SensorsAnalyticsUtil.getTrackId(propertyMap, message); 71 | return new IDMUserRecord(idMap, propertyMap, resPair.getKey(), trackId); 72 | } 73 | 74 | public IDMUserRecord.IDMBuilder identityMap(Map identityMap) { 75 | if (identityMap != null) { 76 | this.idMap.putAll(identityMap); 77 | } 78 | return this; 79 | } 80 | 81 | public IDMUserRecord.IDMBuilder addIdentityProperty(String key, String value) { 82 | this.idMap.put(key, value); 83 | return this; 84 | } 85 | 86 | public IDMUserRecord.IDMBuilder setDistinctId(@NonNull String distinctId) { 87 | this.distinctId = distinctId; 88 | // IDM3.0 设置 distinctId,设置 $is_login_id = false,其实也可不设置 89 | return this; 90 | } 91 | 92 | public IDMUserRecord.IDMBuilder addProperties(Map properties) { 93 | if (properties != null) { 94 | propertyMap.putAll(properties); 95 | } 96 | return this; 97 | } 98 | 99 | public IDMUserRecord.IDMBuilder addProperty(String key, String property) { 100 | addPropertyObject(key, property); 101 | return this; 102 | } 103 | 104 | public IDMUserRecord.IDMBuilder addProperty(String key, boolean property) { 105 | addPropertyObject(key, property); 106 | return this; 107 | } 108 | 109 | public IDMUserRecord.IDMBuilder addProperty(String key, Number property) { 110 | addPropertyObject(key, property); 111 | return this; 112 | } 113 | 114 | public IDMUserRecord.IDMBuilder addProperty(String key, Date property) { 115 | addPropertyObject(key, property); 116 | return this; 117 | } 118 | 119 | public IDMUserRecord.IDMBuilder addProperty(String key, List property) { 120 | addPropertyObject(key, property); 121 | return this; 122 | } 123 | 124 | private void addPropertyObject(String key, Object property) { 125 | if (key != null) { 126 | propertyMap.put(key, property); 127 | } 128 | } 129 | 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/UserRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | 4 | import com.sensorsdata.analytics.javasdk.SensorsConst; 5 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 6 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 7 | 8 | import java.io.Serializable; 9 | import java.util.Date; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * 用户信息实体对象 16 | * 17 | * @author fz 18 | * @version 1.0.0 19 | * @since 2021/05/24 15:52 20 | */ 21 | public class UserRecord implements Serializable { 22 | 23 | private static final long serialVersionUID = -6661580072748171679L; 24 | 25 | private final Map propertyMap; 26 | 27 | private final String distinctId; 28 | 29 | private final Boolean isLoginId; 30 | 31 | private final Integer trackId; 32 | 33 | private UserRecord(Map propertyMap, String distinctId, Boolean isLoginId, Integer trackId) { 34 | this.propertyMap = propertyMap; 35 | if (isLoginId) { 36 | propertyMap.put(SensorsConst.LOGIN_SYSTEM_ATTR, true); 37 | } 38 | this.distinctId = distinctId; 39 | this.isLoginId = isLoginId; 40 | this.trackId = trackId; 41 | } 42 | 43 | public static Builder builder() { 44 | return new Builder(); 45 | } 46 | 47 | public String getDistinctId() { 48 | return distinctId; 49 | } 50 | 51 | public Boolean getIsLoginId() { 52 | return isLoginId; 53 | } 54 | 55 | public Map getPropertyMap() { 56 | return propertyMap; 57 | } 58 | 59 | public Integer getTrackId() {return trackId; } 60 | 61 | public static class Builder { 62 | private final Map propertyMap = new HashMap<>(); 63 | private String distinctId; 64 | private Boolean isLoginId; 65 | private Integer trackId; 66 | 67 | private Builder() { 68 | } 69 | 70 | public UserRecord build() throws InvalidArgumentException { 71 | if (distinctId == null) { 72 | throw new InvalidArgumentException("The distinctId is empty."); 73 | } 74 | if (isLoginId == null) { 75 | throw new InvalidArgumentException("The isLoginId is empty."); 76 | } 77 | SensorsAnalyticsUtil.assertValue("distinct_id", distinctId); 78 | String message = String.format("[distinct_id=%s,target_table=user]",distinctId); 79 | trackId = SensorsAnalyticsUtil.getTrackId(propertyMap, message); 80 | return new UserRecord(propertyMap, distinctId, isLoginId, trackId); 81 | } 82 | 83 | public UserRecord.Builder setDistinctId(String distinctId) { 84 | this.distinctId = distinctId; 85 | return this; 86 | } 87 | 88 | public UserRecord.Builder isLoginId(Boolean loginId) { 89 | this.isLoginId = loginId; 90 | return this; 91 | } 92 | 93 | public UserRecord.Builder addProperties(Map properties) { 94 | if (properties != null) { 95 | propertyMap.putAll(properties); 96 | } 97 | return this; 98 | } 99 | 100 | public UserRecord.Builder addProperty(String key, String property) { 101 | addPropertyObject(key, property); 102 | return this; 103 | } 104 | 105 | public UserRecord.Builder addProperty(String key, boolean property) { 106 | addPropertyObject(key, property); 107 | return this; 108 | } 109 | 110 | public UserRecord.Builder addProperty(String key, Number property) { 111 | addPropertyObject(key, property); 112 | return this; 113 | } 114 | 115 | public UserRecord.Builder addProperty(String key, Date property) { 116 | addPropertyObject(key, property); 117 | return this; 118 | } 119 | 120 | public UserRecord.Builder addProperty(String key, List property) { 121 | addPropertyObject(key, property); 122 | return this; 123 | } 124 | 125 | private void addPropertyObject(String key, Object property) { 126 | if (key != null) { 127 | propertyMap.put(key, property); 128 | } 129 | } 130 | 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/UserEventSchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import com.sensorsdata.analytics.javasdk.SensorsConst; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 6 | 7 | import lombok.Getter; 8 | import lombok.NonNull; 9 | 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * user-event schema 18 | * 19 | * @author fangzhuo 20 | * @version 1.0.0 21 | * @since 2022/06/13 15:28 22 | */ 23 | @Getter 24 | public class UserEventSchema { 25 | 26 | private Integer trackId; 27 | 28 | private Long userId; 29 | 30 | private String eventName; 31 | /** 32 | * distinctId 标识,在 IDM3.0 里面,该参数可传可不传 33 | */ 34 | private String distinctId; 35 | /** 36 | * 事件携带的属性集合 37 | */ 38 | private Map propertyMap; 39 | 40 | private Map identityMap; 41 | 42 | 43 | protected UserEventSchema(Map identityMap, String eventName, String distinctId, 44 | Map propertyMap, Integer trackId, Long userId) { 45 | this.eventName = eventName; 46 | this.distinctId = distinctId; 47 | this.propertyMap = propertyMap; 48 | this.trackId = trackId; 49 | this.identityMap = identityMap; 50 | this.userId = userId; 51 | } 52 | 53 | public Long getUserId() { 54 | return userId; 55 | } 56 | 57 | public static UESBuilder init() { 58 | return new UESBuilder(); 59 | } 60 | 61 | public static class UESBuilder { 62 | private Map idMap = new LinkedHashMap<>(); 63 | private Long userId; 64 | private String distinctId; 65 | private String eventName; 66 | private Map properties = new HashMap<>(); 67 | private Integer trackId; 68 | 69 | private UESBuilder() { 70 | } 71 | 72 | public UserEventSchema start() throws InvalidArgumentException { 73 | SensorsAnalyticsUtil.assertKey("event_name", eventName); 74 | if (!properties.isEmpty()) { 75 | SensorsAnalyticsUtil.assertSchemaProperties(properties, SensorsConst.TRACK_ACTION_TYPE); 76 | } 77 | this.trackId = SensorsAnalyticsUtil.getTrackId(properties, 78 | String.format("[distinct_id=%s,event_name=%s]", distinctId, eventName)); 79 | this.distinctId = SensorsAnalyticsUtil.checkUserInfo(userId, idMap, distinctId); 80 | return new UserEventSchema(idMap, eventName, distinctId, properties, trackId, userId); 81 | } 82 | 83 | public UESBuilder setUserId(@NonNull Long userId) { 84 | this.userId = userId; 85 | return this; 86 | } 87 | 88 | public UESBuilder identityMap(@NonNull Map identityMap) { 89 | this.idMap.putAll(identityMap); 90 | return this; 91 | } 92 | 93 | public UESBuilder addIdentityProperty(@NonNull String key, @NonNull String value) { 94 | this.idMap.put(key, value); 95 | return this; 96 | } 97 | 98 | public UESBuilder setEventName(@NonNull String eventName) { 99 | this.eventName = eventName; 100 | return this; 101 | } 102 | 103 | public UESBuilder setDistinctId(@NonNull String distinctId) { 104 | this.distinctId = distinctId; 105 | // IDM3.0 设置 distinctId,设置 $is_login_id = false,其实也可不设置 106 | return this; 107 | } 108 | 109 | public UESBuilder addProperties(@NonNull Map properties) { 110 | this.properties.putAll(properties); 111 | return this; 112 | } 113 | 114 | public UESBuilder addProperty(@NonNull String key, @NonNull String property) { 115 | addPropertyObject(key, property); 116 | return this; 117 | } 118 | 119 | public UESBuilder addProperty(@NonNull String key, boolean property) { 120 | addPropertyObject(key, property); 121 | return this; 122 | } 123 | 124 | public UESBuilder addProperty(@NonNull String key, @NonNull Number property) { 125 | addPropertyObject(key, property); 126 | return this; 127 | } 128 | 129 | public UESBuilder addProperty(@NonNull String key, @NonNull Date property) { 130 | addPropertyObject(key, property); 131 | return this; 132 | } 133 | 134 | public UESBuilder addProperty(@NonNull String key, @NonNull List property) { 135 | addPropertyObject(key, property); 136 | return this; 137 | } 138 | 139 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 140 | this.properties.put(key, property); 141 | } 142 | 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SensorsAnalyticsUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | import static org.junit.Assert.fail; 5 | 6 | import com.sensorsdata.analytics.javasdk.bean.FailedData; 7 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 8 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 9 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 10 | 11 | import org.junit.Test; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.Random; 17 | 18 | /** 19 | * 工具类测试 20 | * 21 | * @author fangzhuo 22 | * @version 1.0.0 23 | * @since 2021/11/25 14:03 24 | */ 25 | public class SensorsAnalyticsUtilTest { 26 | 27 | @Test 28 | public void checkAssertFailedDataWithNoTrackId() { 29 | Map event = new HashMap<>(); 30 | event.put("distinct_id", "12345"); 31 | event.put("event", "test"); 32 | event.put("type", "track"); 33 | Map properties = new HashMap<>(); 34 | properties.put("test", "test"); 35 | event.put("properties", properties); 36 | ArrayList> list = new ArrayList<>(); 37 | list.add(event); 38 | FailedData failedData = new FailedData("", list); 39 | try { 40 | SensorsAnalyticsUtil.assertFailedData(failedData); 41 | fail(); 42 | } catch (InvalidArgumentException e) { 43 | assertTrue(true); 44 | } 45 | } 46 | 47 | @Test 48 | public void checkAssertFailedDataWithNoType() { 49 | Map event = new HashMap<>(); 50 | event.put("_track_id", new Random().nextInt()); 51 | event.put("distinct_id", "12345"); 52 | event.put("event", "test"); 53 | Map properties = new HashMap<>(); 54 | properties.put("test", "test"); 55 | event.put("properties", properties); 56 | ArrayList> list = new ArrayList<>(); 57 | list.add(event); 58 | FailedData failedData = new FailedData("", list); 59 | try { 60 | SensorsAnalyticsUtil.assertFailedData(failedData); 61 | fail(); 62 | } catch (InvalidArgumentException e) { 63 | assertTrue(true); 64 | } 65 | } 66 | 67 | @Test 68 | public void checkAssertFailedDataWithInvalidPropertiesKey() { 69 | Map event = new HashMap<>(); 70 | event.put("_track_id", new Random().nextInt()); 71 | event.put("distinct_id", "12345"); 72 | event.put("event", "test"); 73 | Map properties = new HashMap<>(); 74 | properties.put("distinct_id", "test"); 75 | event.put("properties", properties); 76 | ArrayList> list = new ArrayList<>(); 77 | list.add(event); 78 | FailedData failedData = new FailedData("", list); 79 | try { 80 | SensorsAnalyticsUtil.assertFailedData(failedData); 81 | fail(); 82 | } catch (InvalidArgumentException e) { 83 | assertTrue(true); 84 | } 85 | } 86 | 87 | @Test 88 | public void checkAssertIDMappingFailedData() { 89 | Map event = new HashMap<>(); 90 | event.put("_track_id", new Random().nextInt()); 91 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 92 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, "12345") 93 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, "fz@163.com") 94 | .build(); 95 | event.put("identities", identity.getIdentityMap()); 96 | event.put("distinct_id", "12345"); 97 | event.put("type", "track"); 98 | event.put("event", "test"); 99 | Map properties = new HashMap<>(); 100 | properties.put("test", "test"); 101 | event.put("properties", properties); 102 | ArrayList> list = new ArrayList<>(); 103 | list.add(event); 104 | try { 105 | SensorsAnalyticsUtil.assertFailedData(new FailedData("", list)); 106 | } catch (InvalidArgumentException e) { 107 | fail(); 108 | } 109 | } 110 | 111 | @Test 112 | public void checkAssertFailedData() { 113 | Map event = new HashMap<>(); 114 | event.put("_track_id", new Random().nextInt()); 115 | event.put("distinct_id", "12345"); 116 | event.put("type", "track"); 117 | event.put("event", "test"); 118 | Map properties = new HashMap<>(); 119 | properties.put("test", "test"); 120 | event.put("properties", properties); 121 | ArrayList> list = new ArrayList<>(); 122 | list.add(event); 123 | try { 124 | SensorsAnalyticsUtil.assertFailedData(new FailedData("", list)); 125 | } catch (InvalidArgumentException e) { 126 | fail(); 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SchemaUserTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.schema.UserSchema; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * v3.4.5+ 用户 schema 14 | * 15 | * @author fangzhuo 16 | * @version 1.0.0 17 | * @since 2022/06/16 18:12 18 | */ 19 | public class SchemaUserTest extends SensorsBaseTest { 20 | 21 | private static final Long USER_ID = 12345L; 22 | 23 | private static final String DISTINCT_ID = "fz123"; 24 | /** 25 | * 用户 26 | */ 27 | @Test 28 | public void checkProfileSetTest() throws InvalidArgumentException { 29 | UserSchema userSchema = UserSchema.init() 30 | .addIdentityProperty("user1", "value1") 31 | .addProperty("key1", "value1") 32 | .addProperty("key2", 22) 33 | .setDistinctId("aaa") 34 | .start(); 35 | sa.profileSet(userSchema); 36 | assertUSData(data); 37 | } 38 | 39 | 40 | @Test 41 | public void checkProfileSetOnce() throws InvalidArgumentException { 42 | UserSchema userSchema = UserSchema.init() 43 | .addIdentityProperty("login_id", DISTINCT_ID) 44 | .addIdentityProperty("key1", "value1") 45 | .setDistinctId("aaa") 46 | .start(); 47 | sa.profileSetOnce(userSchema); 48 | assertUSData(data); 49 | } 50 | 51 | @Test 52 | public void checkProfileSetIncrement() throws InvalidArgumentException { 53 | UserSchema userSchema = UserSchema.init() 54 | .addIdentityProperty("login_id", DISTINCT_ID) 55 | .addProperty("key1", 20) 56 | .start(); 57 | sa.profileIncrement(userSchema); 58 | assertUSData(data); 59 | } 60 | 61 | @Test 62 | public void checkProfileAppend() throws InvalidArgumentException { 63 | List others = new ArrayList<>(); 64 | others.add("swim"); 65 | others.add("run"); 66 | UserSchema userSchema = UserSchema.init() 67 | .addIdentityProperty("login_id", DISTINCT_ID) 68 | .addProperty("key1", others) 69 | .start(); 70 | sa.profileAppend(userSchema); 71 | assertUSData(data); 72 | } 73 | 74 | @Test 75 | public void checkProfileUnset() throws InvalidArgumentException { 76 | UserSchema userSchema = UserSchema.init() 77 | .addIdentityProperty("login_id", DISTINCT_ID) 78 | .addProperty("key1", true) 79 | .start(); 80 | sa.profileUnset(userSchema); 81 | assertUSData(data); 82 | } 83 | 84 | /** 85 | * 删除用户属性;只支持传入单个用户 86 | */ 87 | @Test 88 | public void checkProfileDeleteByUserId() throws InvalidArgumentException { 89 | sa.profileDelete("key1", "value1"); 90 | assertUSData(data); 91 | } 92 | 93 | @Test 94 | public void checkPreDefineProperties() throws InvalidArgumentException { 95 | UserSchema userSchema = UserSchema.init() 96 | .addIdentityProperty("key1", "value1") 97 | .addProperty("$project", "abc") 98 | .start(); 99 | sa.profileSet(userSchema); 100 | assertUSData(data); 101 | 102 | } 103 | 104 | //----------------------------------v3.5.2-------------------------------------- 105 | 106 | /** 107 | * 支持 user 数据传入 userId 作为用户标识 108 | *

期望:用户数据传入 userId,最终节点中包含 userId 和 distinctId 信息

109 | */ 110 | @Test 111 | public void checkUserId() throws InvalidArgumentException { 112 | UserSchema userSchema = UserSchema.init() 113 | .setUserId(123L) 114 | .addProperty("$project", "abc") 115 | .start(); 116 | sa.profileSet(userSchema); 117 | assertUSData(data); 118 | } 119 | 120 | /** 121 | * 同时传入 userID 和 identities 节点 122 | *

期望:userId 优先级最高,两者同时传入,最终数据中存在 userId

123 | */ 124 | @Test 125 | public void checkUserIdAndIdentities() throws InvalidArgumentException { 126 | UserSchema userSchema = UserSchema.init() 127 | .setUserId(123L) 128 | .addIdentityProperty("key1", "value1") 129 | .addProperty("$project", "abc") 130 | .start(); 131 | sa.profileSet(userSchema); 132 | assertUSData(data); 133 | Assert.assertTrue(data.containsKey("id")); 134 | } 135 | 136 | /** 137 | * 同时传入 userID 和 distinctId 节点 138 | *

期望:最终数据节点中,以传入的 distinctId 为主

139 | */ 140 | @Test 141 | public void checkUserIdAndDistinctId() throws InvalidArgumentException { 142 | UserSchema userSchema = UserSchema.init() 143 | .setUserId(123L) 144 | .setDistinctId("test") 145 | .addIdentityProperty("key1", "value1") 146 | .addProperty("$project", "abc") 147 | .start(); 148 | sa.profileSet(userSchema); 149 | assertUSData(data); 150 | Assert.assertTrue(data.containsKey("distinct_id")); 151 | Assert.assertEquals("test", data.get("distinct_id")); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/IDMEventRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_ACTION_TYPE; 4 | 5 | import com.sensorsdata.analytics.javasdk.SensorsConst; 6 | import com.sensorsdata.analytics.javasdk.common.Pair; 7 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 8 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 9 | 10 | import lombok.AccessLevel; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | import lombok.NonNull; 14 | 15 | import java.util.Date; 16 | import java.util.HashMap; 17 | import java.util.LinkedHashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | /** 22 | * IDM3.0 事件参数对象,用于构建 IDM3.0 track 请求 23 | * 使用示例: 24 | *

25 | * IDMEventRecord.starter().identityMap(identityMap).setEventName(eventName).addProperties(properties).build(); 26 | *

27 | * 28 | * @author fangzhuo 29 | * @version 1.0.0 30 | * @since 2022/03/09 14:41 31 | */ 32 | @Getter 33 | public class IDMEventRecord { 34 | /** 35 | * 事件名称 36 | */ 37 | private String eventName; 38 | /** 39 | * distinctId 标识,在 IDM3.0 里面,该参数可传可不传 40 | */ 41 | private String distinctId; 42 | /** 43 | * 事件携带的属性集合 44 | */ 45 | private Map propertyMap; 46 | 47 | private Integer trackId; 48 | 49 | private Map identityMap; 50 | 51 | 52 | protected IDMEventRecord(Map identityMap, String eventName, String distinctId, 53 | Map propertyMap, Integer trackId) { 54 | this.identityMap = identityMap; 55 | this.eventName = eventName; 56 | this.distinctId = distinctId; 57 | this.propertyMap = propertyMap; 58 | this.trackId = trackId; 59 | } 60 | 61 | public static IDMBuilder starter() { 62 | return new IDMBuilder(); 63 | } 64 | 65 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 66 | public static class IDMBuilder { 67 | private final Map idMap = new LinkedHashMap<>(); 68 | private final Map propertyMap = new HashMap<>(); 69 | private String eventName; 70 | private String distinctId; 71 | private Integer trackId; 72 | 73 | public IDMEventRecord build() throws InvalidArgumentException { 74 | SensorsAnalyticsUtil.assertKey("event_name", eventName); 75 | if (propertyMap.size() != 0) { 76 | SensorsAnalyticsUtil.assertProperties(TRACK_ACTION_TYPE, propertyMap); 77 | } 78 | if (idMap.size() < 1) { 79 | throw new InvalidArgumentException("The identity is empty."); 80 | } 81 | Pair resPair = 82 | SensorsAnalyticsUtil.checkIdentitiesAndGenerateDistinctId(distinctId, idMap); 83 | if (resPair.getValue()) { 84 | propertyMap.put(SensorsConst.LOGIN_SYSTEM_ATTR, true); 85 | } 86 | String message = String.format("[distinct_id=%s,event_name=%s]",distinctId,eventName); 87 | trackId = SensorsAnalyticsUtil.getTrackId(propertyMap, message); 88 | return new IDMEventRecord(idMap, eventName, resPair.getKey(), propertyMap,trackId); 89 | } 90 | 91 | public IDMEventRecord.IDMBuilder identityMap(Map identityMap) { 92 | if (identityMap != null) { 93 | this.idMap.putAll(identityMap); 94 | } 95 | return this; 96 | } 97 | 98 | public IDMEventRecord.IDMBuilder addIdentityProperty(String key, String value) { 99 | this.idMap.put(key, value); 100 | return this; 101 | } 102 | 103 | public IDMEventRecord.IDMBuilder setEventName(@NonNull String eventName) { 104 | this.eventName = eventName; 105 | return this; 106 | } 107 | 108 | public IDMEventRecord.IDMBuilder setDistinctId(@NonNull String distinctId) { 109 | this.distinctId = distinctId; 110 | // IDM3.0 设置 distinctId,设置 $is_login_id = false,其实也可不设置 111 | return this; 112 | } 113 | 114 | public IDMEventRecord.IDMBuilder addProperties(Map properties) { 115 | if (properties != null) { 116 | propertyMap.putAll(properties); 117 | } 118 | return this; 119 | } 120 | 121 | public IDMEventRecord.IDMBuilder addProperty(String key, String property) { 122 | addPropertyObject(key, property); 123 | return this; 124 | } 125 | 126 | public IDMEventRecord.IDMBuilder addProperty(String key, boolean property) { 127 | addPropertyObject(key, property); 128 | return this; 129 | } 130 | 131 | public IDMEventRecord.IDMBuilder addProperty(String key, Number property) { 132 | addPropertyObject(key, property); 133 | return this; 134 | } 135 | 136 | public IDMEventRecord.IDMBuilder addProperty(String key, Date property) { 137 | addPropertyObject(key, property); 138 | return this; 139 | } 140 | 141 | public IDMEventRecord.IDMBuilder addProperty(String key, List property) { 142 | addPropertyObject(key, property); 143 | return this; 144 | } 145 | 146 | private void addPropertyObject(String key, Object property) { 147 | if (key != null) { 148 | propertyMap.put(key, property); 149 | } 150 | } 151 | 152 | 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/InnerLoggingConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.FileNotFoundException; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | import java.util.Map; 13 | 14 | @Slf4j 15 | class InnerLoggingConsumer implements Consumer { 16 | 17 | // 默认缓存限制为 1G 18 | private static final int BUFFER_LIMITATION = 1024 * 1024 * 1024; 19 | private final ObjectMapper jsonMapper; 20 | private final String filenamePrefix; 21 | private final StringBuilder messageBuffer; 22 | private final int bufferSize; 23 | private final SimpleDateFormat simpleDateFormat; 24 | 25 | private final LoggingFileWriterFactory fileWriterFactory; 26 | private LoggingFileWriter fileWriter; 27 | 28 | InnerLoggingConsumer( 29 | LoggingFileWriterFactory fileWriterFactory, 30 | String filenamePrefix, 31 | int bufferSize, LogSplitMode splitMode) { 32 | this.fileWriterFactory = fileWriterFactory; 33 | this.filenamePrefix = filenamePrefix; 34 | this.jsonMapper = SensorsAnalyticsUtil.getJsonObjectMapper(); 35 | this.messageBuffer = new StringBuilder(bufferSize); 36 | this.bufferSize = bufferSize; 37 | if (splitMode == LogSplitMode.HOUR) { 38 | this.simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH"); 39 | } else { 40 | this.simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); 41 | } 42 | log.info( 43 | "Initialize LoggingConsumer with params:[filenamePrefix:{},bufferSize:{},splitMode:{}].", 44 | filenamePrefix, bufferSize, splitMode); 45 | } 46 | 47 | @Override 48 | public synchronized void send(Map message) { 49 | if (messageBuffer.length() < BUFFER_LIMITATION) { 50 | try { 51 | messageBuffer.append(jsonMapper.writeValueAsString(message)); 52 | messageBuffer.append("\n"); 53 | } catch (JsonProcessingException e) { 54 | log.error("Failed to process json.", e); 55 | throw new RuntimeException("fail to process json", e); 56 | } 57 | } else { 58 | log.error("Logging cache exceeded the allowed limitation,current cache size is {}.", 59 | messageBuffer.length()); 60 | throw new RuntimeException("logging buffer exceeded the allowed limitation."); 61 | } 62 | log.debug("Successfully save data to cache,The cache current size is {}.", messageBuffer.length()); 63 | if (messageBuffer.length() >= bufferSize) { 64 | log.info("Flush triggered because logging cache size reached the threshold,cache size:{},bulkSize:{}.", 65 | messageBuffer.length(), bufferSize); 66 | flush(); 67 | } 68 | } 69 | 70 | private String constructFileName(Date now) { 71 | return filenamePrefix + "." + simpleDateFormat.format(now); 72 | } 73 | 74 | @Override 75 | public synchronized void flush() { 76 | if (messageBuffer.length() == 0) { 77 | log.info("The cache is empty when flush."); 78 | return; 79 | } 80 | 81 | String filename = constructFileName(new Date()); 82 | 83 | if (fileWriter != null && !fileWriter.isValid(filename)) { 84 | this.fileWriterFactory.closeFileWriter(fileWriter); 85 | log.info("The new file name [{}] is different from current file name,so update file writer.", filename); 86 | fileWriter = null; 87 | } 88 | 89 | if (fileWriter == null) { 90 | try { 91 | fileWriter = this.fileWriterFactory.getFileWriter(filenamePrefix, filename); 92 | } catch (FileNotFoundException e) { 93 | log.error("Failed to create file Writer.", e); 94 | throw new RuntimeException(e); 95 | } 96 | log.info("Initialize LoggingConsumer file writer,fileName:{}.", filename); 97 | } 98 | log.debug("Will be write data from cache to file.[{}]", messageBuffer); 99 | if (fileWriter.write(messageBuffer)) { 100 | messageBuffer.setLength(0); 101 | log.info("Successfully write data from cache to file."); 102 | } 103 | } 104 | 105 | @Override 106 | public synchronized void close() { 107 | flush(); 108 | if (fileWriter != null) { 109 | this.fileWriterFactory.closeFileWriter(fileWriter); 110 | fileWriter = null; 111 | } 112 | log.info("Call close method."); 113 | } 114 | 115 | } 116 | 117 | interface LoggingFileWriter { 118 | boolean isValid(final String fileName); 119 | 120 | boolean write(final StringBuilder sb); 121 | 122 | void close(); 123 | } 124 | 125 | interface LoggingFileWriterFactory { 126 | 127 | LoggingFileWriter getFileWriter(final String fileName, final String scheduleFileName) 128 | throws FileNotFoundException; 129 | 130 | void closeFileWriter(LoggingFileWriter writer); 131 | 132 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SchemaDetailTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | 6 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 7 | import com.sensorsdata.analytics.javasdk.bean.schema.DetailSchema; 8 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 9 | 10 | import org.junit.Test; 11 | 12 | import java.util.Date; 13 | import java.util.Map; 14 | 15 | /** 16 | * detail 明细数据单元测试 17 | *

v3.6.3+ 版本开始支持

18 | * 19 | * @author fangzhuo 20 | * @version 1.0.0 21 | * @since 2023/03/21 15:42 22 | */ 23 | public class SchemaDetailTest extends SensorsBaseTest { 24 | 25 | /** 26 | * 测试 detail schema 数据生成逻辑是否正确 27 | */ 28 | @Test 29 | public void checkDetailSet() throws InvalidArgumentException { 30 | DetailSchema detailSchema = 31 | DetailSchema.init().setSchema("test").setDetailId("ee").addProperty("key1", "value1").start(); 32 | sa.detailSet(detailSchema); 33 | assertSchemaDataNode(data); 34 | } 35 | 36 | /** 37 | * 测试用户实体明细数据生成逻辑是否正常 38 | */ 39 | @Test 40 | public void checkUserDetailSet() throws InvalidArgumentException { 41 | SensorsAnalyticsIdentity sensorsIdentities = SensorsAnalyticsIdentity.builder() 42 | .addIdentityProperty("user_key1", "user_value1") 43 | .addIdentityProperty("user_key2", "user_value2") 44 | .build(); 45 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 46 | .identityMap(sensorsIdentities.getIdentityMap()) 47 | .addProperty("key1", "value1") 48 | .addProperty("key2", new Date()) 49 | .start(); 50 | sa.detailSet(detailSchema); 51 | assertSchemaDataNode(data); 52 | Object distinctId = ((Map) data.get("properties")).get("distinct_id"); 53 | assertEquals("user_key1+user_value1", distinctId); 54 | } 55 | 56 | /** 57 | * 测试item实体明细数据生成逻辑 58 | */ 59 | @Test 60 | public void checkItemDetailSet() throws InvalidArgumentException { 61 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 62 | .setItemPair("item_key", "item_value") 63 | .addProperty("key1", "value1") 64 | .start(); 65 | sa.detailSet(detailSchema); 66 | assertSchemaDataNode(data); 67 | } 68 | 69 | /** 70 | * 手动指定 distinct_id,确认 distinct_id 是否为设置值 71 | */ 72 | @Test 73 | public void checkUserDistinctIdDetailSet() throws InvalidArgumentException { 74 | SensorsAnalyticsIdentity sensorsIdentities = SensorsAnalyticsIdentity.builder() 75 | .addIdentityProperty("user_key1", "user_value1") 76 | .addIdentityProperty("user_key2", "user_value2") 77 | .build(); 78 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 79 | .identityMap(sensorsIdentities.getIdentityMap()) 80 | .setDistinctId("123") 81 | .addProperty("key1", "value1") 82 | .addProperty("key2", new Date()) 83 | .start(); 84 | sa.detailSet(detailSchema); 85 | assertSchemaDataNode(data); 86 | Object distinctId = ((Map) data.get("properties")).get("distinct_id"); 87 | assertEquals("123", distinctId); 88 | } 89 | 90 | /** 91 | * 不传入 identities,然后设置 distinct_id,最终数据不应该出现该值 92 | */ 93 | @Test 94 | public void checkDistinctIdDetailSet() throws InvalidArgumentException { 95 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 96 | .setDistinctId("123") 97 | .addProperty("key1", "value1") 98 | .addProperty("key2", new Date()) 99 | .start(); 100 | sa.detailSet(detailSchema); 101 | assertSchemaDataNode(data); 102 | assertFalse(((Map) data.get("properties")).containsKey("distinct_id")); 103 | } 104 | 105 | /** 106 | * 同时传入 identities 和 itemPair 会导致抛出参数不合法异常 107 | */ 108 | @Test 109 | public void checkIdentitiesAndItemIdDetailSet() { 110 | SensorsAnalyticsIdentity sensorsIdentities = SensorsAnalyticsIdentity.builder() 111 | .addIdentityProperty("user_key1", "user_value1") 112 | .addIdentityProperty("user_key2", "user_value2") 113 | .build(); 114 | String message = ""; 115 | try { 116 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 117 | .identityMap(sensorsIdentities.getIdentityMap()) 118 | .setItemPair("item_key", "item_value") 119 | .setDistinctId("123") 120 | .addProperty("key1", "value1") 121 | .addProperty("key2", new Date()) 122 | .start(); 123 | } catch (InvalidArgumentException e) { 124 | message = e.getMessage(); 125 | } 126 | assertEquals("detail schema cannot both set identities and itemPair.", message); 127 | } 128 | 129 | @Test 130 | public void checkDetailDelete() throws InvalidArgumentException { 131 | DetailSchema detailSchema = DetailSchema.init().setSchema("test").setDetailId("ee") 132 | .setItemPair("item_key", "item_value") 133 | .setDistinctId("123") 134 | .addProperty("key1", "value1") 135 | .addProperty("key2", new Date()) 136 | .start(); 137 | sa.detailDelete(detailSchema); 138 | assertSchemaDataNode(data); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/schema/DetailSchema.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean.schema; 2 | 3 | import com.sensorsdata.analytics.javasdk.common.Pair; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 6 | 7 | import lombok.Getter; 8 | import lombok.NonNull; 9 | 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 明细数据入参 18 | * 19 | * @author fangzhuo 20 | * @version 1.0.0 21 | * @since 2023/03/21 11:19 22 | */ 23 | @Getter 24 | public class DetailSchema { 25 | /** 26 | * detail 对应的 schema 27 | *

必传参数,若不传入,则抛出 InvalidArgumentException 异常;

28 | */ 29 | private String schema; 30 | /** 31 | * detail 对应的 id 32 | *

必传参数,若不传入,则抛出 InvalidArgumentException 异常;

33 | */ 34 | private String detailId; 35 | /** 36 | * detail 的自定义属性集合 37 | *

非必传参数

38 | */ 39 | private Map properties; 40 | /** 41 | * 若传入该参数,该 detail 则被视为该用户实体的明细 42 | *

非必传参数,跟 itemId 节点互斥,若同时传入,则会抛出 InvalidArgumentException 异常;

43 | */ 44 | private Map identities; 45 | /** 46 | * 只有传入 identities 节点信息,再设置该节点才会生效,否则设置该值无效; 47 | *

非必传参数,不传此值,会取 identities 节点集合里第一个节点的用户信息作为 distinct_id

48 | */ 49 | private String distinctId; 50 | /** 51 | * 设置该参数,则该 detail 被视为 item 实体的明细 52 | *

非必传参数,跟 identities 节点互斥。若同时传入,则会抛出 InvalidArgumentException 异常;

53 | */ 54 | private Pair itemPair; 55 | 56 | private Integer trackId; 57 | 58 | 59 | protected DetailSchema(String schema, String detailId, Map properties, 60 | Map identities, String distinctId, Pair itemPair, Integer trackId) { 61 | this.schema = schema; 62 | this.detailId = detailId; 63 | this.properties = properties; 64 | this.identities = identities; 65 | this.distinctId = distinctId; 66 | this.itemPair = itemPair; 67 | this.trackId = trackId; 68 | } 69 | 70 | public static Builder init() { 71 | return new Builder(); 72 | } 73 | 74 | public static class Builder { 75 | private String schema; 76 | private String detailId; 77 | private Map properties = new HashMap<>(); 78 | private Map identities = new LinkedHashMap<>(); 79 | private String distinctId; 80 | private Pair itemPair; 81 | private Integer trackId; 82 | 83 | 84 | public DetailSchema start() throws InvalidArgumentException { 85 | SensorsAnalyticsUtil.assertSchema(schema); 86 | SensorsAnalyticsUtil.assertValue("detail_id", detailId); 87 | if (!identities.isEmpty() && null != itemPair) { 88 | throw new InvalidArgumentException("detail schema cannot both set identities and itemPair."); 89 | } 90 | this.trackId = SensorsAnalyticsUtil.getTrackId(properties, 91 | String.format("detail generate trackId error.[distinct_id=%s,detail_id=%s,schema=%s]", 92 | distinctId, detailId, schema)); 93 | if (!identities.isEmpty()) { 94 | this.distinctId = SensorsAnalyticsUtil.checkUserInfo(null, identities, distinctId); 95 | } 96 | SensorsAnalyticsUtil.assertSchemaProperties(properties, null); 97 | return new DetailSchema(schema, detailId, properties, identities, distinctId, itemPair, trackId); 98 | } 99 | 100 | public Builder setSchema(@NonNull String schema) { 101 | this.schema = schema; 102 | return this; 103 | } 104 | 105 | public Builder setDetailId(@NonNull String detailId) { 106 | this.detailId = detailId; 107 | return this; 108 | } 109 | 110 | public Builder setItemPair(@NonNull String key, @NonNull String value) { 111 | this.itemPair = Pair.of(key, value); 112 | return this; 113 | } 114 | 115 | public Builder addProperties(@NonNull Map properties) { 116 | this.properties.putAll(properties); 117 | return this; 118 | } 119 | 120 | public Builder addProperty(@NonNull String key, @NonNull String property) { 121 | addPropertyObject(key, property); 122 | return this; 123 | } 124 | 125 | public Builder addProperty(@NonNull String key, boolean property) { 126 | addPropertyObject(key, property); 127 | return this; 128 | } 129 | 130 | public Builder addProperty(@NonNull String key, @NonNull Number property) { 131 | addPropertyObject(key, property); 132 | return this; 133 | } 134 | 135 | public Builder addProperty(@NonNull String key, @NonNull Date property) { 136 | addPropertyObject(key, property); 137 | return this; 138 | } 139 | 140 | public Builder addProperty(@NonNull String key, @NonNull List property) { 141 | addPropertyObject(key, property); 142 | return this; 143 | } 144 | 145 | public Builder identityMap(@NonNull Map identities) { 146 | this.identities.putAll(identities); 147 | return this; 148 | } 149 | 150 | public Builder addIdentityProperty(@NonNull String key, @NonNull String value) { 151 | this.identities.put(key, value); 152 | return this; 153 | } 154 | 155 | public Builder setDistinctId(@NonNull String distinctId) { 156 | this.distinctId = distinctId; 157 | return this; 158 | } 159 | 160 | private void addPropertyObject(@NonNull String key, @NonNull Object property) { 161 | this.properties.put(key, property); 162 | } 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/ConcurrentLoggingConsumerTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import com.sensorsdata.analytics.javasdk.consumer.ConcurrentLoggingConsumer; 8 | import com.sensorsdata.analytics.javasdk.consumer.LogSplitMode; 9 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * ConcurrentLoggingConsumer 单测 20 | * 21 | * @author fangzhuo 22 | * @version 1.0.0 23 | * @since 2021/11/25 16:46 24 | */ 25 | public class ConcurrentLoggingConsumerTest { 26 | 27 | private ConcurrentLoggingConsumer consumer; 28 | 29 | private StringBuilder messageBuffer; 30 | 31 | @Before 32 | public void init() throws NoSuchFieldException, IllegalAccessException { 33 | consumer = new ConcurrentLoggingConsumer("file.log"); 34 | Field field = consumer.getClass().getSuperclass().getDeclaredField("messageBuffer"); 35 | field.setAccessible(true); 36 | messageBuffer = (StringBuilder) field.get(consumer); 37 | } 38 | 39 | @Test 40 | public void checkSendData() { 41 | assertEquals(0, messageBuffer.length()); 42 | Map event = new HashMap<>(); 43 | event.put("distinct_id", "12345"); 44 | event.put("event", "test"); 45 | event.put("type", "track"); 46 | consumer.send(event); 47 | assertNotNull(messageBuffer); 48 | messageBuffer.setLength(0); 49 | } 50 | 51 | @Test 52 | public void checkInit() { 53 | new ConcurrentLoggingConsumer("file.log"); 54 | new ConcurrentLoggingConsumer("file.log", 30); 55 | new ConcurrentLoggingConsumer("file.log", "lock.name", 20); 56 | new ConcurrentLoggingConsumer("file.log", "lock.name", 20, LogSplitMode.DAY); 57 | assertTrue(true); 58 | } 59 | 60 | @Test 61 | public void testInit01() throws InvalidArgumentException { 62 | consumer = new ConcurrentLoggingConsumer("test.log"); 63 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 64 | Map properties = new HashMap<>(); 65 | properties.put("test", "test"); 66 | properties.put("$project", "abc"); 67 | properties.put("$token", "123"); 68 | sa.track("123", true, "test", properties); 69 | } 70 | 71 | @Test 72 | public void testInit02() throws InvalidArgumentException { 73 | consumer = new ConcurrentLoggingConsumer("test.log", 100); 74 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 75 | Map properties = new HashMap<>(); 76 | properties.put("test", "test"); 77 | properties.put("$project", "abc"); 78 | properties.put("$token", "123"); 79 | sa.track("123", true, "test01", properties); 80 | sa.track("123", true, "test01", properties); 81 | } 82 | 83 | @Test 84 | public void testInit03() throws InvalidArgumentException { 85 | consumer = new ConcurrentLoggingConsumer("test.log", "lock.log"); 86 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 87 | Map properties = new HashMap<>(); 88 | properties.put("test", "test"); 89 | properties.put("$project", "abc"); 90 | properties.put("$token", "123"); 91 | sa.track("123", true, "test01", properties); 92 | sa.track("123", true, "test01", properties); 93 | } 94 | 95 | @Test 96 | public void testInit04() throws InvalidArgumentException { 97 | consumer = new ConcurrentLoggingConsumer("test.log", "lock.log", 100); 98 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 99 | Map properties = new HashMap<>(); 100 | properties.put("test", "test"); 101 | properties.put("$project", "abc"); 102 | properties.put("$token", "123"); 103 | sa.track("123", true, "test01", properties); 104 | sa.track("123", true, "test01", properties); 105 | } 106 | 107 | @Test 108 | public void testInit05() throws InvalidArgumentException { 109 | consumer = new ConcurrentLoggingConsumer("test.log", "lock.log", 100); 110 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 111 | Map properties = new HashMap<>(); 112 | properties.put("test", "test"); 113 | properties.put("$project", "abc"); 114 | properties.put("$token", "123"); 115 | sa.track("123", true, "test01", properties); 116 | sa.track("123", true, "test01", properties); 117 | } 118 | 119 | @Test 120 | public void testInit06() throws InvalidArgumentException { 121 | consumer = new ConcurrentLoggingConsumer("test.log", "lock.log", 100, LogSplitMode.DAY); 122 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 123 | Map properties = new HashMap<>(); 124 | properties.put("test", "test"); 125 | properties.put("$project", "abc"); 126 | properties.put("$token", "123"); 127 | sa.track("123", true, "test01", properties); 128 | sa.track("123", true, "test01", properties); 129 | } 130 | 131 | @Test 132 | public void testInit07() throws InvalidArgumentException { 133 | consumer = new ConcurrentLoggingConsumer("test.log", "lock.log", 100, LogSplitMode.HOUR); 134 | SensorsAnalytics sa = new SensorsAnalytics(consumer); 135 | Map properties = new HashMap<>(); 136 | properties.put("test", "test"); 137 | properties.put("$project", "abc"); 138 | properties.put("$token", "123"); 139 | sa.track("123", true, "test01", properties); 140 | sa.track("123", true, "test01", properties); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/bean/EventRecord.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.bean; 2 | 3 | import com.sensorsdata.analytics.javasdk.SensorsConst; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * 事件表信息实体对象 15 | */ 16 | public class EventRecord implements Serializable { 17 | private static final long serialVersionUID = -2327319579147636283L; 18 | 19 | private final Map propertyMap; 20 | 21 | private final String eventName; 22 | 23 | private final String distinctId; 24 | 25 | private final Boolean isLoginId; 26 | 27 | private final Integer trackId; 28 | 29 | private final String originalId; 30 | 31 | private EventRecord(String eventName, String distinctId, Boolean isLoginId, Map propertyMap, 32 | Integer trackId, String originalId) { 33 | this.eventName = eventName; 34 | this.distinctId = distinctId; 35 | this.isLoginId = isLoginId; 36 | if (isLoginId) { 37 | propertyMap.put(SensorsConst.LOGIN_SYSTEM_ATTR, true); 38 | } 39 | this.propertyMap = propertyMap; 40 | this.trackId = trackId; 41 | this.originalId = originalId; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "EventRecord{" + 47 | "propertyMap=" + propertyMap + 48 | ", eventName='" + eventName + '\'' + 49 | ", distinctId='" + distinctId + '\'' + 50 | ", isLoginId='" + isLoginId + '\'' + 51 | '}'; 52 | } 53 | 54 | public static Builder builder() { 55 | return new Builder(); 56 | } 57 | 58 | public Map getPropertyMap() { 59 | return propertyMap; 60 | } 61 | 62 | public String getEventName() { 63 | return eventName; 64 | } 65 | 66 | public String getDistinctId() { 67 | return distinctId; 68 | } 69 | 70 | public Boolean getIsLoginId() { 71 | return isLoginId; 72 | } 73 | 74 | public String getOriginalId() { 75 | return originalId; 76 | } 77 | 78 | public Integer getTrackId() {return trackId; } 79 | 80 | public static class Builder { 81 | private final Map propertyMap = new HashMap<>(); 82 | private String eventName; 83 | private String distinctId; 84 | private Boolean isLoginId; 85 | private Integer trackId; 86 | private String originalId; 87 | 88 | private Builder() { 89 | } 90 | public EventRecord build() throws InvalidArgumentException { 91 | 92 | if (eventName == null) { 93 | throw new InvalidArgumentException("The eventName is empty."); 94 | } 95 | if (distinctId == null) { 96 | throw new InvalidArgumentException("The distinctId is empty."); 97 | } 98 | if (isLoginId == null) { 99 | throw new InvalidArgumentException("The isLoginId is empty."); 100 | } 101 | SensorsAnalyticsUtil.assertKey("event_name",eventName); 102 | SensorsAnalyticsUtil.assertProperties("property", propertyMap); 103 | SensorsAnalyticsUtil.assertValue("distinct_id", distinctId); 104 | String message = String.format("[distinct_id=%s,event_name=%s,is_login_id=%s]",distinctId,eventName,isLoginId); 105 | trackId = SensorsAnalyticsUtil.getTrackId(propertyMap, message); 106 | return new EventRecord(eventName, distinctId, isLoginId, propertyMap,trackId, originalId); 107 | } 108 | 109 | public EventRecord.Builder setOriginalId(String originalId) { 110 | this.originalId = originalId; 111 | return this; 112 | } 113 | 114 | public EventRecord.Builder setEventName(String eventName) { 115 | this.eventName = eventName; 116 | return this; 117 | } 118 | 119 | public EventRecord.Builder setDistinctId(String distinctId) { 120 | this.distinctId = distinctId; 121 | return this; 122 | } 123 | 124 | public EventRecord.Builder isLoginId(Boolean loginId) { 125 | this.isLoginId = loginId; 126 | return this; 127 | } 128 | 129 | public EventRecord.Builder addProperties(Map properties) { 130 | if (properties != null) { 131 | propertyMap.putAll(properties); 132 | } 133 | return this; 134 | } 135 | 136 | public EventRecord.Builder addProperty(String key, String property) { 137 | addPropertyObject(key, property); 138 | return this; 139 | } 140 | 141 | public EventRecord.Builder addProperty(String key, boolean property) { 142 | addPropertyObject(key, property); 143 | return this; 144 | } 145 | 146 | public EventRecord.Builder addProperty(String key, Number property) { 147 | addPropertyObject(key, property); 148 | return this; 149 | } 150 | 151 | public EventRecord.Builder addProperty(String key, Date property) { 152 | addPropertyObject(key, property); 153 | return this; 154 | } 155 | 156 | public EventRecord.Builder addProperty(String key, List property) { 157 | addPropertyObject(key, property); 158 | return this; 159 | } 160 | 161 | private void addPropertyObject(String key, Object property) { 162 | if (key != null) { 163 | propertyMap.put(key, property); 164 | } 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/util/Base64Coder.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.util; 2 | 3 | //Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland 4 | //www.source-code.biz, www.inventec.ch/chdh 5 | // 6 | //This module is multi-licensed and may be used under the terms 7 | //of any of the following licenses: 8 | // 9 | //EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal 10 | //LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html 11 | //GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html 12 | //AL, Apache License, V2.0 or later, http://www.apache.org/licenses 13 | //BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php 14 | // 15 | //Please contact the author if you need another license. 16 | //This module is provided "as is", without warranties of any kind. 17 | // 18 | // This file has been modified from it's original version by Mixpanel, Inc 19 | 20 | 21 | public class Base64Coder { 22 | 23 | // Mapping table from 6-bit nibbles to Base64 characters. 24 | private static char[] map1 = new char[64]; 25 | // Mapping table from Base64 characters to 6-bit nibbles. 26 | private static byte[] map2 = new byte[128]; 27 | 28 | static { 29 | int i = 0; 30 | for (char c = 'A'; c <= 'Z'; c++) 31 | map1[i++] = c; 32 | for (char c = 'a'; c <= 'z'; c++) 33 | map1[i++] = c; 34 | for (char c = '0'; c <= '9'; c++) 35 | map1[i++] = c; 36 | map1[i++] = '+'; 37 | map1[i++] = '/'; 38 | } 39 | 40 | static { 41 | for (int i = 0; i < map2.length; i++) 42 | map2[i] = -1; 43 | for (int i = 0; i < 64; i++) 44 | map2[map1[i]] = (byte) i; 45 | } 46 | 47 | /** 48 | * Encodes a string into Base64 format. 49 | * No blanks or line breaks are inserted. 50 | * 51 | * @param s a String to be encoded. 52 | * @return A String with the Base64 encoded data. 53 | */ 54 | public static String encodeString(String s) { 55 | return new String(encode(s.getBytes())); 56 | } 57 | 58 | /** 59 | * Encodes a byte array into Base64 format. 60 | * No blanks or line breaks are inserted. 61 | * 62 | * @param in an array containing the data bytes to be encoded. 63 | * @return A character array with the Base64 encoded data. 64 | */ 65 | public static char[] encode(byte[] in) { 66 | return encode(in, in.length); 67 | } 68 | 69 | /** 70 | * Encodes a byte array into Base64 format. 71 | * No blanks or line breaks are inserted. 72 | * 73 | * @param in an array containing the data bytes to be encoded. 74 | * @param iLen number of bytes to process in in. 75 | * @return A character array with the Base64 encoded data. 76 | */ 77 | public static char[] encode(byte[] in, int iLen) { 78 | int oDataLen = (iLen * 4 + 2) / 3; // output length without padding 79 | int oLen = ((iLen + 2) / 3) * 4; // output length including padding 80 | char[] out = new char[oLen]; 81 | int ip = 0; 82 | int op = 0; 83 | while (ip < iLen) { 84 | int i0 = in[ip++] & 0xff; 85 | int i1 = ip < iLen ? in[ip++] & 0xff : 0; 86 | int i2 = ip < iLen ? in[ip++] & 0xff : 0; 87 | int o0 = i0 >>> 2; 88 | int o1 = ((i0 & 3) << 4) | (i1 >>> 4); 89 | int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); 90 | int o3 = i2 & 0x3F; 91 | out[op++] = map1[o0]; 92 | out[op++] = map1[o1]; 93 | out[op] = op < oDataLen ? map1[o2] : '='; 94 | op++; 95 | out[op] = op < oDataLen ? map1[o3] : '='; 96 | op++; 97 | } 98 | return out; 99 | } 100 | 101 | /** 102 | * Decodes a string from Base64 format. 103 | * 104 | * @param s a Base64 String to be decoded. 105 | * @return A String containing the decoded data. 106 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data. 107 | */ 108 | public static String decodeString(String s) { 109 | return new String(decode(s)); 110 | } 111 | 112 | /** 113 | * Decodes a byte array from Base64 format. 114 | * 115 | * @param s a Base64 String to be decoded. 116 | * @return An array containing the decoded data bytes. 117 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data. 118 | */ 119 | public static byte[] decode(String s) { 120 | return decode(s.toCharArray()); 121 | } 122 | 123 | /** 124 | * Decodes a byte array from Base64 format. 125 | * No blanks or line breaks are allowed within the Base64 encoded data. 126 | * 127 | * @param in a character array containing the Base64 encoded data. 128 | * @return An array containing the decoded data bytes. 129 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data. 130 | */ 131 | public static byte[] decode(char[] in) { 132 | int iLen = in.length; 133 | if (iLen % 4 != 0) 134 | throw new IllegalArgumentException( 135 | "Length of Base64 encoded input string is not a multiple of 4."); 136 | while (iLen > 0 && in[iLen - 1] == '=') 137 | iLen--; 138 | int oLen = (iLen * 3) / 4; 139 | byte[] out = new byte[oLen]; 140 | int ip = 0; 141 | int op = 0; 142 | while (ip < iLen) { 143 | int i0 = in[ip++]; 144 | int i1 = in[ip++]; 145 | int i2 = ip < iLen ? in[ip++] : 'A'; 146 | int i3 = ip < iLen ? in[ip++] : 'A'; 147 | if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) 148 | throw new IllegalArgumentException("Illegal character in Base64 encoded data."); 149 | int b0 = map2[i0]; 150 | int b1 = map2[i1]; 151 | int b2 = map2[i2]; 152 | int b3 = map2[i3]; 153 | if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) 154 | throw new IllegalArgumentException("Illegal character in Base64 encoded data."); 155 | int o0 = (b0 << 2) | (b1 >>> 4); 156 | int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); 157 | int o2 = ((b2 & 3) << 6) | b3; 158 | out[op++] = (byte) o0; 159 | if (op < oLen) 160 | out[op++] = (byte) o1; 161 | if (op < oLen) 162 | out[op++] = (byte) o2; 163 | } 164 | return out; 165 | } 166 | 167 | 168 | } // end class Base64Coder -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/SchemaUserEventTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 8 | import com.sensorsdata.analytics.javasdk.bean.schema.IdentitySchema; 9 | import com.sensorsdata.analytics.javasdk.bean.schema.UserEventSchema; 10 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 11 | 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | 15 | import java.util.Date; 16 | import java.util.Map; 17 | 18 | /** 19 | * userEventSchema 单元测试 20 | * 21 | * @author fangzhuo 22 | * @version 1.0.0 23 | * @since 2022/06/15 10:23 24 | */ 25 | public class SchemaUserEventTest extends SensorsBaseTest { 26 | 27 | private static final Long USER_ID = 12345L; 28 | 29 | private static final String EVENT_NAME = "testEvent"; 30 | 31 | private static final String DISTINCT_ID = "fz123"; 32 | 33 | /** 34 | * 使用 userEventSchema 生成带 userId 数据 35 | * 期望:生成数据格式中用户信息节点为 userId 36 | */ 37 | @Test 38 | public void checkUserEventSchemaWithUserId() throws InvalidArgumentException { 39 | UserEventSchema userEventSchema = UserEventSchema.init() 40 | .setUserId(USER_ID) 41 | .setEventName(EVENT_NAME) 42 | .addProperty("key1", "value1") 43 | .addProperty("key2", 22) 44 | .addProperty("$time", new Date()) 45 | .start(); 46 | sa.track(userEventSchema); 47 | assertUESData(data); 48 | } 49 | 50 | /** 51 | * 构建携带 identities 的用户信息,distinct_id 值与 IDM3.0 生成逻辑保持一致 52 | * 期望:生成数据中 properties 内有 identities + distinct_id 节点;并且 distinct_id 的值为 $identity_login_id 的值 53 | */ 54 | @Test 55 | public void checkUserEventSchemaWithIdentities() throws InvalidArgumentException { 56 | UserEventSchema userEventSchema = UserEventSchema.init() 57 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, DISTINCT_ID) 58 | .setEventName(EVENT_NAME) 59 | .start(); 60 | sa.track(userEventSchema); 61 | assertUESData(data); 62 | assertEventIdentitiesInfo(data, DISTINCT_ID); 63 | } 64 | 65 | /** 66 | * 校验用户绑定事件 67 | */ 68 | @Test 69 | public void checkSchemaBind() throws InvalidArgumentException { 70 | IdentitySchema identitySchema = IdentitySchema.init().build(); 71 | try { 72 | sa.bind(identitySchema); 73 | fail("生成异常数据"); 74 | } catch (InvalidArgumentException e) { 75 | assertTrue(e.getMessage().contains("The identities is invalid,you should have at least two identities.")); 76 | } 77 | 78 | IdentitySchema oneIdentity = IdentitySchema.init() 79 | .addIdentityProperty("key1", "value1") 80 | .build(); 81 | try { 82 | sa.bind(oneIdentity); 83 | fail("生成异常数据"); 84 | } catch (InvalidArgumentException e) { 85 | assertTrue(e.getMessage().contains("The identities is invalid,you should have at least two identities.")); 86 | } 87 | 88 | IdentitySchema moreIdentity = IdentitySchema.init() 89 | .addIdentityProperty("key1", "value1") 90 | .addIdentityProperty("key2", "value2") 91 | .build(); 92 | sa.bind(moreIdentity); 93 | assertUESData(data); 94 | } 95 | 96 | @Test 97 | public void checkSchemaUnbind() throws InvalidArgumentException { 98 | IdentitySchema identitySchema = IdentitySchema.init().build(); 99 | try { 100 | sa.unbind(identitySchema); 101 | fail("生成异常数据"); 102 | } catch (InvalidArgumentException e) { 103 | assertTrue(e.getMessage().contains("unbind user operation cannot input multiple or none identifiers")); 104 | } 105 | 106 | IdentitySchema moreIdentity = IdentitySchema.init() 107 | .addIdentityProperty("key1", "value1") 108 | .addIdentityProperty("key2", "value2") 109 | .build(); 110 | 111 | try { 112 | sa.unbind(moreIdentity); 113 | fail("生成异常数据"); 114 | } catch (InvalidArgumentException e) { 115 | assertTrue(e.getMessage().contains("unbind user operation cannot input multiple or none identifiers")); 116 | } 117 | 118 | 119 | IdentitySchema oneIdentity = IdentitySchema.init() 120 | .addIdentityProperty("key1", "value1") 121 | .build(); 122 | sa.unbind(oneIdentity); 123 | assertUESData(data); 124 | 125 | 126 | } 127 | 128 | 129 | //----------------------------------v3.5.2-------------------------------------- 130 | 131 | /** 132 | * 支持 user 数据传入 userId 作为用户标识 133 | *

期望:用户数据传入 userId,最终节点中包含 userId 和 distinctId 信息

134 | */ 135 | @Test 136 | public void checkUserId() throws InvalidArgumentException { 137 | UserEventSchema userEventSchema = UserEventSchema.init() 138 | .setUserId(123L) 139 | .setEventName(EVENT_NAME) 140 | .start(); 141 | sa.track(userEventSchema); 142 | assertUESData(data); 143 | } 144 | 145 | /** 146 | * 同时传入 userID 和 identities 节点 147 | *

期望:userId 优先级最高,两者同时传入,最终数据中存在 userId

148 | */ 149 | @Test 150 | public void checkUserIdAndIdentities() throws InvalidArgumentException { 151 | UserEventSchema userEventSchema = UserEventSchema.init() 152 | .setUserId(123L) 153 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, "eee") 154 | .setEventName(EVENT_NAME) 155 | .start(); 156 | sa.track(userEventSchema); 157 | assertUESData(data); 158 | Map properties = (Map) data.get("properties"); 159 | assertTrue(properties.containsKey("user_id")); 160 | assertEquals(123L, properties.get("user_id")); 161 | } 162 | 163 | /** 164 | * 同时传入 userID 和 distinctId 节点 165 | *

期望:最终数据节点中,以传入的 distinctId 为主

166 | */ 167 | @Test 168 | public void checkUserIdAndDistinctId() throws InvalidArgumentException { 169 | UserEventSchema userEventSchema = UserEventSchema.init() 170 | .setUserId(123L) 171 | .setDistinctId("test") 172 | .setEventName(EVENT_NAME) 173 | .start(); 174 | sa.track(userEventSchema); 175 | assertUESData(data); 176 | Map properties = (Map) data.get("properties"); 177 | Assert.assertTrue(properties.containsKey("distinct_id")); 178 | Assert.assertEquals("test", properties.get("distinct_id")); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/ConcurrentLoggingConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.FileNotFoundException; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.nio.channels.FileChannel; 9 | import java.nio.channels.FileLock; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @Slf4j 15 | public class ConcurrentLoggingConsumer extends InnerLoggingConsumer { 16 | 17 | public ConcurrentLoggingConsumer(final String filenamePrefix) { 18 | this(filenamePrefix, null); 19 | } 20 | 21 | public ConcurrentLoggingConsumer(final String filenamePrefix, int bufferSize) { 22 | this(filenamePrefix, null, bufferSize); 23 | } 24 | 25 | public ConcurrentLoggingConsumer(final String filenamePrefix, final String lockFileName) { 26 | this(filenamePrefix, lockFileName, 8192); 27 | } 28 | 29 | public ConcurrentLoggingConsumer( 30 | String filenamePrefix, 31 | String lockFileName, 32 | int bufferSize) { 33 | this(filenamePrefix, lockFileName, bufferSize, LogSplitMode.DAY); 34 | } 35 | 36 | public ConcurrentLoggingConsumer( 37 | String filenamePrefix, 38 | String lockFileName, 39 | int bufferSize, 40 | LogSplitMode splitMode) { 41 | super(new InnerLoggingFileWriterFactory(lockFileName), filenamePrefix, bufferSize, splitMode); 42 | } 43 | 44 | static class InnerLoggingFileWriterFactory implements LoggingFileWriterFactory { 45 | 46 | private String lockFileName; 47 | 48 | InnerLoggingFileWriterFactory(String lockFileName) { 49 | this.lockFileName = lockFileName; 50 | } 51 | 52 | @Override 53 | public LoggingFileWriter getFileWriter(String fileName, String scheduleFileName) 54 | throws FileNotFoundException { 55 | return InnerLoggingFileWriter.getInstance(scheduleFileName, lockFileName); 56 | } 57 | 58 | @Override 59 | public void closeFileWriter(LoggingFileWriter writer) { 60 | ConcurrentLoggingConsumer.InnerLoggingFileWriter 61 | .removeInstance((ConcurrentLoggingConsumer.InnerLoggingFileWriter) writer); 62 | } 63 | } 64 | 65 | static class InnerLoggingFileWriter implements LoggingFileWriter { 66 | private final Object fileLock = new Object(); 67 | private final String fileName; 68 | private final String lockFileName; 69 | private FileOutputStream outputStream; 70 | private FileOutputStream lockStream; 71 | private int refCount; 72 | 73 | private static final Map instances; 74 | 75 | static { 76 | instances = new HashMap<>(); 77 | } 78 | 79 | static InnerLoggingFileWriter getInstance(final String fileName, final String lockFileName) throws FileNotFoundException { 80 | synchronized (instances) { 81 | if (!instances.containsKey(fileName)) { 82 | instances.put(fileName, new InnerLoggingFileWriter(fileName, lockFileName)); 83 | } 84 | 85 | InnerLoggingFileWriter writer = instances.get(fileName); 86 | writer.refCount = writer.refCount + 1; 87 | return writer; 88 | } 89 | } 90 | 91 | static void removeInstance(final InnerLoggingFileWriter writer) { 92 | synchronized (instances) { 93 | writer.refCount = writer.refCount - 1; 94 | if (writer.refCount == 0) { 95 | writer.close(); 96 | instances.remove(writer.fileName); 97 | } 98 | } 99 | } 100 | 101 | private InnerLoggingFileWriter(final String fileName, final String lockFileName) throws FileNotFoundException { 102 | this.fileName = fileName; 103 | this.lockFileName = lockFileName; 104 | this.refCount = 0; 105 | initLock(); 106 | } 107 | 108 | public void close() { 109 | try { 110 | outputStream.close(); 111 | } catch (Exception e) { 112 | log.error("Failed to close output stream.", e); 113 | throw new RuntimeException("fail to close output stream.", e); 114 | } 115 | } 116 | 117 | public boolean isValid(final String fileName) { 118 | return this.fileName.equals(fileName); 119 | } 120 | 121 | public boolean write(final StringBuilder sb) { 122 | synchronized (fileLock) { 123 | FileLock lock = null; 124 | try { 125 | final FileChannel channel = lockStream.getChannel(); 126 | if (!channel.isOpen()) { 127 | lockStream.close(); 128 | outputStream.close(); 129 | initLock(); 130 | } 131 | lock = channel.lock(0, Long.MAX_VALUE, false); 132 | outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8)); 133 | } catch (Exception e) { 134 | log.error("Failed to write file.", e); 135 | throw new RuntimeException("fail to write file.", e); 136 | } finally { 137 | if (lock != null) { 138 | try { 139 | lock.release(); 140 | } catch (IOException e) { 141 | log.error("Failed to release file lock.", e); 142 | throw new RuntimeException("fail to release file lock.", e); 143 | } 144 | } 145 | } 146 | } 147 | 148 | return true; 149 | } 150 | 151 | private void initLock() throws FileNotFoundException { 152 | this.outputStream = new FileOutputStream(fileName, true); 153 | if (lockFileName != null) { 154 | this.lockStream = new FileOutputStream(lockFileName, true); 155 | } else { 156 | this.lockStream = this.outputStream; 157 | } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/SensorsSchemaData.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static com.sensorsdata.analytics.javasdk.SensorsConst.BIND_ID_ACTION_TYPE; 4 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_ACTION_TYPE; 5 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_SIGN_UP_ACTION_TYPE; 6 | import static com.sensorsdata.analytics.javasdk.SensorsConst.UNBIND_ID_ACTION_TYPE; 7 | 8 | import com.sensorsdata.analytics.javasdk.bean.schema.DetailSchema; 9 | import com.sensorsdata.analytics.javasdk.bean.schema.ItemEventSchema; 10 | import com.sensorsdata.analytics.javasdk.bean.schema.ItemSchema; 11 | import com.sensorsdata.analytics.javasdk.bean.schema.UserEventSchema; 12 | import com.sensorsdata.analytics.javasdk.bean.schema.UserSchema; 13 | import com.sensorsdata.analytics.javasdk.common.Pair; 14 | import com.sensorsdata.analytics.javasdk.common.SchemaTypeEnum; 15 | 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * 神策多实体数据 23 | * 24 | * @author fangzhuo 25 | * @version 1.0.0 26 | * @since 2022/06/14 14:44 27 | */ 28 | @Slf4j 29 | class SensorsSchemaData extends SensorsData { 30 | 31 | private String version = SensorsConst.PROTOCOL_VERSION; 32 | 33 | private String schema; 34 | 35 | private Long userId; 36 | 37 | private String detailId; 38 | 39 | private Pair itemEventPair; 40 | 41 | private SchemaTypeEnum schemaTypeEnum; 42 | 43 | /** 44 | * 构建 userEventSchema 数据,actionType:track/bind/unbind 45 | */ 46 | protected SensorsSchemaData(UserEventSchema userEventSchema, String actionType) { 47 | super(userEventSchema.getDistinctId(), null, userEventSchema.getIdentityMap(), actionType, 48 | userEventSchema.getEventName(), userEventSchema.getPropertyMap(), null, null, userEventSchema.getTrackId()); 49 | this.schema = SensorsConst.USER_EVENT_SCHEMA; 50 | this.userId = userEventSchema.getUserId(); 51 | this.schemaTypeEnum = SchemaTypeEnum.USER_EVENT; 52 | } 53 | 54 | protected SensorsSchemaData(ItemSchema itemSchema, String actionType) { 55 | super(itemSchema.getTrackId(), null, null, itemSchema.getItemId(), actionType, null, itemSchema.getProperties()); 56 | this.schema = itemSchema.getSchema(); 57 | this.schemaTypeEnum = SchemaTypeEnum.ITEM; 58 | } 59 | 60 | protected SensorsSchemaData(ItemEventSchema itemEventSchema, String actionType) { 61 | super(itemEventSchema.getTrackId(), null, null, null, actionType, 62 | itemEventSchema.getEventName(), 63 | itemEventSchema.getProperties()); 64 | this.schema = itemEventSchema.getSchema(); 65 | this.itemEventPair = itemEventSchema.getItemPair(); 66 | this.schemaTypeEnum = SchemaTypeEnum.ITEM_EVENT; 67 | } 68 | 69 | protected SensorsSchemaData(UserSchema userSchema, String actionType) { 70 | super(userSchema.getDistinctId(), actionType, userSchema.getIdentityMap(), userSchema.getPropertyMap(), 71 | userSchema.getTrackId()); 72 | this.schema = SensorsConst.USER_SCHEMA; 73 | this.userId = userSchema.getUserId(); 74 | this.schemaTypeEnum = SchemaTypeEnum.USER; 75 | } 76 | 77 | protected SensorsSchemaData(DetailSchema detailSchema, String actionType) { 78 | super(detailSchema.getTrackId(), detailSchema.getDistinctId(), detailSchema.getIdentities(), 79 | null, actionType, null, 80 | detailSchema.getProperties()); 81 | this.schema = detailSchema.getSchema(); 82 | this.itemEventPair = detailSchema.getItemPair(); 83 | this.detailId = detailSchema.getDetailId(); 84 | this.schemaTypeEnum = SchemaTypeEnum.DETAIL; 85 | } 86 | 87 | public Map generateData() { 88 | Map data = new HashMap<>(); 89 | data.put("_track_id", getTrackId()); 90 | data.put("version", version); 91 | data.put("type", getType()); 92 | data.put("schema", schema); 93 | data.put("lib", getLib()); 94 | data.put("time", getTime().getTime()); 95 | if (getProject() != null && !"".equals(getProject())) { 96 | data.put("project", getProject()); 97 | } 98 | if (getToken() != null && !"".equals(getToken())) { 99 | data.put("token", getToken()); 100 | } 101 | switch (schemaTypeEnum) { 102 | case ITEM: 103 | data.put("id", getItemId()); 104 | break; 105 | case ITEM_EVENT: 106 | getProperties().put(itemEventPair.getKey(), itemEventPair.getValue()); 107 | addTimeFree(data); 108 | data.put("event", getEvent()); 109 | break; 110 | case USER: 111 | checkUserIdAndAddUser(data, "id"); 112 | break; 113 | case USER_EVENT: 114 | addTimeFree(data); 115 | data.put("event", getEvent()); 116 | checkUserIdAndAddUser(getProperties(), "user_id"); 117 | break; 118 | case USER_ITEM: 119 | data.put("id", getItemId()); 120 | checkUserIdAndAddUser(getProperties(), "user_id"); 121 | break; 122 | case DETAIL: 123 | data.put("id", detailId); 124 | if (itemEventPair != null) { 125 | getProperties().put(itemEventPair.getKey(), itemEventPair.getValue()); 126 | } 127 | if (!getIdentities().isEmpty()) { 128 | checkUserIdAndAddUser(getProperties(), "user_id"); 129 | } 130 | default: 131 | break; 132 | } 133 | data.put("properties", getProperties()); 134 | return data; 135 | } 136 | 137 | public boolean isEventSchemaData() { 138 | return SchemaTypeEnum.ITEM_EVENT.equals(schemaTypeEnum) 139 | || SchemaTypeEnum.USER_EVENT.equals(schemaTypeEnum); 140 | } 141 | 142 | 143 | public String getVersion() { 144 | return version; 145 | } 146 | 147 | public String getSchema() { 148 | return schema; 149 | } 150 | 151 | public Long getUserId() { 152 | return userId; 153 | } 154 | 155 | public SchemaTypeEnum getSchemaTypeEnum() { 156 | return schemaTypeEnum; 157 | } 158 | 159 | private void addTimeFree(Map data) { 160 | if (isTimeFree() && (TRACK_ACTION_TYPE.equals(getType()) 161 | || TRACK_SIGN_UP_ACTION_TYPE.equals(getType()) 162 | || BIND_ID_ACTION_TYPE.equals(getType()) 163 | || UNBIND_ID_ACTION_TYPE.equals(getType()))) { 164 | data.put("time_free", true); 165 | } 166 | } 167 | 168 | private void checkUserIdAndAddUser(Map data, String key) { 169 | if (null != getUserId()) { 170 | data.put(key, getUserId()); 171 | } else if (null != getIdentities() && !getIdentities().isEmpty()) { 172 | data.put("identities", getIdentities()); 173 | } 174 | if (null != getDistinctId()) { 175 | data.put("distinct_id", getDistinctId()); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/HttpConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.SensorsConst; 4 | import com.sensorsdata.analytics.javasdk.util.Base64Coder; 5 | 6 | import org.apache.http.NameValuePair; 7 | import org.apache.http.client.config.RequestConfig; 8 | import org.apache.http.client.entity.UrlEncodedFormEntity; 9 | import org.apache.http.client.methods.CloseableHttpResponse; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.client.methods.HttpUriRequest; 12 | import org.apache.http.impl.client.CloseableHttpClient; 13 | import org.apache.http.impl.client.HttpClientBuilder; 14 | import org.apache.http.impl.client.HttpClients; 15 | import org.apache.http.message.BasicNameValuePair; 16 | import org.apache.http.util.EntityUtils; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | import java.nio.charset.Charset; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.zip.GZIPOutputStream; 27 | 28 | class HttpConsumer implements Closeable { 29 | CloseableHttpClient httpClient; 30 | final String serverUrl; 31 | final Map httpHeaders; 32 | final boolean compressData; 33 | final RequestConfig requestConfig; 34 | 35 | public HttpConsumer(String serverUrl, int timeoutSec) { 36 | this(HttpClients.custom(), serverUrl, null, timeoutSec); 37 | } 38 | 39 | public HttpConsumer(String serverUrl, Map httpHeaders) { 40 | this(HttpClients.custom(), serverUrl, httpHeaders, 3); 41 | } 42 | 43 | HttpConsumer(String serverUrl, Map httpHeaders, int timeoutSec) { 44 | this(HttpClients.custom(), serverUrl, httpHeaders, timeoutSec); 45 | } 46 | 47 | public HttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, Map httpHeaders) { 48 | this(httpClientBuilder, serverUrl, httpHeaders, 3); 49 | } 50 | 51 | public HttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, int timeoutSec) { 52 | this(httpClientBuilder, serverUrl, null, timeoutSec); 53 | } 54 | 55 | HttpConsumer(HttpClientBuilder httpClientBuilder, String serverUrl, Map httpHeaders, int timeoutSec) { 56 | this.serverUrl = serverUrl.trim(); 57 | this.httpHeaders = httpHeaders; 58 | this.compressData = true; 59 | int timeout = timeoutSec * 1000; 60 | this.requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeout) 61 | .setConnectTimeout(timeout).setSocketTimeout(timeout).build(); 62 | this.httpClient = httpClientBuilder 63 | .setUserAgent(String.format("SensorsAnalytics Java SDK %s", SensorsConst.SDK_VERSION)) 64 | .setDefaultRequestConfig(requestConfig) 65 | .build(); 66 | } 67 | 68 | void consume(final String data) throws IOException, HttpConsumerException { 69 | HttpUriRequest request = getHttpRequest(data); 70 | CloseableHttpResponse response = null; 71 | if (httpClient == null) { 72 | httpClient = HttpClients.custom() 73 | .setUserAgent(String.format("SensorsAnalytics Java SDK %s", SensorsConst.SDK_VERSION)) 74 | .setDefaultRequestConfig(requestConfig) 75 | .build(); 76 | } 77 | try { 78 | response = httpClient.execute(request); 79 | int httpStatusCode = response.getStatusLine().getStatusCode(); 80 | if (httpStatusCode < 200 || httpStatusCode >= 300) { 81 | String httpContent = new String(EntityUtils.toByteArray(response.getEntity()), StandardCharsets.UTF_8); 82 | throw new HttpConsumerException( 83 | String.format("Unexpected response %d from Sensors Analytics: %s", httpStatusCode, httpContent), data, 84 | httpStatusCode, httpContent); 85 | } 86 | } finally { 87 | if (response != null) { 88 | response.close(); 89 | } 90 | } 91 | } 92 | 93 | HttpUriRequest getHttpRequest(final String data) throws IOException { 94 | HttpPost httpPost = new HttpPost(this.serverUrl); 95 | httpPost.setEntity(getHttpEntry(data)); 96 | 97 | if (this.httpHeaders != null) { 98 | for (Map.Entry entry : this.httpHeaders.entrySet()) { 99 | httpPost.addHeader(entry.getKey(), entry.getValue()); 100 | } 101 | } 102 | 103 | return httpPost; 104 | } 105 | 106 | UrlEncodedFormEntity getHttpEntry(final String data) throws IOException { 107 | List nameValuePairs = getNameValuePairs(data); 108 | return new UrlEncodedFormEntity(nameValuePairs); 109 | } 110 | 111 | List getNameValuePairs(String data) throws IOException { 112 | 113 | byte[] bytes = data.getBytes(Charset.forName("UTF-8")); 114 | 115 | List nameValuePairs = new ArrayList(); 116 | 117 | if (compressData) { 118 | ByteArrayOutputStream os = new ByteArrayOutputStream(bytes.length); 119 | GZIPOutputStream gos = new GZIPOutputStream(os); 120 | gos.write(bytes); 121 | gos.close(); 122 | byte[] compressed = os.toByteArray(); 123 | os.close(); 124 | 125 | nameValuePairs.add(new BasicNameValuePair("gzip", "1")); 126 | nameValuePairs.add(new BasicNameValuePair("data_list", new String(Base64Coder.encode 127 | (compressed)))); 128 | } else { 129 | nameValuePairs.add(new BasicNameValuePair("gzip", "0")); 130 | nameValuePairs.add(new BasicNameValuePair("data_list", new String(Base64Coder.encode 131 | (bytes)))); 132 | } 133 | 134 | return nameValuePairs; 135 | } 136 | 137 | @Override 138 | public synchronized void close() { 139 | try { 140 | if (httpClient != null) { 141 | httpClient.close(); 142 | httpClient = null; 143 | } 144 | } catch (IOException ignored) { 145 | // do nothing 146 | } 147 | } 148 | 149 | static class HttpConsumerException extends Exception { 150 | 151 | HttpConsumerException(String error, String sendingData, int httpStatusCode, String 152 | httpContent) { 153 | super(error); 154 | this.sendingData = sendingData; 155 | this.httpStatusCode = httpStatusCode; 156 | this.httpContent = httpContent; 157 | } 158 | 159 | String getSendingData() { 160 | return sendingData; 161 | } 162 | 163 | int getHttpStatusCode() { 164 | return httpStatusCode; 165 | } 166 | 167 | String getHttpContent() { 168 | return httpContent; 169 | } 170 | 171 | final String sendingData; 172 | final int httpStatusCode; 173 | final String httpContent; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/InstantEventTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 4 | import com.sensorsdata.analytics.javasdk.bean.FailedData; 5 | import com.sensorsdata.analytics.javasdk.bean.schema.ItemEventSchema; 6 | import com.sensorsdata.analytics.javasdk.consumer.BatchConsumer; 7 | import com.sensorsdata.analytics.javasdk.consumer.Callback; 8 | import com.sensorsdata.analytics.javasdk.consumer.FastBatchConsumer; 9 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 10 | 11 | import lombok.NonNull; 12 | import org.apache.http.impl.client.HttpClientBuilder; 13 | import org.apache.http.impl.client.HttpClients; 14 | import org.eclipse.jetty.client.HttpClient; 15 | import org.junit.Assert; 16 | import org.junit.Test; 17 | 18 | import java.util.Arrays; 19 | 20 | public class InstantEventTest extends SensorsBaseTest { 21 | 22 | private static final String SCHEMA = "product_event"; 23 | 24 | private static final String EVENT_NAME = "cart"; 25 | public void initBatchConsumer() { 26 | BatchConsumer bc = new BatchConsumer("http://localhost:8888/instant", 50, 0, true, 3, Arrays.asList("test1", "test4")); 27 | sa = new SensorsAnalytics(bc); 28 | data = null; 29 | res.clear(); 30 | } 31 | 32 | @Test 33 | public void checkBatchConsumer() throws InvalidArgumentException { 34 | initBatchConsumer(); 35 | EventRecord event1 = EventRecord.builder().setEventName("test1").setDistinctId("1234").isLoginId(false).build(); 36 | EventRecord event2 = EventRecord.builder().setEventName("test2").setDistinctId("1234").isLoginId(false).build(); 37 | EventRecord event3 = EventRecord.builder().setEventName("test3").setDistinctId("1234").isLoginId(false).build(); 38 | EventRecord event4 = EventRecord.builder().setEventName("test4").setDistinctId("1234").isLoginId(false).build(); 39 | EventRecord event5 = EventRecord.builder().setEventName("test5").setDistinctId("1234").isLoginId(false).build(); 40 | 41 | sa.track(event1); 42 | 43 | sa.flush(); 44 | sa.track(event2); 45 | sa.track(event3); 46 | sa.track(event4); 47 | sa.track(event5); 48 | sa.flush(); 49 | 50 | } 51 | 52 | public void initFastBatchConsumer() { 53 | FastBatchConsumer bc = new FastBatchConsumer(HttpClients.custom(), "http://localhost:8888/instant", 54 | false , 50, 0, 3, 3, new Callback() { 55 | @Override 56 | public void onFailed(FailedData failedData) { 57 | SensorsLogsUtil.setFailedData(failedData); 58 | } 59 | }, Arrays.asList("test1", "test4")); 60 | sa = new SensorsAnalytics(bc); 61 | data = null; 62 | res.clear(); 63 | } 64 | 65 | @Test 66 | public void checkFastBatchConsumer() throws InvalidArgumentException { 67 | initFastBatchConsumer(); 68 | EventRecord event1 = EventRecord.builder().setEventName("test1").setDistinctId("1234").isLoginId(false).build(); 69 | EventRecord event2 = EventRecord.builder().setEventName("test2").setDistinctId("1234").isLoginId(false).build(); 70 | EventRecord event3 = EventRecord.builder().setEventName("test3").setDistinctId("1234").isLoginId(false).build(); 71 | EventRecord event4 = EventRecord.builder().setEventName("test4").setDistinctId("1234").isLoginId(false).build(); 72 | EventRecord event5 = EventRecord.builder().setEventName("test5").setDistinctId("1234").isLoginId(false).build(); 73 | 74 | sa.track(event1); 75 | 76 | sa.flush(); 77 | sa.track(event2); 78 | sa.track(event3); 79 | sa.track(event4); 80 | sa.track(event5); 81 | sa.flush(); 82 | 83 | } 84 | // 85 | // public void initRemoteBatchConsumer() { 86 | // FastBatchConsumer bc = new FastBatchConsumer(HttpClients.custom(), "http://10.120.213.104:8106/sa?project=default", 87 | // false , 50, 0, 3, 3, new Callback() { 88 | // @Override 89 | // public void onFailed(FailedData failedData) { 90 | // SensorsLogsUtil.setFailedData(failedData); 91 | // } 92 | // }, Arrays.asList("JavaSDKInstantEvent")); 93 | // sa = new SensorsAnalytics(bc); 94 | // data = null; 95 | // res.clear(); 96 | // } 97 | // 98 | // @Test 99 | // public void checkRemoteBatchConsumer() throws InvalidArgumentException { 100 | // initRemoteBatchConsumer(); 101 | // EventRecord event1 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 102 | // EventRecord event2 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 103 | // EventRecord event3 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 104 | // EventRecord event4 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 105 | // EventRecord event5 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 106 | // 107 | // sa.track(event1); 108 | // 109 | // sa.flush(); 110 | // sa.track(event2); 111 | // sa.track(event3); 112 | // sa.track(event4); 113 | // sa.track(event5); 114 | // sa.flush(); 115 | // 116 | // } 117 | // 118 | // public void initRemoteFastBatchConsumer() { 119 | // FastBatchConsumer bc = new FastBatchConsumer(HttpClients.custom(), "http://10.120.213.104:8106/sa?project=default", 120 | // false , 50, 0, 3, 3, new Callback() { 121 | // @Override 122 | // public void onFailed(FailedData failedData) { 123 | // SensorsLogsUtil.setFailedData(failedData); 124 | // } 125 | // }, Arrays.asList("JavaSDKInstantEvent")); 126 | // sa = new SensorsAnalytics(bc); 127 | // data = null; 128 | // res.clear(); 129 | // } 130 | // 131 | // @Test 132 | // public void checkRemoteFastBatchConsumer() throws InvalidArgumentException { 133 | // initRemoteFastBatchConsumer(); 134 | // EventRecord event1 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 135 | // EventRecord event2 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 136 | // EventRecord event3 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 137 | // EventRecord event4 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 138 | // EventRecord event5 = EventRecord.builder().setEventName("JavaSDKInstantEvent").setDistinctId("o826p5zjQny5wKCAxRWss9bzWmlI").addProperty("myname", "qinglintest").isLoginId(false).build(); 139 | // 140 | // sa.track(event1); 141 | // 142 | // sa.flush(); 143 | // sa.track(event2); 144 | // sa.track(event3); 145 | // sa.track(event4); 146 | // sa.track(event5); 147 | // sa.flush(); 148 | // 149 | // } 150 | } 151 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/SensorsData.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static com.sensorsdata.analytics.javasdk.SensorsConst.BIND_ID_ACTION_TYPE; 4 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_ACTION_TYPE; 5 | import static com.sensorsdata.analytics.javasdk.SensorsConst.TRACK_SIGN_UP_ACTION_TYPE; 6 | import static com.sensorsdata.analytics.javasdk.SensorsConst.UNBIND_ID_ACTION_TYPE; 7 | 8 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 9 | import com.sensorsdata.analytics.javasdk.bean.IDMEventRecord; 10 | import com.sensorsdata.analytics.javasdk.bean.IDMUserRecord; 11 | import com.sensorsdata.analytics.javasdk.bean.ItemRecord; 12 | import com.sensorsdata.analytics.javasdk.bean.UserRecord; 13 | 14 | import lombok.Getter; 15 | import lombok.Setter; 16 | 17 | import java.util.Date; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * 神策数据格式(IDM2.0 和 IDM3.0 格式的数据) 23 | * 24 | * @author fangzhuo 25 | * @version 1.0.0 26 | * @since 2022/03/09 16:16 27 | */ 28 | @Getter 29 | @Setter 30 | class SensorsData { 31 | /** 32 | * 数据唯一标识 33 | */ 34 | private Integer trackId; 35 | /** 36 | * 数据用户唯一标识 37 | */ 38 | private String distinctId; 39 | /** 40 | * 数据用户匿名标识 41 | */ 42 | private String originalId; 43 | /** 44 | * IDM3.0 用户维度标识 45 | */ 46 | private Map identities; 47 | /** 48 | * 事件类型 49 | */ 50 | private String type; 51 | /** 52 | * 事件名称 53 | */ 54 | private String event; 55 | /** 56 | * 数据采集来源 57 | */ 58 | private Map lib; 59 | /** 60 | * 记录生成时间 61 | */ 62 | private Date time; 63 | /** 64 | * 数据属性集合 65 | */ 66 | private Map properties; 67 | /** 68 | * 数据接收项目 69 | */ 70 | private String project; 71 | /** 72 | * 数据 token 73 | */ 74 | private String token; 75 | /** 76 | * 纬度类型 77 | */ 78 | private String itemType; 79 | /** 80 | * 纬度 ID 81 | */ 82 | private String itemId; 83 | 84 | private boolean timeFree = false; 85 | 86 | protected SensorsData() { 87 | } 88 | 89 | protected SensorsData(EventRecord eventRecord, String actionType) { 90 | this(eventRecord.getDistinctId(), eventRecord.getOriginalId(), null, actionType, eventRecord.getEventName(), 91 | eventRecord.getPropertyMap(), null, null, eventRecord.getTrackId()); 92 | } 93 | 94 | protected SensorsData(ItemRecord itemRecord, String actionType) { 95 | this(null, null, null, actionType, null, itemRecord.getPropertyMap(), 96 | itemRecord.getItemType(), itemRecord.getItemId(), itemRecord.getTrackId()); 97 | } 98 | 99 | protected SensorsData(UserRecord userRecord, String actionType) { 100 | this(userRecord.getDistinctId(), actionType, null, userRecord.getPropertyMap(), userRecord.getTrackId()); 101 | } 102 | 103 | protected SensorsData(T userRecord, String actionType) { 104 | this(userRecord.getDistinctId(), actionType, userRecord.getIdentityMap(), userRecord.getPropertyMap(), 105 | userRecord.getTrackId()); 106 | } 107 | 108 | protected SensorsData(T eventRecord) { 109 | this(eventRecord.getDistinctId(), eventRecord.getIdentityMap(), eventRecord.getEventName(), 110 | eventRecord.getPropertyMap(), eventRecord.getTrackId()); 111 | } 112 | 113 | protected SensorsData(T eventRecord, String actionType) { 114 | this(eventRecord.getDistinctId(), null, eventRecord.getIdentityMap(), actionType, 115 | eventRecord.getEventName(), eventRecord.getPropertyMap(), null, null, eventRecord.getTrackId()); 116 | } 117 | 118 | protected SensorsData(String distinctId, String type, Map identities, 119 | Map properties, Integer trackId) { 120 | this(distinctId, null, identities, type, null, properties, null, null, trackId); 121 | } 122 | 123 | /** 124 | * 构建 track 数据实体 125 | * 126 | * @param distinctId 用户ID 127 | * @param event 事件名 128 | * @param properties 事件属性集合 129 | */ 130 | protected SensorsData(String distinctId, Map identities, String event, 131 | Map properties, Integer trackId) { 132 | this(distinctId, null, identities, TRACK_ACTION_TYPE, event, properties, null, null, trackId); 133 | } 134 | 135 | /** 136 | * 专用于 item 相关 schema 构建 137 | */ 138 | protected SensorsData(Integer trackId, String distinctId, Map identities, String itemId, String type, 139 | String event, Map properties) { 140 | this(distinctId, null, identities, type, event, properties, null, itemId, trackId); 141 | } 142 | 143 | protected SensorsData(String distinctId, String originalId, Map identities, String type, String event, 144 | Map properties, String itemType, String itemId, Integer trackId) { 145 | this.trackId = trackId; 146 | this.distinctId = distinctId; 147 | this.originalId = originalId; 148 | this.identities = identities; 149 | this.type = type; 150 | this.event = event; 151 | this.time = properties.containsKey(SensorsConst.TIME_SYSTEM_ATTR) ? 152 | (Date) properties.remove(SensorsConst.TIME_SYSTEM_ATTR) : new Date(); 153 | this.properties = properties; 154 | this.itemType = itemType; 155 | this.itemId = itemId; 156 | this.project = properties.get(SensorsConst.PROJECT_SYSTEM_ATTR) == null ? 157 | null : String.valueOf(properties.remove(SensorsConst.PROJECT_SYSTEM_ATTR)); 158 | this.token = properties.get(SensorsConst.TOKEN_SYSTEM_ATTR) == null ? 159 | null : String.valueOf(properties.remove(SensorsConst.TOKEN_SYSTEM_ATTR)); 160 | this.timeFree = properties.containsKey(SensorsConst.TIME_FREE_ATTR) 161 | && Boolean.parseBoolean(properties.remove(SensorsConst.TIME_FREE_ATTR).toString()); 162 | } 163 | 164 | 165 | protected static Map generateData(SensorsData sensorsData) { 166 | Map eventMap = new HashMap<>(); 167 | if (sensorsData.getTrackId() != null) { 168 | eventMap.put("_track_id", sensorsData.getTrackId()); 169 | } 170 | if (sensorsData.getDistinctId() != null) { 171 | eventMap.put("distinct_id", sensorsData.getDistinctId()); 172 | } 173 | if (sensorsData.getOriginalId() != null) { 174 | eventMap.put("original_id", sensorsData.getOriginalId()); 175 | } 176 | if (sensorsData.getIdentities() != null) { 177 | eventMap.put("identities", sensorsData.getIdentities()); 178 | } 179 | if (sensorsData.getType() != null) { 180 | eventMap.put("type", sensorsData.getType()); 181 | } 182 | if (sensorsData.getEvent() != null) { 183 | eventMap.put("event", sensorsData.getEvent()); 184 | } 185 | // fix 【SDK-4709】time 类型保持为时间戳类型 186 | eventMap.put("time", sensorsData.getTime().getTime()); 187 | if (sensorsData.getProject() != null) { 188 | eventMap.put("project", sensorsData.getProject()); 189 | } 190 | if (sensorsData.getToken() != null) { 191 | eventMap.put("token", sensorsData.getToken()); 192 | } 193 | if (sensorsData.getItemId() != null) { 194 | eventMap.put("item_id", sensorsData.getItemId()); 195 | } 196 | if (sensorsData.getItemType() != null) { 197 | eventMap.put("item_type", sensorsData.getItemType()); 198 | } 199 | if (sensorsData.getProperties() != null) { 200 | eventMap.put("properties", sensorsData.getProperties()); 201 | } 202 | if (sensorsData.isTimeFree() 203 | && (TRACK_ACTION_TYPE.equals(sensorsData.getType()) 204 | || TRACK_SIGN_UP_ACTION_TYPE.equals(sensorsData.getType()) 205 | || BIND_ID_ACTION_TYPE.equals(sensorsData.getType()) 206 | || UNBIND_ID_ACTION_TYPE.equals(sensorsData.getType()))) { 207 | eventMap.put("time_free", true); 208 | } 209 | return eventMap; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/BatchConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.http.impl.client.HttpClientBuilder; 9 | import org.apache.http.impl.client.HttpClients; 10 | 11 | import java.util.ArrayList; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Slf4j 17 | public class BatchConsumer implements Consumer { 18 | private static final int MAX_FLUSH_BULK_SIZE = 1000; 19 | private static final int MAX_CACHE_SIZE = 6000; 20 | private static final int MIN_CACHE_SIZE = 3000; 21 | 22 | private final List> messageList; 23 | private final HttpConsumer httpConsumer; 24 | private final InstantHttpConsumer instantHttpConsumer; 25 | private final ObjectMapper jsonMapper; 26 | private final int bulkSize; 27 | private final boolean throwException; 28 | private final int maxCacheSize; 29 | private List instantEvents; 30 | private boolean isInstantStatus; 31 | 32 | public BatchConsumer(final String serverUrl) { 33 | this(serverUrl, 50); 34 | } 35 | 36 | public BatchConsumer(final String serverUrl, final int bulkSize) { 37 | this(serverUrl, bulkSize, 3); 38 | } 39 | 40 | public BatchConsumer(final String serverUrl, final int bulkSize, final int timeoutSec) { 41 | this(serverUrl, bulkSize, false, timeoutSec); 42 | } 43 | 44 | public BatchConsumer(final String serverUrl, final int bulkSize, final boolean throwException) { 45 | this(serverUrl, bulkSize, throwException, 3); 46 | } 47 | 48 | public BatchConsumer(final String serverUrl, final int bulkSize, final boolean throwException, 49 | final int timeoutSec) { 50 | this(serverUrl, bulkSize, 0, throwException, timeoutSec); 51 | } 52 | 53 | public BatchConsumer(final String serverUrl, final int bulkSize, final int maxCacheSize, 54 | final boolean throwException) { 55 | this(serverUrl, bulkSize, maxCacheSize, throwException, 3); 56 | } 57 | 58 | public BatchConsumer(final String serverUrl, final int bulkSize, final int maxCacheSize, 59 | final boolean throwException, final int timeoutSec) { 60 | this(HttpClients.custom(), serverUrl, bulkSize, maxCacheSize, throwException, timeoutSec); 61 | } 62 | 63 | public BatchConsumer(HttpClientBuilder httpClientBuilder, final String serverUrl, final int bulkSize, final int maxCacheSize, 64 | final boolean throwException, final int timeoutSec) { 65 | this(httpClientBuilder, serverUrl, bulkSize, maxCacheSize, throwException, timeoutSec, new ArrayList()); 66 | } 67 | 68 | 69 | public BatchConsumer(final String serverUrl, final int bulkSize, final int maxCacheSize, 70 | final boolean throwException, final int timeoutSec, List instantEvents) { 71 | this(HttpClients.custom(), serverUrl, bulkSize, maxCacheSize, throwException, timeoutSec, instantEvents); 72 | } 73 | 74 | public BatchConsumer(HttpClientBuilder httpClientBuilder, final String serverUrl, final int bulkSize, final int maxCacheSize, 75 | final boolean throwException, final int timeoutSec, List instantEvents) { 76 | this.messageList = new LinkedList<>(); 77 | this.isInstantStatus = false; 78 | this.instantEvents = instantEvents; 79 | this.httpConsumer = new HttpConsumer(httpClientBuilder, serverUrl, Math.max(timeoutSec, 1)); 80 | this.instantHttpConsumer = new InstantHttpConsumer(httpClientBuilder, serverUrl, Math.max(timeoutSec, 1)); 81 | this.jsonMapper = SensorsAnalyticsUtil.getJsonObjectMapper(); 82 | this.bulkSize = Math.min(MAX_FLUSH_BULK_SIZE, Math.max(1, bulkSize)); 83 | if (maxCacheSize > MAX_CACHE_SIZE) { 84 | this.maxCacheSize = MAX_CACHE_SIZE; 85 | } else if (maxCacheSize > 0 && maxCacheSize < MIN_CACHE_SIZE) { 86 | this.maxCacheSize = MIN_CACHE_SIZE; 87 | } else { 88 | this.maxCacheSize = maxCacheSize; 89 | } 90 | this.throwException = throwException; 91 | log.info( 92 | "Initialize BatchConsumer with params:[bulkSize:{},timeoutSec:{},maxCacheSize:{},throwException:{}]", 93 | bulkSize, timeoutSec, maxCacheSize, throwException); 94 | } 95 | 96 | @Override 97 | public void send(Map message) { 98 | synchronized (messageList) { 99 | dealInstantSignal(message); 100 | int size = messageList.size(); 101 | if (maxCacheSize <= 0 || size < maxCacheSize) { 102 | messageList.add(message); 103 | ++size; 104 | log.debug("Successfully save data to cache,The cache current size is {}.", size); 105 | } 106 | if (size >= bulkSize) { 107 | log.info("Flush was triggered because the cache size reached the threshold,cache size:{},bulkSize:{}.", 108 | size, bulkSize); 109 | flush(); 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | public void flush() { 116 | synchronized (messageList) { 117 | while (!messageList.isEmpty()) { 118 | String sendingData; 119 | List> sendList = messageList.subList(0, Math.min(bulkSize, messageList.size())); 120 | try { 121 | sendingData = jsonMapper.writeValueAsString(sendList); 122 | } catch (JsonProcessingException e) { 123 | sendList.clear(); 124 | log.error("Failed to process json.", e); 125 | if (throwException) { 126 | throw new RuntimeException("Failed to serialize data.", e); 127 | } 128 | continue; 129 | } 130 | log.debug("Will be send data:{}.", sendingData); 131 | try { 132 | if (isInstantStatus) { 133 | this.instantHttpConsumer.consume(sendingData); 134 | } else { 135 | this.httpConsumer.consume(sendingData); 136 | } 137 | sendList.clear(); 138 | } catch (Exception e) { 139 | log.error("Failed to send data:{}.", sendingData, e); 140 | if (throwException) { 141 | throw new RuntimeException("Failed to dump message with BatchConsumer.", e); 142 | } 143 | return; 144 | } 145 | log.debug("Successfully send data:{}.", sendingData); 146 | } 147 | log.info("Finish flush."); 148 | } 149 | } 150 | 151 | @Override 152 | public void close() { 153 | flush(); 154 | httpConsumer.close(); 155 | log.info("Call close method."); 156 | } 157 | 158 | private void dealInstantSignal(Map message) { 159 | 160 | /* 161 | * 如果当前是「instant」状态,且(message中不包含event 或者 event 不是「instant」的,则刷新,设置 「非instant」状态 162 | */ 163 | if (isInstantStatus && (!message.containsKey("event") || !instantEvents.contains(message.get("event")))) { 164 | flush(); 165 | isInstantStatus = false; 166 | } 167 | 168 | /* 169 | * 如果当前是 「非instant」状态,且(message中包含event 且 event 是「instant」的,则刷新,设置 「instant」状态 170 | */ 171 | if (!isInstantStatus && message.containsKey("event") && instantEvents.contains(message.get("event"))) { 172 | flush(); 173 | isInstantStatus = true; 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/HelloSensorsAnalytics.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 4 | import com.sensorsdata.analytics.javasdk.bean.ItemRecord; 5 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 6 | import com.sensorsdata.analytics.javasdk.bean.SuperPropertiesRecord; 7 | import com.sensorsdata.analytics.javasdk.bean.UserRecord; 8 | import com.sensorsdata.analytics.javasdk.consumer.ConcurrentLoggingConsumer; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Calendar; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author fz 18 | * @version 1.0.0 19 | * @since 2021/05/24 15:31 20 | */ 21 | public class HelloSensorsAnalytics { 22 | 23 | 24 | public static void main(final String[] args) throws Exception { 25 | // LoggingConsumer 26 | final ISensorsAnalytics sa = new SensorsAnalytics(new ConcurrentLoggingConsumer("file.log")); 27 | 28 | //设置公共属性,以后上传的每一个事件都附带该属性 29 | SuperPropertiesRecord propertiesRecord = SuperPropertiesRecord.builder() 30 | .addProperty("$os", "Windows") 31 | .addProperty("$os_version", "8.1") 32 | .addProperty("$ip", "123.123.123.123") 33 | .build(); 34 | sa.registerSuperProperties(propertiesRecord); 35 | 36 | // 1. 用户匿名访问网站,cookieId 默认神策生成分配 37 | String cookieId = "ABCDEF123456789"; 38 | // 1.1 访问首页 39 | // 前面有$开头的property字段,是SA提供给用户的预置字段 40 | // 对于预置字段,已经确定好了字段类型和字段的显示名 41 | EventRecord firstRecord = EventRecord.builder().setDistinctId(cookieId).isLoginId(Boolean.FALSE) 42 | .setEventName("track") 43 | .addProperty("$time", Calendar.getInstance().getTime()) 44 | .addProperty("Channel", "baidu") 45 | .addProperty("$project", "abc") 46 | .addProperty("$token", "123") 47 | .build(); 48 | sa.track(firstRecord); 49 | // 1.2 搜索商品 50 | EventRecord searchRecord = EventRecord.builder().setDistinctId(cookieId).isLoginId(Boolean.FALSE) 51 | .setEventName("SearchProduct") 52 | .addProperty("KeyWord", "XX手机") 53 | .build(); 54 | sa.track(searchRecord); 55 | // 1.3 浏览商品 56 | EventRecord lookRecord = EventRecord.builder().setDistinctId(cookieId).isLoginId(Boolean.FALSE) 57 | .setEventName("ViewProduct") 58 | .addProperty("ProductName", "XX手机") 59 | .addProperty("ProductType", "智能手机") 60 | .addProperty("ShopName", "XX官方旗舰店") 61 | .build(); 62 | sa.track(lookRecord); 63 | // 2. 用户注册登录之后,系统分配的注册ID 64 | String registerId = "123456"; 65 | //使用trackSignUp关联用户匿名ID和登录ID 66 | sa.trackSignUp(registerId, cookieId); 67 | 68 | // 2.2 用户注册时,填充了一些个人信息,可以用Profile接口记录下来 69 | List interests = new ArrayList(); 70 | interests.add("movie"); 71 | interests.add("swim"); 72 | UserRecord userRecord = UserRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 73 | .addProperty("$city", "武汉") 74 | .addProperty("$province", "湖北") 75 | .addProperty("$name", "昵称123") 76 | .addProperty("$signup_time", Calendar.getInstance().getTime()) 77 | .addProperty("Gender", "male") 78 | .addProperty("age", 20) 79 | .addProperty("interest", interests) 80 | .build(); 81 | sa.profileSet(userRecord); 82 | 83 | //2.3 设置首次访问时间 84 | UserRecord firstVisitRecord = UserRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 85 | .addProperty("$first_visit_time", Calendar.getInstance().getTime()) 86 | .build(); 87 | sa.profileSetOnce(firstVisitRecord); 88 | 89 | //2.4 追加属性 90 | List newInterest = new ArrayList(); 91 | newInterest.add("ball"); 92 | UserRecord appendRecord = UserRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 93 | .addProperty("interest", newInterest) 94 | .build(); 95 | sa.profileAppend(appendRecord); 96 | 97 | //2.5 给属性加值 98 | UserRecord incrementRecord = UserRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 99 | .addProperty("age", 2) 100 | .build(); 101 | sa.profileIncrement(incrementRecord); 102 | 103 | //2.6 移除用户属性 104 | UserRecord unsetRecord = UserRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 105 | .addProperty("age", true) 106 | .build(); 107 | sa.profileUnset(unsetRecord); 108 | 109 | // 3. 用户注册后,进行后续行为 110 | // 3.1 提交订单和提交订单详情 111 | // 订单的信息 112 | EventRecord orderRecord = EventRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 113 | .setEventName("SubmitOrder") 114 | .addProperty("OrderId", "SN_123_AB_TEST") 115 | .build(); 116 | sa.track(orderRecord); 117 | 118 | // 3.2 支付订单和支付订单详情 119 | // 整个订单的支付情况 120 | EventRecord payRecord = EventRecord.builder().setDistinctId(registerId).isLoginId(Boolean.TRUE) 121 | .setEventName("PayOrder") 122 | .addProperty("PaymentMethod", "AliPay") 123 | .addProperty("AllowanceAmount", 30.0) 124 | .addProperty("PaymentAmount", 1204.0) 125 | .build(); 126 | sa.track(payRecord); 127 | 128 | //物品纬度表上报 129 | String itemId = "product001", itemType = "mobile"; 130 | ItemRecord addRecord = ItemRecord.builder().setItemId(itemId).setItemType(itemType) 131 | .addProperty("color", "white") 132 | .build(); 133 | sa.itemSet(addRecord); 134 | 135 | //删除物品纬度信息 136 | ItemRecord deleteRecord = ItemRecord.builder().setItemId(itemId).setItemType(itemType) 137 | .build(); 138 | sa.itemDelete(deleteRecord); 139 | 140 | //Id-mapping 141 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 142 | .addIdentityProperty("email", "fz@163.com") 143 | .build(); 144 | Map properties = new HashMap(); 145 | properties.put("Channel", "baidu"); 146 | sa.trackById(identity, "test", properties); 147 | SensorsAnalyticsIdentity identity1 = SensorsAnalyticsIdentity.builder() 148 | .addIdentityProperty("login_id", "fz123") 149 | .build(); 150 | sa.bind(identity, identity1); 151 | sa.profileSetById(identity, "age", 15); 152 | List sports = new ArrayList(); 153 | sports.add("swim"); 154 | sports.add("run"); 155 | Map hh = new HashMap(); 156 | hh.put("sport", sports); 157 | sa.profileSetById(identity, hh); 158 | sa.profileIncrementById(identity, "age", 1); 159 | sa.profileAppendById(identity, "sport", "ball"); 160 | sa.profileUnsetById(identity,"sport"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/main/java/com/sensorsdata/analytics/javasdk/consumer/FastBatchConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk.consumer; 2 | 3 | import com.sensorsdata.analytics.javasdk.bean.FailedData; 4 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 5 | import com.sensorsdata.analytics.javasdk.util.SensorsAnalyticsUtil; 6 | 7 | import com.fasterxml.jackson.core.JsonProcessingException; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import lombok.NonNull; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.http.impl.client.HttpClientBuilder; 12 | import org.apache.http.impl.client.HttpClients; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.concurrent.LinkedBlockingQueue; 18 | import java.util.concurrent.ScheduledExecutorService; 19 | import java.util.concurrent.ScheduledThreadPoolExecutor; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | /** 23 | * 网络批量请求发送,异常快速返回模式 24 | * 25 | * @author fangzhuo 26 | * @version 1.0.0 27 | * @since 2021/11/05 23:48 28 | */ 29 | @Slf4j 30 | public class FastBatchConsumer implements Consumer { 31 | 32 | private static final int MAX_CACHE_SIZE = 10000; 33 | private static final int MIN_CACHE_SIZE = 1000; 34 | private static final int MIN_BULK_SIZE = 1; 35 | 36 | 37 | private final LinkedBlockingQueue> buffer; 38 | private final HttpConsumer httpConsumer; 39 | private final InstantHttpConsumer instantHttpConsumer; 40 | private final ObjectMapper jsonMapper; 41 | private final Callback callback; 42 | private final int bulkSize; 43 | private final ScheduledExecutorService executorService; 44 | private List instantEvents; 45 | private boolean isInstantStatus; 46 | 47 | public FastBatchConsumer(@NonNull String serverUrl, @NonNull Callback callback) { 48 | this(serverUrl, false, callback); 49 | } 50 | 51 | public FastBatchConsumer(@NonNull String serverUrl, int flushSec, final boolean timing, @NonNull Callback callback) { 52 | this(serverUrl, timing, 50, 6000, flushSec, 3, callback); 53 | } 54 | 55 | public FastBatchConsumer(@NonNull String serverUrl, final boolean timing, @NonNull Callback callback) { 56 | this(serverUrl, timing, 50, callback); 57 | } 58 | 59 | public FastBatchConsumer(@NonNull String serverUrl, final boolean timing, int bulkSize, @NonNull Callback callback) { 60 | this(serverUrl, timing, bulkSize, 6000, callback); 61 | } 62 | 63 | public FastBatchConsumer(@NonNull String serverUrl, final boolean timing, int bulkSize, int maxCacheSize, 64 | @NonNull Callback callback) { 65 | this(serverUrl, timing, bulkSize, maxCacheSize, 1, 3, callback); 66 | } 67 | 68 | public FastBatchConsumer(@NonNull String serverUrl, final boolean timing, final int bulkSize, int maxCacheSize, 69 | int flushSec, int timeoutSec, @NonNull Callback callback) { 70 | this(HttpClients.custom(), serverUrl, timing, bulkSize, maxCacheSize, flushSec, timeoutSec, callback); 71 | } 72 | 73 | public FastBatchConsumer(HttpClientBuilder httpClientBuilder, @NonNull String serverUrl, final boolean timing, final int bulkSize, int maxCacheSize, 74 | int flushSec, int timeoutSec, @NonNull Callback callback) { 75 | this(httpClientBuilder, serverUrl, timing, bulkSize, maxCacheSize, flushSec, timeoutSec, callback, new ArrayList()); 76 | } 77 | 78 | public FastBatchConsumer(HttpClientBuilder httpClientBuilder, @NonNull String serverUrl, final boolean timing, final int bulkSize, int maxCacheSize, 79 | int flushSec, int timeoutSec, @NonNull Callback callback, List instantEvents) { 80 | this.buffer = 81 | new LinkedBlockingQueue<>(Math.min(Math.max(MIN_CACHE_SIZE, maxCacheSize), MAX_CACHE_SIZE)); 82 | this.httpConsumer = new HttpConsumer(httpClientBuilder, serverUrl, Math.max(timeoutSec, 1)); 83 | this.instantHttpConsumer = new InstantHttpConsumer(httpClientBuilder, serverUrl, Math.max(timeoutSec, 1)); 84 | 85 | this.jsonMapper = SensorsAnalyticsUtil.getJsonObjectMapper(); 86 | this.callback = callback; 87 | this.bulkSize = Math.min(MIN_CACHE_SIZE, Math.max(bulkSize, MIN_BULK_SIZE)); 88 | this.instantEvents = instantEvents; 89 | 90 | executorService = new ScheduledThreadPoolExecutor(1); 91 | executorService.scheduleWithFixedDelay(new Runnable() { 92 | @Override 93 | public void run() { 94 | if (timing) { 95 | flush(); 96 | } else { 97 | if (buffer.size() >= bulkSize) { 98 | flush(); 99 | } 100 | } 101 | } 102 | }, 1, Math.max(flushSec, 1), TimeUnit.SECONDS); 103 | log.info( 104 | "Initialize FastBatchConsumer with params:[timing:{};bulkSize:{};maxCacheSize:{};flushSec:{};timeoutSec:{}].", 105 | timing, bulkSize, maxCacheSize, flushSec, timeoutSec); 106 | } 107 | 108 | @Override 109 | public void send(Map message) { 110 | dealInstantSignal(message); 111 | if (buffer.remainingCapacity() == 0) { 112 | flush(); 113 | } 114 | buffer.offer(message); 115 | log.debug("Successfully save data to cache.The cache current size is {}.", buffer.size()); 116 | } 117 | 118 | private void dealInstantSignal(Map message) { 119 | 120 | /* 121 | * 如果当前是「instant」状态,且(message中不包含event 或者 event 不是「instant」的,则刷新,设置 「非instant」状态 122 | */ 123 | if (isInstantStatus && (!message.containsKey("event") || !instantEvents.contains(message.get("event")))) { 124 | flush(); 125 | isInstantStatus = false; 126 | } 127 | 128 | /* 129 | * 如果当前是 「非instant」状态,且(message中包含event 且 event 是「instant」的,则刷新,设置 「instant」状态 130 | */ 131 | if (!isInstantStatus && message.containsKey("event") && instantEvents.contains(message.get("event"))) { 132 | flush(); 133 | isInstantStatus = true; 134 | } 135 | } 136 | 137 | /** 138 | * This method don't need to be called actively.Because instance will create scheduled thread to do. 139 | */ 140 | @Override 141 | public void flush() { 142 | List> results = new ArrayList<>(); 143 | buffer.drainTo(results); 144 | if (results.isEmpty()) { 145 | log.info("The Data of cache is empty when flush."); 146 | return; 147 | } 148 | log.debug("Successfully get [{}] messages from the cache.", results.size()); 149 | while (!results.isEmpty()) { 150 | String sendingData; 151 | List> sendList = results.subList(0, Math.min(bulkSize, results.size())); 152 | try { 153 | sendingData = jsonMapper.writeValueAsString(sendList); 154 | } catch (JsonProcessingException e) { 155 | callback.onFailed(new FailedData(String.format("can't process json,message:%s.", e.getMessage()), 156 | SensorsAnalyticsUtil.deepCopy(sendList))); 157 | sendList.clear(); 158 | log.error("Failed to process json.", e); 159 | continue; 160 | } 161 | log.debug("Data will be sent.{}", sendingData); 162 | try { 163 | if (isInstantStatus) { 164 | this.instantHttpConsumer.consume(sendingData); 165 | } else { 166 | this.httpConsumer.consume(sendingData); 167 | } 168 | } catch (Exception e) { 169 | log.error("Failed to send data:{}.", sendingData, e); 170 | callback.onFailed(new FailedData(String.format("failed to send data,message:%s.", e.getMessage()), 171 | SensorsAnalyticsUtil.deepCopy(sendList))); 172 | } 173 | sendList.clear(); 174 | } 175 | log.debug("Finish flush."); 176 | } 177 | 178 | @Override 179 | public void close() { 180 | log.info("Call close method."); 181 | this.httpConsumer.close(); 182 | this.executorService.shutdown(); 183 | } 184 | 185 | /** 186 | * 重发送 FastBatchConsumer 模式发送失败返回的数据 187 | * 188 | * @param failedData 失败的数据集合 189 | * @return true:发送成功;false:发送失败 190 | */ 191 | public boolean resendFailedData(@NonNull FailedData failedData) 192 | throws InvalidArgumentException, JsonProcessingException { 193 | SensorsAnalyticsUtil.assertFailedData(failedData); 194 | final String sendData = jsonMapper.writeValueAsString(failedData.getFailedData()); 195 | log.debug("Will be resent data.{}", sendData); 196 | try { 197 | this.httpConsumer.consume(sendData); 198 | } catch (Exception e) { 199 | log.error("failed to send data.data:{}.", sendData, e); 200 | return false; 201 | } 202 | log.info("Successfully resend failed data."); 203 | return true; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.sonatype.oss 9 | oss-parent 10 | 7 11 | 12 | 13 | jar 14 | 15 | com.sensorsdata.analytics.javasdk 16 | SensorsAnalyticsSDK 17 | SensorsAnalyticsSDK 18 | 3.6.8 19 | The official Java SDK of Sensors Analytics 20 | http://sensorsdata.cn 21 | 22 | 23 | 24 | The Apache Software License, Version 2.0 25 | http://www.apache.org/licenses/LICENSE-2.0.txt 26 | 27 | 28 | 29 | 30 | scm:git@github.com:sensorsdata/sa-sdk-java.git 31 | scm:git@github.com:sensorsdata/sa-sdk-java.git 32 | git@github.com:sensorsdata/sa-sdk-java.git 33 | 34 | 35 | 36 | utf-8 37 | utf-8 38 | 1.7 39 | 4.5.13 40 | 2.12.7.1 41 | 1.18.20 42 | 4.11 43 | 1.7.25 44 | 1.6.6 45 | 9.4.12.v20180830 46 | 47 | 48 | 49 | 50 | org.apache.httpcomponents 51 | httpclient 52 | ${httpclient.version} 53 | 54 | 55 | com.fasterxml.jackson.core 56 | jackson-databind 57 | ${jackson-databind.version} 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | ${lombok.version} 63 | provided 64 | 65 | 66 | org.slf4j 67 | slf4j-api 68 | ${slf4j.version} 69 | 70 | 71 | org.slf4j 72 | slf4j-simple 73 | ${slf4j-simple.version} 74 | test 75 | 76 | 77 | junit 78 | junit 79 | ${junit.version} 80 | test 81 | 82 | 83 | org.eclipse.jetty.websocket 84 | websocket-server 85 | ${jetty.version} 86 | test 87 | 88 | 89 | org.junit.jupiter 90 | junit-jupiter 91 | RELEASE 92 | test 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-compiler-plugin 101 | 3.3 102 | 103 | ${jdk.version} 104 | ${jdk.version} 105 | UTF-8 106 | 107 | 108 | 109 | maven-source-plugin 110 | 2.1 111 | 112 | true 113 | 114 | 115 | 116 | compile 117 | 118 | jar 119 | 120 | 121 | 122 | 123 | 124 | maven-assembly-plugin 125 | 126 | 127 | jar-with-dependencies 128 | 129 | 130 | 131 | 132 | maven-jar-plugin 133 | 2.6 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-surefire-plugin 138 | 2.18.1 139 | 140 | -Xms1024m -Xmx1024m 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | release 149 | 150 | 151 | ossrh 152 | https://s01.oss.sonatype.org/content/repositories/snapshots/ 153 | 154 | 155 | ossrh 156 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 157 | 158 | 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-source-plugin 165 | 2.4 166 | 167 | 168 | package 169 | 170 | jar-no-fork 171 | 172 | 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-javadoc-plugin 179 | 2.10.3 180 | 181 | 182 | package 183 | 184 | jar 185 | 186 | 187 | 188 | 189 | 190 | 191 | org.apache.maven.plugins 192 | maven-gpg-plugin 193 | 1.6 194 | 195 | 196 | sign-artifacts 197 | verify 198 | 199 | sign 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/IDMappingProfileTest.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.junit.Assert.fail; 7 | 8 | import com.sensorsdata.analytics.javasdk.bean.IDMEventRecord; 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | import com.sensorsdata.analytics.javasdk.bean.IDMUserRecord; 14 | import com.sensorsdata.analytics.javasdk.bean.SensorsAnalyticsIdentity; 15 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 16 | 17 | import org.junit.Test; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Map; 21 | 22 | /** 23 | * id-mapping profile 相关接口单元测试 24 | * 版本支持 3.3.0+ 25 | * 26 | * @author fangzhuo 27 | * @version 1.0.0 28 | * @since 2022/03/12 11:10 29 | */ 30 | public class IDMappingProfileTest extends SensorsBaseTest { 31 | 32 | private final String loginId = "fz123"; 33 | private final String email = "fz@163.com"; 34 | 35 | //------------------------------3.3.0---------------------------- 36 | 37 | /** 38 | *

39 | * 用户使用 IDMUserRecord 调用 profileSetById 接口; 40 | *

41 | * 期望生成的数据结构符合神策数据格式 42 | */ 43 | @Test 44 | public void checkProfileSetById() throws InvalidArgumentException { 45 | final SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 46 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 47 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 48 | .build(); 49 | sa.profileSetById(identity, "test", "ddd"); 50 | assertIDM3UserData(data); 51 | } 52 | 53 | /** 54 | * 用户生成 IDM3.0 profile 数据的时候,不需要生成 super 里面的属性 55 | * 期望 properties 不产生 super 属性 56 | */ 57 | @Test 58 | public void checkProfileSuperProperties() throws InvalidArgumentException { 59 | final SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 60 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 61 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 62 | .build(); 63 | sa.profileSetById(identity, "test", "ddd"); 64 | assertIDM3UserData(data); 65 | Map properties = (Map) data.get("properties"); 66 | assertEquals(2, properties.size()); 67 | assertTrue(properties.containsKey("$is_login_id")); 68 | assertTrue(properties.containsKey("test")); 69 | } 70 | 71 | //------------------------------3.4.2---------------------------- 72 | 73 | /** 74 | * 用户生成 IDM3.0 profile 数据的时候,不需要生成 super 里面的属性 75 | * 期望 properties 不产生 super 属性 76 | */ 77 | @Test 78 | public void checkProfileSuperProperties01() throws InvalidArgumentException { 79 | IDMUserRecord userRecord = IDMUserRecord.starter() 80 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, "fz@163.com") 81 | .addProperty("test", "ddd") 82 | .build(); 83 | sa.profileSetById(userRecord); 84 | assertIDM3UserData(data); 85 | Map properties = (Map) data.get("properties"); 86 | assertEquals(1, properties.size()); 87 | assertFalse(properties.containsKey("$is_login_id")); 88 | assertTrue(properties.containsKey("test")); 89 | } 90 | 91 | /** 92 | *

93 | * 用户使用 IDMUserRecord 调用 profileSetById 接口; 94 | *

95 | * 期望生成的数据结构符合神策数据格式 96 | */ 97 | @Test 98 | public void checkProfileSetById01() throws InvalidArgumentException { 99 | IDMUserRecord userRecord = IDMUserRecord.starter() 100 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 101 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 102 | .addProperty("hello", "dd") 103 | .build(); 104 | sa.profileSetById(userRecord); 105 | assertIDM3UserData(data); 106 | } 107 | 108 | /** 109 | *

110 | * 用户携带 $identity_login_id 使用 IDMUserRecord 调用 profileSetById 接口; 111 | *

112 | * 期望生成的数据结构,distinct_id 取值为 $identity_login_id 113 | */ 114 | @Test 115 | public void checkProfileSetByIdWithLoginId() throws InvalidArgumentException { 116 | IDMUserRecord userRecord = IDMUserRecord.starter() 117 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 118 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 119 | .addProperty("hello", "dd") 120 | .build(); 121 | sa.profileSetById(userRecord); 122 | assertEquals(loginId, data.get("distinct_id")); 123 | } 124 | 125 | /** 126 | *

127 | * 用户携带 $identity_login_id,设置 distinct_id 使用 IDMUserRecord 调用 profileSetById 接口; 128 | *

129 | * 期望生成的数据结构,distinct_id 取值为 $identity_login_id 130 | */ 131 | @Test 132 | public void checkProfileSetByIdWithLoginId01() throws InvalidArgumentException { 133 | IDMUserRecord userRecord = IDMUserRecord.starter() 134 | .setDistinctId("zzz") 135 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 136 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 137 | .addProperty("hello", "dd") 138 | .build(); 139 | sa.profileSetById(userRecord); 140 | assertEquals("zzz", data.get("distinct_id")); 141 | } 142 | 143 | /** 144 | *

145 | * 用户使用 IDMUserRecord 调用 profileSetOnceById 接口; 146 | *

147 | * 期望生成的数据结构符合神策数据格式 148 | */ 149 | @Test 150 | public void checkProfileSetOnceById() throws InvalidArgumentException { 151 | IDMUserRecord userRecord = IDMUserRecord.starter() 152 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 153 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 154 | .addProperty("hello", "dd") 155 | .build(); 156 | sa.profileSetOnceById(userRecord); 157 | assertIDM3UserData(data); 158 | } 159 | 160 | /** 161 | *

162 | * 用户使用 IDMUserRecord 调用 profileIncrementById 接口; 163 | *

164 | * 期望生成的数据结构符合神策数据格式 165 | */ 166 | @Test 167 | public void checkProfileIncrementById() throws InvalidArgumentException { 168 | IDMUserRecord userRecord = IDMUserRecord.starter() 169 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 170 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 171 | .addProperty("num", 33) 172 | .build(); 173 | sa.profileIncrementById(userRecord); 174 | assertIDM3UserData(data); 175 | } 176 | 177 | /** 178 | *

179 | * 用户使用 IDMUserRecord 调用 profileIncrementById 接口; 180 | *

181 | * 期望生成的数据结构符合神策数据格式 182 | */ 183 | @Test 184 | public void checkProfileAppendById() throws InvalidArgumentException { 185 | ArrayList strings = new ArrayList<>(); 186 | strings.add("movie"); 187 | strings.add("swim"); 188 | IDMUserRecord userRecord = IDMUserRecord.starter() 189 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 190 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 191 | .addProperty("lists", strings) 192 | .build(); 193 | sa.profileAppendById(userRecord); 194 | assertIDM3UserData(data); 195 | } 196 | 197 | /** 198 | *

199 | * 用户使用 IDMUserRecord 调用 profileUnsetById 接口; 200 | *

201 | * 期望生成的数据结构符合神策数据格式 202 | */ 203 | @Test 204 | public void checkProfileUnsetById() throws InvalidArgumentException { 205 | IDMUserRecord userRecord = IDMUserRecord.starter() 206 | .addIdentityProperty(SensorsAnalyticsIdentity.LOGIN_ID, loginId) 207 | .addIdentityProperty(SensorsAnalyticsIdentity.EMAIL, email) 208 | .build(); 209 | sa.profileUnsetById(userRecord); 210 | assertIDM3UserData(data); 211 | } 212 | 213 | //------------------------------------bug fix 3.5.0 begin------------------------------- 214 | 215 | /** 216 | * bug fix : 修复解绑可以传入多个用户ID 217 | *

修复后的逻辑为:解绑只可以传入一个用户维度ID

218 | */ 219 | @Test 220 | public void checkUnbind() throws InvalidArgumentException { 221 | SensorsAnalyticsIdentity identity = SensorsAnalyticsIdentity.builder() 222 | .addIdentityProperty("key1", "value1") 223 | .addIdentityProperty("key2", "value2") 224 | .build(); 225 | try { 226 | sa.unbind(identity); 227 | fail("生成异常数据!"); 228 | } catch (InvalidArgumentException e) { 229 | assertTrue(e.getMessage().contains("unbind user operation cannot input multiple or none identifiers")); 230 | } 231 | 232 | //正常单个用户标识 233 | sa.unbind("key1", "value1"); 234 | assertIDM3EventData(data); 235 | 236 | // 无用户标识 237 | SensorsAnalyticsIdentity noIdentity = SensorsAnalyticsIdentity.builder().build(); 238 | try { 239 | sa.unbind(noIdentity); 240 | fail("生成异常数据!"); 241 | } catch (Exception e) { 242 | assertTrue(e.getMessage().contains("unbind user operation cannot input multiple or none identifiers")); 243 | } 244 | } 245 | //------------------------------------bug fix 3.5.0 end------------------------------- 246 | } 247 | -------------------------------------------------------------------------------- /SensorsAnalyticsSDK/src/test/java/com.sensorsdata.analytics.javasdk/IDMappingModel1TrackIdDebugConsumer.java: -------------------------------------------------------------------------------- 1 | package com.sensorsdata.analytics.javasdk; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import com.sensorsdata.analytics.javasdk.bean.EventRecord; 6 | import com.sensorsdata.analytics.javasdk.bean.ItemRecord; 7 | import com.sensorsdata.analytics.javasdk.bean.UserRecord; 8 | import com.sensorsdata.analytics.javasdk.consumer.BatchConsumer; 9 | import com.sensorsdata.analytics.javasdk.consumer.ConcurrentLoggingConsumer; 10 | import com.sensorsdata.analytics.javasdk.consumer.DebugConsumer; 11 | import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Date; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * 适用于 v3.4.4+ 版本 24 | * 测试点:验证事件的 _track_id 正常由 $track_id 生成。 25 | * 无特殊情况,不在下面的 testcase 上一一说明 26 | */ 27 | public class IDMappingModel1TrackIdDebugConsumer extends SensorsBaseTest { 28 | 29 | private BatchConsumer batchConsumer; 30 | 31 | private List> messageList; 32 | private ConcurrentLoggingConsumer consumer; 33 | 34 | private StringBuilder messageBuffer; 35 | SensorsAnalytics sa; 36 | 37 | @Before 38 | public void init() { 39 | String url = "http://10.120.111.143:8106/sa?project=default"; 40 | DebugConsumer consumer = new DebugConsumer(url, true); 41 | sa = new SensorsAnalytics(consumer); 42 | } 43 | 44 | /** 45 | * 校验调用 track 方法生成事件节点数是否完整 46 | */ 47 | @Test 48 | public void checkTrackEventLoginTrue() throws InvalidArgumentException { 49 | Map properties = new HashMap<>(); 50 | properties.put("$track_id", 111); 51 | 52 | sa.track("123", true, "test", properties); 53 | } 54 | 55 | 56 | /** 57 | * 校验 trackSignup 记录节点 58 | */ 59 | @Test 60 | public void checkTrackSignUpProp() throws InvalidArgumentException { 61 | Map properties = new HashMap<>(); 62 | properties.put("number1", 1234); 63 | properties.put("String1", "str"); 64 | properties.put("boolean1", false); 65 | properties.put("$track_id", 111); 66 | sa.trackSignUp("123", "345", properties); 67 | } 68 | 69 | 70 | /** 71 | * 校验自定义属性格式是否正常 72 | */ 73 | @Test 74 | public void checkProfileSetDataType() throws InvalidArgumentException { 75 | List list = new ArrayList<>(); 76 | Date date = new Date(); 77 | list.add("aaa"); 78 | list.add("bbb"); 79 | Map properties = new HashMap<>(); 80 | properties.put("number1", 1234); 81 | properties.put("date1", date); 82 | properties.put("String1", "str"); 83 | properties.put("boolean1", false); 84 | properties.put("list1", list); 85 | properties.put("$track_id", 111); 86 | sa.profileSet("123", true, properties); 87 | } 88 | 89 | /** 90 | * 校验自定义属性格式是否正常 91 | */ 92 | @Test 93 | public void checkProfileSetDataType01() throws InvalidArgumentException { 94 | 95 | sa.profileSet("123", true, "$track_id", 111); 96 | } 97 | /** 98 | * 校验自定义属性格式是否正常 99 | */ 100 | @Test 101 | public void testProfileSetOnceDataType() throws InvalidArgumentException { 102 | List list = new ArrayList<>(); 103 | Date date = new Date(); 104 | list.add("aaa"); 105 | list.add("bbb"); 106 | Map properties = new HashMap<>(); 107 | properties.put("number1", 1234); 108 | properties.put("date1", date); 109 | properties.put("String1", "str"); 110 | properties.put("boolean1", false); 111 | properties.put("list1", list); 112 | properties.put("$track_id", 111); 113 | sa.profileSetOnce("123", true, properties); 114 | } 115 | 116 | /** 117 | * 校验自定义属性格式是否正常 118 | */ 119 | @Test 120 | public void testProfileIncrement() throws InvalidArgumentException { 121 | Map properties = new HashMap<>(); 122 | properties.put("number1", 1234); 123 | properties.put("$track_id", 111); 124 | sa.profileIncrement("123", true, properties); 125 | } 126 | 127 | /** 128 | * 校验自定义属性格式是否正常 129 | */ 130 | @Test 131 | public void testProfileIncrement01() throws InvalidArgumentException { 132 | sa.profileIncrement("123", true, "$track_id", 111); 133 | } 134 | 135 | 136 | @Test 137 | public void testProfileAppend() throws InvalidArgumentException{ 138 | List list = new ArrayList<>(); 139 | list.add("aaa"); 140 | list.add("bbb"); 141 | Map properties = new HashMap<>(); 142 | properties.put("list1", list); 143 | 144 | List listInt = new ArrayList<>(); 145 | listInt.add(111); 146 | properties.put("$track_id", listInt); 147 | 148 | try { 149 | sa.profileAppend("123", true, properties); 150 | fail("[ERROR] profileAppend should throw InvalidArgumentException."); 151 | }catch (Exception e){ 152 | e.printStackTrace(); 153 | } 154 | } 155 | 156 | @Test 157 | public void testProfileAppend01() throws InvalidArgumentException{ 158 | sa.profileAppend("123", true, "$track_id", "111"); 159 | } 160 | 161 | // profileUnset 162 | @Test 163 | public void testProfileUnset() { 164 | Map properties = new HashMap<>(); 165 | properties.put("list1", true); 166 | properties.put("$track_id", 111); 167 | 168 | try { 169 | sa.profileUnset("123", true, properties); 170 | fail("[ERROR] profileUnset should throw InvalidArgumentException."); 171 | }catch (InvalidArgumentException e){ 172 | } 173 | } 174 | 175 | // profileUnset 176 | @Test 177 | public void testProfileUnset01() throws InvalidArgumentException{ 178 | sa.profileUnset("123", true, "$track_id"); 179 | } 180 | 181 | // profileDelete 182 | @Test 183 | public void testProfileDelete() throws InvalidArgumentException{ 184 | sa.profileDelete("123", true); 185 | } 186 | 187 | @Test 188 | public void testItemSet_Delete() throws Exception { 189 | //物品纬度表上报 190 | String itemId = "product001", itemType = "mobile"; 191 | ItemRecord addRecord = ItemRecord.builder().setItemId(itemId).setItemType(itemType) 192 | .addProperty("color", "white") 193 | .build(); 194 | sa.itemSet(addRecord); 195 | 196 | //删除物品纬度信息 197 | ItemRecord deleteRecord = ItemRecord.builder().setItemId(itemId).setItemType(itemType) 198 | .build(); 199 | sa.itemDelete(deleteRecord); 200 | sa.flush(); 201 | } 202 | 203 | 204 | 205 | /** 206 | * 校验 event Builder 模式生成数据用户属性是否正常 207 | */ 208 | @Test 209 | public void checkTrackEventBuilder() throws InvalidArgumentException { 210 | EventRecord eventRecord = EventRecord.builder() 211 | .setDistinctId("abc") 212 | .isLoginId(false) 213 | .setEventName("test") 214 | .addProperty("$track_id", 111) 215 | .build(); 216 | sa.track(eventRecord); 217 | } 218 | 219 | /** 220 | * 校验 is_login_id 为 true 的事件属性 221 | */ 222 | @Test 223 | public void checkTrackEventBuilderLoginIdIsTrue() throws InvalidArgumentException { 224 | EventRecord eventRecord = EventRecord.builder() 225 | .setDistinctId("abc") 226 | .isLoginId(true) 227 | .setEventName("test") 228 | .addProperty("$track_id", 111) 229 | .build(); 230 | sa.track(eventRecord); 231 | } 232 | 233 | /** 234 | * 校验自定义属性格式是否正常 235 | */ 236 | @Test 237 | public void checkProfileSetDataTypeEventBuilder() throws InvalidArgumentException { 238 | List list = new ArrayList<>(); 239 | Date date = new Date(); 240 | list.add("aaa"); 241 | list.add("bbb"); 242 | UserRecord userRecord = UserRecord.builder() 243 | .setDistinctId("123") 244 | .isLoginId(true) 245 | .addProperty("number1", 1234) 246 | .addProperty("date1", date) 247 | .addProperty("String1", "str") 248 | .addProperty("boolean1", false) 249 | .addProperty("list1", list) 250 | .addProperty("$track_id", 111) 251 | .build(); 252 | sa.profileSet(userRecord); 253 | } 254 | 255 | /** 256 | * 校验自定义属性格式是否正常 257 | */ 258 | @Test 259 | public void testProfileSetOnceDataTypeEventBuilder() throws InvalidArgumentException { 260 | List list = new ArrayList<>(); 261 | Date date = new Date(); 262 | list.add("aaa"); 263 | list.add("bbb"); 264 | UserRecord userRecord = UserRecord.builder() 265 | .setDistinctId("123") 266 | .isLoginId(true) 267 | .addProperty("number1", 1234) 268 | .addProperty("date1", date) 269 | .addProperty("String1", "str") 270 | .addProperty("boolean1", false) 271 | .addProperty("list1", list) 272 | .addProperty("$track_id", 111) 273 | .build(); 274 | sa.profileSetOnce(userRecord); 275 | } 276 | 277 | /** 278 | * 校验自定义属性格式是否正常 279 | */ 280 | @Test 281 | public void testProfileIncrementEventBuilder() throws InvalidArgumentException { 282 | List list = new ArrayList<>(); 283 | Date date = new Date(); 284 | list.add("aaa"); 285 | list.add("bbb"); 286 | UserRecord userRecord = UserRecord.builder() 287 | .setDistinctId("123") 288 | .isLoginId(true) 289 | .addProperty("number1", 1234) 290 | .addProperty("$track_id", 111) 291 | .build(); 292 | sa.profileIncrement(userRecord); 293 | } 294 | 295 | } 296 | --------------------------------------------------------------------------------