需求:
公司是打车的业务,埋点这块后续加的是代驾的业务,运营想要在一些关键的节点上进行事件分析,比如乘客、司机登录事件,代驾乘客下单,代驾乘客取消订单,代驾司机取消订单,司机接单、乘客确认支付、支付结果这些事件上添加数据分析,来分析事件发生的次数啥的。
过程
这个业务已经在顺风车引入了,后来其他同事就直接在相关事件后边加上了数据埋点,我是后来接手的,加了点公共属性做修改
如:
乘客下单,下单完成——使用@Async异步调用神策数据埋点推送入参,异步调用避免神策埋点报错导致主流程造成损害,神策入参拼接完成后,使用kafak生产者发送神策推送消息——》kafak消费者接收,使用神策的sdk提供的实体里的推送数据的方法发送数据——》根据配置累配置的接收神策数据推送的地址,查看数据推送结果,可以在这个web界面上进行事件分析,根据事件的属性进行分析
具体引入
1.直接引入神策数据埋点的sdk,即依赖
<dependency>
<groupId>com.sensorsdata.analytics.javasdk</groupId>
<artifactId>SensorsAnalyticsSDK</artifactId>
<version>3.1.16</version>
</dependency>
2.添加配置类
package com.wanshun.springboot;
import com.sensorsdata.analytics.javasdk.SensorsAnalytics;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SensorsApplication {
private String saServerUrl;
private Integer saBulkSize;
private Boolean throwException;
private Integer maxCacheSize;
public String getSaServerUrl() {
return saServerUrl;
}
@Value("${sensors.batch.saserverurl:")
public void setSaServerUrl(String saServerUrl) {
this.saServerUrl = saServerUrl;
}
public Integer getSaBulkSize() {
return saBulkSize;
}
@Value("${sensors.batch.sabulksize:5}")
public void setSaBulkSize(Integer saBulkSize) {
this.saBulkSize = saBulkSize;
}
public Boolean getThrowException() {
return throwException;
}
@Value("${sensors.batch.throwexception:false}")
public void setThrowException(Boolean throwException) {
this.throwException = throwException;
}
public Integer getMaxCacheSize() {
return maxCacheSize;
}
@Value("${sensors.batch.maxcachesize:5000}")
public void setMaxCacheSize(Integer maxCacheSize) {
this.maxCacheSize = maxCacheSize;
}
@Bean
public SensorsAnalytics sensorsAnalytics() {
// 从神策分析获取的数据接收的 URL,即运营或产品在web界面查看事件的数据分析平台
final String SA_SERVER_URL = getSaServerUrl();
// 当缓存的数据量达到50条时,批量发送数据
final int SA_BULK_SIZE = getSaBulkSize();
// 数据同步失败不抛出异常
final boolean THROW_EXCEPTION = getThrowException();
// 内存中数据最大缓存条数,如此值大于0,代表缓存的数据会有条数限制,最小 3000 条,最大 6000 条。否则无条数限制。
final int MAX_CACHE_SIZE = getMaxCacheSize();
// 使用 BatchConsumer 初始化 SensorsAnalytics
// 建议不要在任何线上的服务中使用此DebugConsumer【神策数据的引入文档上也有注明】
return new SensorsAnalytics(
// new SensorsAnalytics.DebugConsumer(SA_SERVER_URL, true)); //在正式环境不要用这个,因为这个debug模式,神策数据那边用的是神策那边的消费者,如果是debug模式,推送数据失败,程序就会报错,如果非debug模式,程序不会报错,但是可能对应的数据推送不过去,这个调试的时候用的话最好了
new SensorsAnalytics.BatchConsumer(SA_SERVER_URL, SA_BULK_SIZE, MAX_CACHE_SIZE, THROW_EXCEPTION));
}
}
3.下单事件异步添加神策埋点事件
controller
/**
* 车主端确认下单
*
* @param parmVO
* @param rpcPacket
* @return
*/
@UrlMapping(url = "addDrivingOrder")
public RpcPacket addDrivingOrder(RpcAddOrderDto parmVO, RpcPacket rpcPacket) {
logger.info("车主端确认下单,入参,RpcAddOrderDto {}", JsonUtil.toJson(parmVO));
RpcPacket packet = new RpcPacket();
try {
Long userId = rpcPacket.getId();
Integer areaCode = parmVO.getCurAddr().getAreaCode();
parmVO.setCarOwnerDevId(new Gson().fromJson(rpcPacket.getExtenData(), RpcPacketExtDto.class).getDeviceId());
String orderId = drivingOrderService.addDrivingOrder(userId, areaCode, parmVO);
Map<String, String> map = new HashMap<>();
map.put("orderId", orderId);
packet.setData(map);
packet.setAnwserCode(new AnwserCode(1, "车主端确认下单成功"));
} catch (ArgsException e) {
logger.error("车主端确认下单失败 {}", e.toString());
packet.setAnwserCode(e.getAnwserCode());
} catch (Exception e) {
logger.error("车主端确认下单失败 {}", e.toString());
packet.setAnwserCode(OrderServiceAnwserCode.BUSS_ERROR_ADDORDERFAIL);
}
logger.info("车主端确认下单,出参,RpcAddOrderDto {},入参 rpcPacket:{} RpcAddOrderDto:{}", JsonUtil.toJson(packet), JsonUtil.toJson(rpcPacket), JsonUtil.toJson(parmVO));
return packet;
}
service
@Override
public String addDrivingOrder(Long userId, Integer areaCode, RpcAddOrderDto parmVO) {
String lockName = "";
String orderId = "";
String lockKey = OrderServiceModulConstant.LOCK_ADD_ORDER_CAROWN_LOKENAME + parmVO.getOrderType() + ":" + userId;
try {
// logger.info("{} into -------------------init",Thread.currentThread().getId()+Thread.currentThread().getName());
lockName = redisCache.getLockLua(lockKey, OrderServiceModulConstant.LLOCK_ADD_ORDER_CAROWN_LOKE_TIMEOUT);
if (!StringUtil.isEmpty(lockName)) {
if (parmVO.getOrderType().equals(OrderAttributeConst.ORDERTYPE_SM)) {
orderId = addDrivingOrderForSMorder(userId, areaCode, parmVO);
} else {
orderId = addDrivingOrderForNoSMorder(userId, areaCode, parmVO);
}
// logger.info("{} lockeName&&&&&&&&&&&&&&& {}",Thread.currentThread().getId()+Thread.currentThread().getName(),lockName);
} else {
throw new ArgsException(OrderServiceAnwserCode.BUSS_ERROR_CAROWNER_ADDORDER_REPEAT_COMMIT);
}
redisCache.deleteKey(OrderServiceModulConstant.KEY_CAROWNER_PUBLIC_ORDER_COUNT_TODAY + userId);
return orderId;
} catch (ArgsException e) {
throw e;
} catch (Exception e) {
throw e;
} finally {
// logger.info("{} release################# {}",Thread.currentThread().getId()+Thread.currentThread().getName(),lockName);
redisCache.releaseLock(lockKey, lockName);
}
}
/**
* 新增预约单和实时单
*
* @param userId
* @param areaCode
* @param parmVO
* @return
*/
public String addDrivingOrderForNoSMorder(Long userId, Integer areaCode, RpcAddOrderDto parmVO) {
logger.info("预约单和实时单下单,RpcAddOrderDto {},userId {}", JsonUtil.toJson(parmVO), JsonUtil.toJson(userId));
if (!parmVO.getOrderType().equals(OrderAttributeConst.ORDERTYPE_YY)) {
parmVO.setAppointTime(MyUtils.getNow());
} else {// 加预约时间
DrivingBaseAlgorithmResultDto dto = rpcService.getScheduleConfig(parmVO.getStartAddr().getAreaCode() / 100 * 100, parmVO.getOrderType());
if (dto != null && dto.getSubscribeBaseRule() != null && dto.getSubscribeBaseRule().getReservationNumber() > 0) {
long current = System.currentTimeMillis() / (1000 * 3600 * 24) * (1000 * 3600 * 24) - TimeZone.getDefault().getRawOffset();
current = current / 1000;
long zero = current + dto.getSubscribeBaseRule().getReservationNumber() * 24 * 3600;
if (parmVO.getAppointTime().compareTo(zero) == 1) {
throw new ArgsException(new AnwserCode(19984, "新增预约单,预约时间不能超过" + dto.getSubscribeBaseRule().getReservationNumber() + "天"));
}
} else {
throw new ArgsException(OrderServiceAnwserCode.INTERFACE_ERROR_RPC_GETSCHEDULECONFIG);
}
}
//加费用--车主的费用 与包干司机、非包干司机费用比较 不合理就抛"后台系统数据异常"
if (!checkEstmastExpenseCompare(parmVO.getChannelId(),parmVO.getOrderType(), parmVO.getStartAddr().getAreaCode() / 100 * 100, parmVO.getAppointTime(), parmVO.getEstDuration(), parmVO.getEstDistance())) {
throw new ArgsException(OrderServiceAnwserCode.BUSS_ERROR_ADD_ORDER_COMPARE_EXPENSE_ERROR);
}
checkCrossCityAddOrder(parmVO.getStartAddr(), parmVO.getEndAddr(), parmVO.getOrderType());
checkAddOrderMaxCountForCarOwner(userId, parmVO);
checkAddOrderIsTimeConflict(userId, parmVO);
//<editor-fold desc="赋值转换">
String orderId = getOrderId(parmVO);
DrivingOrderDo drivingOrderDo = new DrivingOrderDo();
String riderNick = parmVO.getRiderNick();
if(!StringUtil.isEmpty(riderNick)){
riderNick = riderNick.trim();
}
drivingOrderDo.setRiderNick(riderNick);
CarOwnerInfoInDto carOwnerInfoInDto = new CarOwnerInfoInDto();
carOwnerInfoInDto.setId(userId);
String carOwnerPhone = null;
List<CarOwnerInfoOutDto> carOwnerInfoOutDtos = rpcCarOwnerInfoService.getCarOwnerInfo(carOwnerInfoInDto);
if(CollectionUtils.isNotEmpty(carOwnerInfoOutDtos)){
CarOwnerInfoOutDto carOwnerInfoOutDto = carOwnerInfoOutDtos.get(0);
carOwnerPhone = carOwnerInfoOutDto.getPhone();
}
if(parmVO.getIsForOther() == null || parmVO.getIsForOther() == 0){
drivingOrderDo.setRiderPhone(carOwnerPhone);
}else{
drivingOrderDo.setRiderPhone(parmVO.getRiderPhone());
}
if(drivingOrderDo.getRiderPhone().equals(carOwnerPhone)){
drivingOrderDo.setIsForOther(0);
}else{
drivingOrderDo.setIsForOther(1);
}
drivingOrderDo.setCarOwnerName(rpcService.getCarOwnerInfo(userId).getName());
drivingOrderDo.setOrderType(parmVO.getOrderType());
drivingOrderDo.setCarOwnerId(userId);
drivingOrderDo.setOrderId(orderId);
drivingOrderDo.setStartAddr(JsonUtil.toJson(parmVO.getStartAddr()));
drivingOrderDo.setEndAddr(JsonUtil.toJson(parmVO.getEndAddr()));
drivingOrderDo.setResource(parmVO.getResource());
drivingOrderDo.setStartCity(Integer.toString(parmVO.getStartAddr().getAreaCode() * 1 / 100 * 100));
drivingOrderDo.setAreaCode(areaCode);
drivingOrderDo.setCityCode(areaCode * 1 / 100 * 100);
drivingOrderDo.setProvinceCode(areaCode * 1 / 10000 * 10000);
drivingOrderDo.setCreateTime(MyUtils.getNow());
drivingOrderDo.setUpdateTime(drivingOrderDo.getCreateTime());
drivingOrderDo.setIsException(1);
drivingOrderDo.setVersion(1);
drivingOrderDo.setChannelId(parmVO.getChannelId());
// drivingOrderDo.setWaitingTime(0L);
drivingOrderDo.setAppointTime(drivingOrderDo.getCreateTime());
if (parmVO.getOrderType().equals(OrderAttributeConst.ORDERTYPE_YY)) {
drivingOrderDo.setAppointTime(parmVO.getAppointTime());
}
OrderOtherProperty otherPro = new OrderOtherProperty();
otherPro.setPulibAddr(parmVO.getCurAddr());
otherPro.setCarOwnerDev(parmVO.getCarOwnerDevId());
otherPro.setDriverDev(parmVO.getDriverDevId());
drivingOrderDo.setOtherProperty(new Gson().toJson(otherPro));
drivingOrderDo.setStatus(OrderAttributeConst.ORDER_STATUS_DISPATCHING);
drivingOrderMapper.insert(drivingOrderDo);
//保存乘车人标签记录
try {
if (drivingOrderDo.getIsForOther() != null && drivingOrderDo.getIsForOther() == 1) {
riderTagMapper.insert(RiderTagDo.builder().carOwnerId(userId).riderNick(parmVO.getRiderNick())
.riderPhone(parmVO.getRiderPhone()).createTime(new Date()).updateTime(new Date()).build());
}
}catch (Exception e){
logger.error("save rider tags fail userId {} orderId {} exc {}" , userId , orderId , e.toString());
}
//</editor-fold>
DrivingOrderEstimateDo estDo = new DrivingOrderEstimateDo();
estDo.setOrderId(drivingOrderDo.getOrderId());
estDo.setEstArrivTime(0L);
estDo.setEstDistance(parmVO.getEstDistance());
estDo.setEstDuration(parmVO.getEstDuration());
estDo.setEstMoney(parmVO.getEstMoney());
estDo.setCreateTime(drivingOrderDo.getCreateTime());
estDo.setUpdateTime(drivingOrderDo.getCreateTime());
drivingOrderEstimateMapper.insert(estDo);
//调用调度接口将订单状态发送给腾讯
this.modifySyncOrderStatus(drivingOrderDo);
try{
//神策埋点事件
sensorsBurialSiteService.getAddPassengerOrderSensorcParams(drivingOrderDo , orderId , parmVO.getEstMoney());
}catch (Exception ex){
logger.error("神策埋点 创建订单失败 orderId {}" , orderId);
}
return drivingOrderDo.getOrderId();
}
sensorsBurialSiteServiceImpl
/**
* 乘客创建订单
* @param drivingOrderDo 乘客下单入参
* @param passengerOrderId 乘客订单号
* @param prePayFee 预估费
*/
@Async
@Override
public void getAddPassengerOrderSensorcParams(DrivingOrderDo drivingOrderDo, String passengerOrderId, Integer prePayFee){
logger.info("乘客创建订单神策事件入参,drivingOrderDo:{},passengerOrderId:{},prePayFee:{}",JsonUtil.toJson(drivingOrderDo),passengerOrderId,prePayFee);
SensorsAo sensorsAo = new SensorsAo();
Map<String,Object> param = new HashMap<>();
//添加公共属性
addBasicParam(drivingOrderDo, param);
// 事件名称
sensorsAo.setEventName("BuildOrder");
// 是否成功
param.put("is_success",true);
// 订单类型
param.put("order_type",String.valueOf(drivingOrderDo.getOrderType()));
// 失败原因
param.put("fail_reason","暂无");
// 业务类型
param.put("service_type","800");
// 订单ID
param.put("order_id", passengerOrderId);
// 订单起点
if(StringUtils.isNotEmpty(drivingOrderDo.getStartAddr())) {
RpcAddrPointDto startAddress = JsonUtil.fromJson(drivingOrderDo.getStartAddr(), RpcAddrPointDto.class);
param.put("order_departure", startAddress.getPoiName());
}
// 订单终点
if(StringUtils.isNotEmpty(drivingOrderDo.getEndAddr())) {
RpcAddrPointDto endAddress = JsonUtil.fromJson(drivingOrderDo.getEndAddr(), RpcAddrPointDto.class);
param.put("order_destination", endAddress.getPoiName());
}
// 订单预估价
param.put("order_estimateprice", prePayFee);
// 是否预付费订单
param.put("is_payed",false);
param.put("scan_order_type" , String.valueOf(drivingOrderDo.getOrderType()));
param.put("valet_driver_type" , String.valueOf(drivingOrderDo.getIsForOther()));
// 用户id
sensorsAo.setUserId(drivingOrderDo.getCarOwnerId());
// 订单id
sensorsAo.setOrderId(passengerOrderId);
// 推送参数
sensorsAo.setParam(param);
sensorsBurialSiteClusterEventProducer.sendSensorsEnlistEvent(sensorsAo);
}
/**
* 添加神策事件的公共属性
* @param drivingOrderDo
* @param param
* @return
*/
public Map<String,Object> addBasicParam(DrivingOrderDo drivingOrderDo,Map<String,Object> param){
RpcLocalCityVo nameByCode = rpcLocalCityService.getNameByCode(drivingOrderDo.getCityCode());
logger.info("根据cityCode获取城市名称出参,nameByCode:{}",JsonUtil.toJson(nameByCode));
if(nameByCode.getAnwserCode().getCode() == 1 && StringUtils.isNotEmpty(nameByCode.getName())) {
//获取所在城市名称
param.put("address", nameByCode.getCode() + nameByCode.getName());
}
//公共属性,平台类型,应用名称
if(drivingOrderDo.getResource() != null && (drivingOrderDo.getResource() == 1 || drivingOrderDo.getResource() == 2)){
param.put("platform_type", "app");
param.put("app_name", "万顺叫车");
}else if(drivingOrderDo.getResource() == 3){
param.put("platform_type", "小程序");
param.put("app_name", "万顺叫车代驾小程序");
}
//是否为登录状态
param.put("is_login", "true");
return param;
}
kafka生产者
package com.wanshun.order.cluster.producer;
import com.wanshun.common.utils.JsonUtil;
import com.wanshun.constants.platform.daijiaservice.DaiJiaKafkaTopicConstants;
import com.wanshun.net.cluster.ClusterEventBusProducer;
import com.wanshun.net.cluster.metadata.ClusterEvent;
import com.wanshun.net.kafka.KafkaConfig;
import com.wanshun.rpcao.SensorsAo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 神策埋点kafka消息
* @author htc
*/
public class SensorsBurialSiteClusterEventProducer {
public static Logger logger = LoggerFactory.getLogger(SensorsBurialSiteClusterEventProducer.class);
private ClusterEventBusProducer producer;
public SensorsBurialSiteClusterEventProducer(KafkaConfig kafkaConfig) {
producer = ClusterEventBusProducer.getClusterEventBusProducer();
producer.init(kafkaConfig);
producer.addTopic(DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC, false);
}
/**
* 神策埋点推送消息
* @param sensorsAo 事件类
* @return 处理结果
*/
public boolean sendSensorsEnlistEvent(SensorsAo sensorsAo) {
ClusterEvent clusterEvent = new ClusterEvent();
String topic = DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC;
try {
clusterEvent.setBalanceId(sensorsAo.getOrderId());
clusterEvent.setData(JsonUtil.toJson(sensorsAo));
clusterEvent.setClusterEventType(DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC_EVENTTYPE_SENSORS);
boolean result = producer.publishImportantEvent(topic, clusterEvent);
if (!result) {
logger.info("神策埋点 推送消息, 但发送消息时,kafka发送异常 " +
"orderId:{}", sensorsAo.getOrderId());
throw new RuntimeException("发送事件异常");
}else{
logger.info("神策埋点 推送消息,发送成功 orderId {}" , sensorsAo.getOrderId());
}
}catch (Exception e){
logger.error(e.getMessage(), e);
}
return true;
}
}
kafk消费者S
package com.wanshun.cluster;
import com.alibaba.fastjson.JSON;
import com.sensorsdata.analytics.javasdk.SensorsAnalytics;
import com.wanshun.common.utils.JsonUtil;
import com.wanshun.constants.platform.daijiaservice.DaiJiaKafkaTopicConstants;
import com.wanshun.constants.platform.thirdplatform.ThirdplatformModuleConstant;
import com.wanshun.net.cluster.ClusterEventBusConsumer;
import com.wanshun.net.cluster.ClusterEventSubscribe;
import com.wanshun.net.cluster.listener.ClusterEventBusListener;
import com.wanshun.net.cluster.metadata.ClusterEvent;
import com.wanshun.net.kafka.KafkaConfig;
import com.wanshun.rpcao.SensorsAo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.annotation.Transactional;
import java.util.Set;
public class SensorsConsumer extends ClusterEventBusListener implements ApplicationContextAware {
public final static Logger logger = LoggerFactory.getLogger(SensorsConsumer.class);
@Autowired
private SensorsAnalytics sa;
private final KafkaConfig kafkaConfig;
private boolean isInit = false;
public SensorsConsumer(KafkaConfig kafkaConfig) {
this.kafkaConfig = kafkaConfig;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (!isInit) {
synchronized (this) {
if (!isInit) {
isInit = true;
ClusterEventSubscribe subscribe = new ClusterEventSubscribe();
subscribe.addTopic(false, false, DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC);
ClusterEventBusConsumer consumer = ClusterEventBusConsumer.getClusterEventBusConsumer(ThirdplatformModuleConstant.GROUP_NAME_SERVER, kafkaConfig);
consumer.init(this, subscribe);
}
}
}
logger.info("三方服务kafka消费端启动");
}
@Override
public boolean checkCanStartConsumer(String topicName) {
if ( DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC.equals(topicName)) {
return true;
}
return false;
}
@Transactional
@Override
public void eventNotify(String topicName, ClusterEvent clusterEvent) {
logger.info("神策推送数据kafka消费事件: {}, topicName: {}", JSON.toJSONString(clusterEvent), topicName);
if ( DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC.equals(topicName) &&
DaiJiaKafkaTopicConstants.THIRDPLAT_SERVICE_OUTER_TOPIC_EVENTTYPE_SENSORS == clusterEvent.getClusterEventType()) {
String data = clusterEvent.getData();
SensorsAo sensorsAo = JSON.parseObject(data, SensorsAo.class);
try {
logger.info("神策推送数据入参,sensorsAo:{}", JsonUtil.toJson(sensorsAo));
sa.track(sensorsAo.getUserId().toString(), sensorsAo.isFlag(), sensorsAo.getEventName(), sensorsAo.getParam());
sa.flush();
} catch (Exception e) {
logger.error("hotfix神策推送数据失败,data:{},sensorsAo:{}", data, JSON.toJSONString(sensorsAo), e);
}
logger.info("神策推送数据kafka消费事件: {}, topicName: 成功{}", JSON.toJSONString(clusterEvent), topicName);
}
}
@Override
public void notifyPartition(String topicName, Set<Integer> partitionSet) {
}
@Override
public void destroyPartition(String topicName, Set<Integer> partitionSet) {
}
}