Commit 9cac9e22 authored by dingjy's avatar dingjy

submit

parents
/.idea/
/target/
FROM harbor.ibreader.com/base/openjdk:8u342-jdk
WORKDIR /app
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ADD target/dmp-engine.jar app.jar
ENV JAVA_OPTS="-server -Xms2G -Xmx2G -Xmn1G -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=128m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=5 -XX:+ExplicitGCInvokesConcurrent -XX:SurvivorRatio=7 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logservice/dump"
ENTRYPOINT [ "/bin/sh", "-c", "java $JAVA_OPTS -Dspring.profiles.active=$APP_ENV -Djava.security.egd=file:/dev/./urandom -jar app.jar" ]
\ No newline at end of file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lwby</groupId>
<artifactId>marketing</artifactId>
<version>0.01</version>
<packaging>jar</packaging>
<name>marketing</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.7</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>javax.persistence</groupId>-->
<!-- <artifactId>persistence-api</artifactId>-->
<!-- <version>1.0.2</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.oceanbase</groupId>-->
<!-- <artifactId>oceanbase-client</artifactId>-->
<!-- <version>2.4.7.1</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.8.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.1</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>marketing</finalName>
</build>
<distributionManagement>
<repository>
<id>lwby-releases</id>
<name>releases repository</name>
<url>http://maven.bayread.com/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>lwby-snapshots</id>
<name>snapshots repository</name>
<url>http://maven.bayread.com/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>
package com.lwby.marketing;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@EnableCreateCacheAnnotation
@EnableMethodCache(basePackages = "com.lwby.marketing")
@SpringBootApplication
@RestController
@EnableAsync
@Slf4j
@Component
public class Main {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
SpringApplication.run(Main.class, args);
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel;
public enum AttributionStatus {
ACTIVE_CALLBACK(2,"激活回传"),
STORE_CALLBACK(3,"商店"),
NORMAL_DEDUCTION_CALLBACK(4,"正常扣量"),
OLDUSER_DEDUCTION_CALLBACK(7,"老用户扣量"),
IP_CALLBACK(9,"IP归因");
final int id;
final String desc;
AttributionStatus(int id,String desc){
this.id = id;
this.desc = desc;
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel;
public enum AttributionType {
CHANNEL("channel"),PLAN("plan"),CROSS_PLATFORM("cross_platform");
private String name;
private AttributionType(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public int select(int channelId,int planId){
switch (this){
case PLAN:
return planId;
case CHANNEL:
return channelId;
}
return -1;
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel;
import com.lwby.marketing.vo.ClientInfo;
import org.apache.commons.lang3.StringUtils;
import java.util.function.Function;
public enum DeviceType {
IMEI("imei",(c) -> isNotEmptyAndSNull(c.getImei())?c.getImei():null),
OAID("oaid", (c) -> isNotEmptyAndSNull(c.getOaid())?c.getOaid():null),
IDFA("idfa",(c) -> isNotEmptyAndSNull(c.getIdfa())?c.getIdfa():null),
IP_UA("ipua",(c) -> isNotEmptyAndSNull(c.getClientIp()) && isNotEmptyAndSNull(c.getUa())?c.getClientIp().concat(StringUtils.substringBefore(c.getUa(), " Chrome/")):null),
IP_MODEL("ipmodel",(c) -> isNotEmptyAndSNull(c.getClientIp()) && isNotEmptyAndSNull(c.getPhoneModel())?c.getClientIp().concat(c.getPhoneModel()):null),
IP("ip",(c) -> isNotEmptyAndSNull(c.getClientIp())?c.getClientIp():null);
private String value;
private Function<ClientInfo,String> fun;
DeviceType(String value,Function<ClientInfo,String> fun) {
this.value = value;
this.fun = fun;
}
public String getValue() {
return this.value;
}
public String getDeviceId(ClientInfo clientInfo){
return fun.apply(clientInfo);
}
private static boolean isNotEmptyAndSNull(String str) {
return StringUtils.isNotEmpty(str) && !"null".equals(str);
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel;
import com.alibaba.fastjson2.JSONObject;
import com.lwby.marketing.flow.FlowExecutor;
import com.lwby.marketing.flow.Rule;
import com.lwby.marketing.vo.BookStoreEvent;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.context.ApplicationContext;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Component
public class NovelAttributionMain {
@Resource
ApplicationContext ctx;
@Resource
private KafkaTemplate<String, String> kafkaTemplate;
FlowExecutor<NovelAction> executorNovel;
@PostConstruct
public void init() {
executorNovel = new FlowExecutor<>(ctx, Rule.create().THEN("setup").THEN("prize").THEN("store").THEN("checkerfirst").SWITCH("cross","plan","channel"));
//test();
}
public void test(){
String msg = "{\"bookStoreEvent\":1,\"clientInfo\":{\"channel\":220605094,\"clientIp\":\"171.14.28.126\",\"dID\":\"aad1da5913749ee5\",\"ddid\":\"null\",\"firm\":\"HUAWEI\",\"fixVersion\":12,\"mainVersion\":2,\"oaid\":\"f243f86a-8887-44df-92f3-7efed504b6f7\",\"os\":\"0\",\"phoneModel\":\"NOH-AN00\",\"pkv\":1,\"platformGroupId\":6,\"platformId\":6,\"pm\":\"NOH-AN00\",\"screenSize\":\"1152*2256\",\"sessionid\":\"wnL6tSDnvCjwnLtNJRDK299Z_jQEkUqSj\",\"signVersion\":2,\"subVersion\":27,\"systemVersion\":\"12\",\"ua\":\"Mozilla/5.0 (Linux; Android 12; NOH-AN00 Build/HUAWEINOH-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Mobile Safari/537.36\",\"user\":{\"career\":\"\",\"channel\":220605094,\"deviceId\":\"aad1da5913749ee5\",\"experience\":1,\"gender\":\"M\",\"headImage\":\"http://cdn.ibreader.com/group1/M00/56/07/rBH0olunjiKAJCYCAAAQJEqQEcM308.png\",\"id\":133842748,\"isKaiqiUser\":false,\"isMajia\":false,\"isPrivate\":false,\"lastLogin\":1705980630000,\"level\":0,\"mainversion\":2,\"nickname\":\"书友742606\",\"platformId\":6,\"registrationDate\":1705980630000,\"subversion\":27,\"userStatus\":0,\"username\":\"bduXujqDcJbCZD\"},\"version\":\"6.2.27.12.220605094\",\"visitor\":\"wnL6tSDnvCjwnLtNJRDK299Z_jQEkUqSj\",\"xClient\":\"dID=aad1da5913749ee5;os=0;firm=HUAWEI;webVersion=new;version=6.2.27.12.220605094;username=wnL6tSDnvCjwnLtNJRDK299Z_jQEkUqSj;ddid=null;sv=12;pm=NOH-AN00;ss=1152*2256;signVersion=2;androidosv=31;oaid=f243f86a-8887-44df-92f3-7efed504b6f7;pkv=1;\"},\"createTime\":1705980632408,\"extraData\":{},\"id\":\"fd17b7d0-2ec2-4392-8980-170dbdf36e69\"}";
BookStoreEvent event = JSONObject.parseObject(msg, BookStoreEvent.class);
NovelAction action = new NovelAction(event.getClientInfo(),msg);
try {
executorNovel.execute(action);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// @KafkaListener(topics = "registerMediaEvent1", groupId = "test")
// public void listen(String message) {
// System.out.println("Received Message: " + message);
//
// BookStoreEvent event = JSONObject.parseObject(message, BookStoreEvent.class);
// NovelAction action = new NovelAction(event.getClientInfo(),message);
//
// try {
// executorNovel.execute(action);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
}
package com.lwby.marketing.att.novel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import com.lwby.marketing.po.AppChannel;
import com.lwby.marketing.vo.NovelAction;
import com.lwby.marketing.vo.DeliveryDeviceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class UniversalProcess {
public static final String PRIZE_FLOW_PREFIX = "c:ouser:";
@Resource
JdbcTemplate lwbyJdbcTemplate;
@Resource
private KafkaTemplate<String, String> kafkaTemplate;
@Resource
private RedisTemplate<String, String> redisTemplate;
/******************************************** GENERIC METHOD ***********************************************/
/**
* 通知处理结果
*/
public void notifyResult(NovelAction action, AttributionStatus status) {
DeliveryDeviceInfo ddi = action.getDeliveryDeviceInfo();
if (Objects.isNull(ddi)) {
ddi = new DeliveryDeviceInfo();
String channelStr = String.valueOf(action.getChannelId());
ddi.setMedia(MediaMapping.getMediaNameByChannelId(channelStr));
ddi.setAd_plan_id("0");
ddi.setAd_group_id("0");
ddi.setAd_creative_id("0");
ddi.setPlatform_id(String.valueOf(action.getPlatformId()));
ddi.setDj_channel(channelStr);
}
ddi.setOs(action.getClientInfo().getOs());
ddi.setIs_call(status.id);
ddi.setDevice_id(action.getDeviceId());
ddi.setActive_time(System.currentTimeMillis());
ddi.setChannel(String.valueOf(action.getChannelId()));
ddi.setUserId(action.getUserId());
ddi.setDevice_status(exists(PRIZE_FLOW_PREFIX.concat(String.valueOf(ddi.getUserId()))) ? 2 : 0);
String jsonString = JSONObject.toJSONString(ddi);
ListenableFuture<SendResult<String, String>> active_result = kafkaTemplate.send("ocpc_result_test", jsonString);
active_result.addCallback(
result -> log.info("归因成功[{}],归因类型[{}]", jsonString, status.desc),
ex -> log.error("归因失败[{}],归因类型[{}]", jsonString, status.desc, ex)
);
}
/******************************************** ASSEMBLE KEY ***************************************************/
public String getTotalCountKey(AttributionType attributionType, int platformId, int channelOrPlanId, int sprDedu, String dateStr) {
return String.format("%s_total_%d_%d_%d_%s", attributionType, platformId, channelOrPlanId, sprDedu, dateStr);
}
public String getCallbackCountKey(AttributionType attributionType, int platformId, int channelOrPlanId, int sprDedu, String dateStr) {
return String.format("%s_callback_%d_%d_%d_%s", attributionType, platformId, channelOrPlanId, sprDedu, dateStr);
}
public String getFirstCheckerKey(NovelAction action) {
return String.format("fc_%s_%d_%s_%s", action.getDeviceId(), action.getPlatformId(),action.getMediaName(),action.getCurrentDateStr());
}
/******************************************** REDIS **********************************************************/
public boolean exists(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public void set(String key, int expires, String value) {
redisTemplate.opsForValue().set(key,value,expires, TimeUnit.SECONDS);
}
public <T> T get(Class<T> clazz, String key) {
String value = redisTemplate.opsForValue().get(key);
if(!Objects.isNull(value)){
return JSON.parseObject(value,clazz);
}
return null;
}
public long incrby(String key, int increment, int expireSecond) {
Long v = redisTemplate.opsForValue().increment(key,increment);
redisTemplate.expire(key,expireSecond, TimeUnit.SECONDS);
return Objects.isNull(v)?0: v;
}
public long incrby(String key, int increment) {
return redisTemplate.opsForValue().increment(key,increment);
}
/******************************************** JDBC *************************************************************/
public AppChannel getAppChannel(int platformId, int channelId){
return lwbyJdbcTemplate.queryForObject(String.format("select * from app_channel where platform_id=%d and channel_id=%d ORDER BY update_time DESC LIMIT 1",platformId,channelId),AppChannel.class);
}
/******************************************** INNER CLASS ******************************************************/
/**
* 媒体ID和名称关系映射
*/
static class MediaMapping{
static Map<String, String> mediaMap = ImmutableMap.<String, String>builder()
.put("11", "xiaomi")
.put("12", "huawei")
.put("13", "oppo")
.put("14", "vivo")
.put("15", "yingyongbao")
.put("16", "meizu")
.put("17", "360zhushou")
.put("18", "baiduzhushou")
.put("19", "other")
.put("21", "jinritoutiao")
.put("22", "guangdiantong")
.put("23", "kuaishou")
.put("24", "uc")
.put("25", "qutoutiao")
.put("28", "youdao")
.put("33", "dystore")
.build();
static String getMediaNameByChannelId(String channelId){
return mediaMap.get(channelId.substring(0, 2));
}
}
}
package com.lwby.marketing.att.novel.handle;
import com.lwby.marketing.att.novel.AttributionStatus;
import com.lwby.marketing.att.novel.AttributionType;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeSwitchFlow;
import com.lwby.marketing.po.AppChannel;
import com.lwby.marketing.util.DateTimUtils;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.ThreadLocalRandom;
@Component("channel")
public class ChannelAttributionFlow extends NodeSwitchFlow<NovelAction> {
@Resource
JdbcTemplate marketingJdbcTemplate;
@Resource
UniversalProcess up;
@Override
public boolean checked(NovelAction action) {
return true;
}
@Override
public void process(NovelAction action) {
process0(action, AttributionType.CHANNEL);
}
public void process0(NovelAction action, AttributionType type) {
//是否是当天注册用户
boolean isNewUser = DateTimUtils.isCurrentDayTime(action.getRegistrationDate());
//获取满意配置
AppChannel appChannel = up.getAppChannel(action.getPlatformId(), action.getChannelId());
//等于空 或 100直接回传
if (appChannel == null || appChannel.getSprDedu() == 100) {
//TODO;channelTotalCount 和 channelCallbackCount不加吗
action.getMedia().callback(action);
up.notifyResult(action, AttributionStatus.ACTIVE_CALLBACK);
up.set(up.getFirstCheckerKey(action),60 * 60 * 24,"1");
return;
}
//如果是20 就是回传 20
Integer sprDedu = appChannel.getSprDedu();
//总数
String channelTotal = up.getTotalCountKey(type, action.getPlatformId(), action.getChannelId(), sprDedu, action.getCurrentDateStr());
//回传
String channelCallback = up.getCallbackCountKey(type, action.getPlatformId(), action.getChannelId(), sprDedu, action.getCurrentDateStr());
long channelTotalCount = up.incrby(channelTotal, 0, 60 * 60 * 24);
long channelCallbackCount = up.incrby(channelCallback, 0, 60 * 60 * 24);
up.incrby(channelTotal, 1);
//TODO;判断1-30天内是否活跃老用户,如果是则 100% 扣量;
if(!isNewUser && isAliveByDay(action.getUserId(),30)){
//TODO;扣量
up.notifyResult(action,AttributionStatus.OLDUSER_DEDUCTION_CALLBACK);
}else {
if (isCallback(channelTotalCount, channelCallbackCount, sprDedu)) {
//回传,回传个数 + 1
up.incrby(channelCallback, 1);
action.getMedia().callback(action);
up.notifyResult(action, AttributionStatus.ACTIVE_CALLBACK);
} else {
up.notifyResult(action, AttributionStatus.NORMAL_DEDUCTION_CALLBACK);
}
}
up.set(up.getFirstCheckerKey(action),60 * 60 * 24,"1");
}
private boolean isCallback(long channelTotalCount, long channelCallbackCount, Integer sprDedu) {
if (channelTotalCount == 0) {
//首次随机
return ThreadLocalRandom.current().nextInt(1, 3) == 1;
}
//计算回传率
BigDecimal divide = new BigDecimal(channelCallbackCount).divide(new BigDecimal(channelTotalCount), 4, RoundingMode.HALF_UP);
//比较回传率和扣量比例,决定是否回传
return divide.compareTo(BigDecimal.valueOf(sprDedu.doubleValue()).setScale(4, RoundingMode.HALF_UP)) != 1;
}
/**
* 用户在指定天数内是否有活跃
* @param userId
* @param day
* @return
*/
public boolean isAliveByDay(long userId, int day){
return marketingJdbcTemplate.queryForMap(String.format("select 1 from lwby_marketing_growth.alive_olduser where user_id ='%d' and last_alive_date >= CURDATE() - INTERVAL %d DAY",userId,day)).size() > 0;
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.handle;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeFlow;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("checkerfirst")
public class CheckerFirstFlow extends NodeFlow<NovelAction> {
@Resource
UniversalProcess up;
@Override
public void process(NovelAction action) {
if(up.exists(up.getFirstCheckerKey(action))){
action.stop(true);
}
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.handle;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.lwby.marketing.att.novel.AttributionStatus;
import com.lwby.marketing.att.novel.AttributionType;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeSwitchFlow;
import com.lwby.marketing.po.AppChannel;
import com.lwby.marketing.po.CrossCallback;
import com.lwby.marketing.util.DateTimUtils;
import com.lwby.marketing.vo.NovelAction;
import com.lwby.marketing.vo.CrossPlatformAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
@Component("cross")
public class CrossPlatformAttributionFlow extends NodeSwitchFlow<NovelAction> {
@Resource
JdbcTemplate lwbyJdbcTemplate;
@Resource
UniversalProcess up;
@Override
public boolean checked(NovelAction action) {
//获取跨平台帐号配置
CrossPlatformAccount cpa = getCrossPlatformMedia().get(action.getMediaName());
//判断是否配置跨平台归因,如果没有则继续执行下一个计划归因处理器
if(Objects.isNull(cpa) || !cpa.getAccount().contains(action.getAdvertiserId())){
return false;
}
action.setCpa(cpa);
return true;
}
@Override
public void process(NovelAction action) {
AppChannel appChannel = up.getAppChannel(action.getPlatformId(), action.getChannelId());
//等于空 或 100直接回传
if (appChannel == null || appChannel.getSprDedu() == 100) {
// callbackService.registerCallback(action);
action.getMedia().callback(action);
up.notifyResult(action, AttributionStatus.ACTIVE_CALLBACK);
up.set(up.getFirstCheckerKey(action),60 * 60 * 24,"1"); //每天扣量过一次,不在重复走
return;
}
//如果是20% 就是回传20%
Integer sprDedu = appChannel.getSprDedu();
//总数
String channelTotal = up.getTotalCountKey(AttributionType.CROSS_PLATFORM, action.getPlatformId(), action.getChannelId(), sprDedu, action.getCurrentDateStr());
//回传
String channelCallback = up.getCallbackCountKey(AttributionType.CROSS_PLATFORM, action.getPlatformId(), action.getChannelId(), sprDedu, action.getCurrentDateStr());
long channelTotalCount = up.incrby(channelTotal, 0, 60 * 60 * 24);
long channelCallbackCount = up.incrby(channelCallback, 0, 60 * 60 * 24);
up.incrby(channelTotal,1);
if(isCallback(action,getUserType(action),sprDedu,channelTotalCount,channelCallbackCount)){
up.incrby(channelCallback,1);
//callbackService.registerCallback(action);
action.getMedia().callback(action);
up.notifyResult(action,AttributionStatus.ACTIVE_CALLBACK);
}else{
up.notifyResult(action,AttributionStatus.NORMAL_DEDUCTION_CALLBACK);
}
up.set(up.getFirstCheckerKey(action),60 * 60 * 24,"1"); //每天扣量过一次,不在重复走
}
/**
* 是否回传
* @param action
* @param userType
* @param sprDedu
* @param channelTotalCount
* @param channelCallbackCount
* @return
*/
private boolean isCallback(NovelAction action, UserType userType, Integer sprDedu, long channelTotalCount, long channelCallbackCount) {
if (userType == UserType.NEW) {
// 纯新用户逻辑(回传)
return true;
} else if (userType == UserType.INTERMEDIATE) {
// 中间用户逻辑(扣量)
return false;
} else {
// 老用户逻辑
if (channelTotalCount == 0) {
//首次随机
return ThreadLocalRandom.current().nextInt(1, 3) == 1;
} else {
//计算
BigDecimal divide = BigDecimal.valueOf(channelCallbackCount).divide(BigDecimal.valueOf(channelTotalCount), 4, RoundingMode.HALF_UP);
//扣量
return divide.compareTo(BigDecimal.valueOf(sprDedu.doubleValue()).setScale(4, RoundingMode.HALF_UP)) != 1;
}
}
}
/**
* 取得用户类型(新、中、老)
* @param action
* @return
*/
private UserType getUserType(NovelAction action) {
UserType userType = UserType.OLD;
long daysSinceRegistration = 0;
if (DateTimUtils.isCurrentDayTime(action.getRegistrationDate())) {
// 当日注册新用户
daysSinceRegistration = DateTimUtils.calculateDaysBetweenDates(getEarliestRegistrationTimeByDeviceId(action.getDeviceId(),action.getPlatformId()), new Date());
int spanCheckMaxDay = action.getCpa().getSpanCheckMaxDay();
if (daysSinceRegistration < spanCheckMaxDay) {
userType = UserType.NEW;
} else if (daysSinceRegistration>=spanCheckMaxDay && daysSinceRegistration<=150) {
userType = UserType.INTERMEDIATE;
}
} else {
// 非当日注册用户
daysSinceRegistration = DateTimUtils.calculateDaysBetweenDates(action.getRegistrationDate(), new Date());
if (daysSinceRegistration<150) {
userType = UserType.INTERMEDIATE;
}
}
return userType;
}
/**
* 获取跨包配置
* @return
*/
@Cached(name="cross_platform_account", cacheType = CacheType.LOCAL)
@CacheRefresh(refresh = 300)
public Map<String,CrossPlatformAccount> getCrossPlatformMedia(){
List<CrossCallback> ls = lwbyJdbcTemplate.queryForList("select id,span_check_max_day,new_account,old_account,media_name from cross_callback", CrossCallback.class);
return ls.stream()
.collect(Collectors.toMap(CrossCallback::getMediaName, CrossPlatformAccount::new));
}
/**
* 获取设备ID的最早注册时间
* @return
*/
public Date getEarliestRegistrationTimeByDeviceId(String deviceId,Integer platformId){
Date date = lwbyJdbcTemplate.queryForObject(String.format("select max(registration_date) from user_profiles where platform_id != %d and device_id = '%s'",platformId,deviceId),Date.class);
return Objects.isNull(date)?new Date():date;
}
enum UserType{
NEW,INTERMEDIATE,OLD;
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.handle;
import cn.hutool.crypto.SecureUtil;
import com.lwby.marketing.att.novel.DeviceType;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeFlow;
import com.lwby.marketing.att.novel.media.Media;
import com.lwby.marketing.util.DateTimUtils;
import com.lwby.marketing.util.NumberUtils;
import com.lwby.marketing.vo.NovelAction;
import com.lwby.marketing.vo.ClientInfo;
import com.lwby.marketing.vo.DeliveryDeviceInfo;
import com.lwby.marketing.vo.UserProfile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
@Slf4j
@Component("setup")
public class ParameterSetupFlow extends NodeFlow<NovelAction> {
@Resource
UniversalProcess up;
@Override
public void process(NovelAction action) {
ClientInfo clientInfo = action.getClientInfo();
//获取用户注册时间
UserProfile userProfile = clientInfo.getUser();
if (userProfile == null || userProfile.getRegistrationDate() == null) {
log.error("ClientInfo.userProfile 对像为[{}],BookStoreEvent报文[{}]、ClientInfo对像[{}]",userProfile, action.getBody(), clientInfo);
return;
}
//平台ID
Integer platformId = clientInfo.getPlatformId();
//设备ID
String deviceIdKey = null;
//VO对像
DeliveryDeviceInfo deliveryDeviceInfo = null;
//匹配设备ID
for(DeviceType type:DeviceType.values()){
deviceIdKey = type.getDeviceId(clientInfo);
if(null != deviceIdKey && null != (deliveryDeviceInfo = up.get(DeliveryDeviceInfo.class,assembleKey(deviceIdKey,platformId)))){
clientInfo.setOaid(deliveryDeviceInfo.getOaid()); //回补OAID
action.setDeliveryDeviceInfo(deliveryDeviceInfo);
action.setDeviceType(type);
break;
}
}
//NovelAction对像参数填充
action.setPlatformId(clientInfo.getPlatformId());
action.setUserId(clientInfo.getUser().getId());
action.setDeviceId(clientInfo.getDID());
action.setNewUser(DateTimUtils.isCurrentDayTime(clientInfo.getUser().getRegistrationDate()));
action.setCurrentDateStr(DateTimUtils.getCurrentDateString());
action.setRegistrationDate(clientInfo.getUser().getRegistrationDate());
//非商店吊起参数设置
if (Objects.nonNull(deliveryDeviceInfo)) {
action.setChannelId(NumberUtils.parseInteger(deliveryDeviceInfo.getDj_channel()));
action.setPlanId(NumberUtils.parseInteger((deliveryDeviceInfo.getAd_plan_id())));
action.setAdvertiserId(deliveryDeviceInfo.getAdvertiser_id());
action.setMediaName(deliveryDeviceInfo.getMedia());
action.setMedia(Media.getMedia(action.getMediaName()));
}
}
public String assembleKey(String deviceId, int platformId) {
return String.format("getClickByIdfaAndPlatformId:%s:%d", SecureUtil.md5(deviceId), platformId);
}
}
package com.lwby.marketing.att.novel.handle;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.lwby.marketing.att.novel.AttributionType;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Component("plan")
public class PlanAttributionFlow extends ChannelAttributionFlow {
@Resource
JdbcTemplate lwbyJdbcTemplate;
@Override
public boolean checked(NovelAction action) {
//如果投放帐号没有配置计划归因,继续执行下一个渠道归因处理器
return getPlanAccount().contains(action.getAdvertiserId());
}
@Override
public void process(NovelAction action) {
process0(action, AttributionType.PLAN);
}
@Cached(name="plan_account", cacheType = CacheType.LOCAL)
@CacheRefresh(refresh = 300)
public Set<String> getPlanAccount(){
List<String> ls = lwbyJdbcTemplate.queryForList("select account from account_plan_callback",String.class);
return ls.stream()
.flatMap(e -> Arrays.stream(e.split(",")))
.map(String::trim)
.filter(_s -> _s.matches("\\d+"))
.collect(Collectors.toSet());
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.handle;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeFlow;
import com.lwby.marketing.util.DateTimUtils;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
@Component("prize")
public class PrizeSevenUserFlow extends NodeFlow<NovelAction> {
//TODO;建议参数先写死
@Value("${platformPrizeExpire:{}}")
private String platformPrizeExpire;
@Resource
UniversalProcess up;
@Override
public void process(NovelAction action) {
if(DateTimUtils.isTimeGreaterThanSevenDays(action.getRegistrationDate())){
//代码有问题
JSONObject platformPrizeExpireJson = JSON.parseObject(platformPrizeExpire);
Integer platformPrizeExpireStr = platformPrizeExpireJson.getInteger(String.valueOf(action.getPlatformId()));
String prizeAssembleKey = UniversalProcess.PRIZE_FLOW_PREFIX;
if (Objects.nonNull(platformPrizeExpireStr)) {
//商业化老用户大奖缓存
up.set(prizeAssembleKey.concat(String.valueOf(action.getUserId())), platformPrizeExpireStr, "1");
} else {
//商业化老用户大奖缓存3天
up.set(prizeAssembleKey.concat(String.valueOf(action.getUserId())), 60 * 60 * 24 * 3, "1");
}
}
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.handle;
import com.lwby.marketing.att.novel.AttributionStatus;
import com.lwby.marketing.att.novel.DeviceType;
import com.lwby.marketing.att.novel.UniversalProcess;
import com.lwby.marketing.flow.NodeFlow;
import com.lwby.marketing.vo.NovelAction;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
@Component("store")
public class StoreAttributionFlow extends NodeFlow<NovelAction> {
@Resource
UniversalProcess up;
@Override
public void process(NovelAction action) {
//商店归因通知
if(Objects.isNull(action.getDeliveryDeviceInfo())){
up.notifyResult(action, AttributionStatus.STORE_CALLBACK);
action.stop(true); //结束后面所有执行流程
}
//IP归因通知
if(DeviceType.IP == action.getDeviceType()){
up.notifyResult(action,AttributionStatus.IP_CALLBACK);
action.stop(true); //结束后面所有执行流程
}
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.media;
import com.lwby.marketing.vo.NovelAction;
import org.apache.commons.lang3.StringUtils;
public abstract class BaseCallback implements ICallback {
public boolean delivery(NovelAction action) {
String os = action.getClientInfo().getOs();
return StringUtils.isNotBlank(os) && os.equals("1")?ios(action):android(action);
}
public abstract boolean android(NovelAction na);
public abstract boolean ios(NovelAction na);
}
package com.lwby.marketing.att.novel.media;
import com.lwby.marketing.vo.NovelAction;
public interface ICallback {
boolean delivery(NovelAction action);
}
package com.lwby.marketing.att.novel.media;
import com.lwby.marketing.att.novel.media.gdt.GDTCallback;
import com.lwby.marketing.att.novel.media.jrtt.JRTTCallback;
import com.lwby.marketing.vo.NovelAction;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public enum Media {
GDT("gdt","广点通",new GDTCallback()),
JRTT("jrtt","今日头条",new JRTTCallback());
final String name;
final String desc;
final ICallback callBack;
final static Map<String, Media> mediaMap = Arrays.stream(Media.values())
.collect(Collectors.toMap(Media::name, Function.identity()));
Media(String name, String desc, ICallback callBack){
this.name = name;
this.desc = desc;
this.callBack = callBack;
}
public boolean callback(NovelAction action){
return callBack.delivery(action);
}
public static Media getMedia(String mediaName){
return mediaMap.get(mediaName);
}
}
\ No newline at end of file
package com.lwby.marketing.att.novel.media.gdt;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lwby.marketing.att.novel.media.BaseCallback;
import com.lwby.marketing.att.novel.media.gdt.dto.Action;
import com.lwby.marketing.att.novel.media.gdt.dto.GdtAttributeRequest;
import com.lwby.marketing.att.novel.media.gdt.dto.UserId;
import com.lwby.marketing.vo.ClientInfo;
import com.lwby.marketing.vo.DeliveryDeviceInfo;
import com.lwby.marketing.vo.NovelAction;
import java.util.Collections;
public class GDTCallback extends BaseCallback {
public boolean android(NovelAction na){
DeliveryDeviceInfo ddi = na.getDeliveryDeviceInfo();
ClientInfo ci = na.getClientInfo();
StringBuilder sb = new StringBuilder(ddi.getCallback_url());
UserId ui = UserId.builder()
.ip(ci.getClientIp())
.oaid(ci.getOaid())
.hash_oaid(SecureUtil.md5(ci.getOaid()))
.hash_imei(ci.getImei())
.hash_android_id(ci.getAndroidid())
.user_agent(ci.getUa())
.build();
Action action = Action.builder().action_time(System.currentTimeMillis() / 1000)
.user_id(ui)
.action_type("ACTIVATE_APP").build();
GdtAttributeRequest dto = GdtAttributeRequest.builder()
.actions(Collections.singletonList(action))
.build();
String userAction = JSONObject.toJSONString(dto);
try {
String result = HttpUtil.post(sb.toString(), userAction);
Integer resultCode = (Integer) JSON.parseObject(result).get("code");
return resultCode == 0;
} catch (Exception e) {
return false;
}
}
@Override
public boolean ios(NovelAction na) {
throw new UnsupportedOperationException("GDTCallback:ios() Method not implemented");
}
}
package com.lwby.marketing.att.novel.media.gdt.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Action {
private Long action_time;
private UserId user_id;
private String action_type;
}
package com.lwby.marketing.att.novel.media.gdt.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GdtAttributeRequest {
private List<Action> actions;
}
package com.lwby.marketing.att.novel.media.gdt.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserId {
private String hash_imei;
private String oaid;
private String hash_oaid;
private String hash_android_id;
private String ip;
private String user_agent;
}
package com.lwby.marketing.att.novel.media.jrtt;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lwby.marketing.att.novel.media.BaseCallback;
import com.lwby.marketing.att.novel.media.jrtt.dto.JrttAttributeRequest;
import com.lwby.marketing.vo.ClientInfo;
import com.lwby.marketing.vo.DeliveryDeviceInfo;
import com.lwby.marketing.vo.NovelAction;
public class JRTTCallback extends BaseCallback {
public static final String ATTRIBUTE_URL = "https://analytics.oceanengine.com/api/v2/conversion";
public boolean android(NovelAction na){
ClientInfo ci = na.getClientInfo();
DeliveryDeviceInfo ddi = na.getDeliveryDeviceInfo();
JrttAttributeRequest.Device device = new JrttAttributeRequest.Device();
device.setPlatform("android");
device.setImei(ci.getImei());
device.setOaid(ci.getOaid());
JrttAttributeRequest.Ad ad = new JrttAttributeRequest.Ad();
ad.setCallback(ddi.getCallback_param());
JrttAttributeRequest.Context context = new JrttAttributeRequest.Context();
context.setAd(ad);
context.setDevice(device);
JrttAttributeRequest request = JrttAttributeRequest.builder().context(context).event_type("active").timestamp(
System.currentTimeMillis()).build();
String userJson = JSONObject.toJSONString(request);
try {
String result = HttpUtil.post(ATTRIBUTE_URL, userJson);
Integer resultCode = (Integer) JSON.parseObject(result).get("code");
return resultCode == 0;
} catch (Exception e) {
return false;
}
}
@Override
public boolean ios(NovelAction na) {
throw new UnsupportedOperationException("JRTTCallback:ios() Method not implemented");
}
}
package com.lwby.marketing.att.novel.media.jrtt.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author songxinyu
* @version JrttAttributeRequest.java, v 0.1 2024年02月23日 17:05 songxinyu Exp $
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JrttAttributeRequest {
/**
* {
* "event_type": "active",
* "context": {
* "ad": {
* "callback": "EPHk9cX3pv4CGJax4ZENKI7w4MDev_4C",
* }
* },
* "timestamp": 1604888786102
* }
*/
/**
* 回传的事件,例如”激活“、”付费“
*/
private String event_type;
/**
* 包含一些关键的上下文信息
*/
private Context context;
/**
* 事件发生的毫秒级时间戳
*/
private long timestamp;
@Data
public static class Context {
/**
* 包含一些关键的广告相关信息
*/
private Ad ad;
private Device device;
}
@Data
public static class Ad {
/**
* callback 字段有两个获取途径,对于监测链接归因的方式,需要从监测链接的__CALLBACK_PARAM__这个宏获取这个字段值;
*/
private String callback;
}
@Data
public static class Device {
/**
* 设备平台
*/
private String platform;
private String imei;
private String oaid;
private String idfa;
}
}
package com.lwby.marketing.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncConfig {
@Value("${core.thread.pool.size:10}")
private Integer corePoolSize;
@Bean
public TaskExecutor commonTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize); // 设置核心线程数
executor.setMaxPoolSize(10); // 设置最大线程数
executor.setQueueCapacity(100); // 设置队列容量
executor.setKeepAliveSeconds(600); // 设置线程活跃时间(秒)
executor.setThreadNamePrefix("AAS-TASK-"); // 设置默认线程名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 设置拒绝策略
executor.setWaitForTasksToCompleteOnShutdown(true); // 等待所有任务结束后再关闭线程池
return executor;
}
}
package com.lwby.marketing.conf;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean(name = "lwbyDataSource")
@Primary
@ConfigurationProperties("spring.datasource.lwby")
DataSource lwbyDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "marketingDataSource")
@ConfigurationProperties("spring.datasource.marketing")
DataSource marketingDataSource() {
return DataSourceBuilder.create().build();
}
}
package com.lwby.marketing.conf;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class JdbcTemplateConfig {
@Bean(name = "lwbyJdbcTemplate")
JdbcTemplate lwbyJdbc(@Qualifier("lwbyDataSource") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "marketingJdbcTemplate")
JdbcTemplate marketingJdbc(@Qualifier("marketingDataSource") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
package com.lwby.marketing.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
\ No newline at end of file
package com.lwby.marketing.flow;
public abstract class Action {
boolean stop = false;
public void stop(boolean stop){
this.stop = stop;
}
public boolean isStop(){
return stop;
}
}
package com.lwby.marketing.flow;
import lombok.Data;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FlowExecutor<T extends Action> {
private final List<FlowWrapper<T>> flowList;
public FlowExecutor(ApplicationContext ctx,Rule rule){
flowList = new ArrayList<>();
for (Object o : rule.getRuleList()) {
if(o instanceof String[]){
flowList.add(new FlowWrapper<>(Arrays.stream((String[])o).map(s->(IFlow<T>)ctx.getBean(s, IFlow.class)).toArray(size -> new IFlow[size])));
}else{
flowList.add(new FlowWrapper<>((IFlow<T>)ctx.getBean((String)o, IFlow.class)));
}
}
}
public void execute(T action) throws Exception {
for (FlowWrapper<T> wrapper : flowList) {
if(wrapper.type == Type.SWITCH_NODE){
executeSwitchNode(wrapper,action);
}else{
executeNormalNode(wrapper,action);
}
if(action.isStop()){
break;
}
}
}
private void executeSwitchNode(FlowWrapper<T> wrapper, T action) throws Exception {
for (IFlow<T> flow : wrapper.getFlows()) {
if (flow.checked(action) && !action.isStop()) {
flow.process(action);
break;
}
}
}
private void executeNormalNode(FlowWrapper<T> wrapper, T action) throws Exception {
if (wrapper.getFlow().checked(action) && !action.isStop()) {
wrapper.getFlow().process(action);
}
}
@Data
static class FlowWrapper<T extends Action> {
IFlow<T> flow;
IFlow<T>[] flows;
Type type;
FlowWrapper(IFlow<T> flow) {
this.flow = flow;
this.type = Type.NODE;
}
FlowWrapper(IFlow<T>[] flows) {
this.flows = flows;
this.type = Type.SWITCH_NODE;
}
}
enum Type{
NODE,SWITCH_NODE
}
}
package com.lwby.marketing.flow;
public interface IFlow<T extends Action> {
boolean checked(T action);
void process(T action) throws Exception;
}
package com.lwby.marketing.flow;
public abstract class NodeFlow<T extends Action> implements IFlow<T>{
public abstract void process(T action) ;
public boolean checked(T action){
return true;
}
}
package com.lwby.marketing.flow;
public abstract class NodeSwitchFlow<T extends Action> implements IFlow<T>{
public abstract void process(T action) ;
public abstract boolean checked(T action);
}
\ No newline at end of file
package com.lwby.marketing.flow;
import java.util.ArrayList;
import java.util.List;
public class Rule {
private final List<Object> list;
public Rule(){
list = new ArrayList<>();
}
public static Rule create(){
return new Rule();
}
public Rule THEN(String beanName){
list.add(beanName);
return this;
}
public Rule SWITCH(String ... beanNames){
list.add(beanNames);
return this;
}
protected List<Object> getRuleList(){
return list;
}
}
package com.lwby.marketing.po;
import lombok.Data;
@Data
public class AppChannel {
private Integer id;
/**
* 合作方
*/
private String cpname;
/**
* 渠道名
*/
private String channel;
/**
* 渠道号
*/
private String channelId;
/**
* 合作方式
*/
private String type;
/**
* 平台id
*/
private Integer platformId;
/**
* 包:1=必看小说标准版 2=广告版
*/
private Integer shadowPackage;
/**
* 1 = s 安全 2 = g 普通 3 = h 大尺度
*/
private Integer level;
/**
* 当前渠道收费章节,不填从默认章节收费
*/
private Integer chargeChapterNum;
/**
* 0=显示原书名 1=显示多媒体书名
*/
private Integer isSubTitle;
/**
* 0按渠道收费章节 1按书籍收费章节
*/
private Integer newUserSwitch;
/**
* 是否直接广告订阅
*/
private Integer autoAd;
/**
* 推广扣量默认不扣量
*/
private Integer sprDedu;
/**
*
*/
private String productLine;
/**
* apk链接
*/
private String apkUrl;
/**
* 书号
*/
private Integer bookId;
/**
* 章节数
*/
private Integer chapterNum;
/**
* 授权模式(1.授权模式,2非授权模式)
*/
private Integer authType;
/**
* 链接类型(1 http 2.hap)
*/
private Integer linkType;
private static final long serialVersionUID = 1L;
/**
* 媒体
*/
private String mediaName;
/**
* 代理
*/
private String proxyName;
}
package com.lwby.marketing.po;
import lombok.Data;
@Data
public class CrossCallback {
private Integer id;
/**
* 跨包纯新用户最大配置天数
*/
private Integer spanCheckMaxDay;
/**
* 灰度渠道(走新的逻辑)
*/
private String newAccount;
/**
* 灰度渠道(走老的逻辑)
*/
private String oldAccount;
/**
* 媒体
*/
private String mediaName;
}
package com.lwby.marketing.util;
import java.nio.charset.Charset;
/**
* 字节数组转换工具类
*/
public class BytesUtils {
public static final String GBK = "GBK";
public static final String UTF8 = "utf-8";
public static final char[] ascii = "0123456789ABCDEF".toCharArray();
private static char[] HEX_VOCABLE = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/**
* 将short整型数值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(short data) {
byte[] bytes = new byte[2];
bytes[0] = (byte) ((data & 0xff00) >> 8);
bytes[1] = (byte) (data & 0xff);
return bytes;
}
/**
* 将字符转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(char data) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (data >> 8);
bytes[1] = (byte) (data);
return bytes;
}
/**
* 将布尔值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(boolean data) {
byte[] bytes = new byte[1];
bytes[0] = (byte) (data ? 1 : 0);
return bytes;
}
/**
* 将整型数值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(int data) {
byte[] bytes = new byte[4];
bytes[0] = (byte) ((data & 0xff000000) >> 24);
bytes[1] = (byte) ((data & 0xff0000) >> 16);
bytes[2] = (byte) ((data & 0xff00) >> 8);
bytes[3] = (byte) (data & 0xff);
return bytes;
}
/**
* 将long整型数值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(long data) {
byte[] bytes = new byte[8];
bytes[0] = (byte) ((data >> 56) & 0xff);
bytes[1] = (byte) ((data >> 48) & 0xff);
bytes[2] = (byte) ((data >> 40) & 0xff);
bytes[3] = (byte) ((data >> 32) & 0xff);
bytes[4] = (byte) ((data >> 24) & 0xff);
bytes[5] = (byte) ((data >> 16) & 0xff);
bytes[6] = (byte) ((data >> 8) & 0xff);
bytes[7] = (byte) (data & 0xff);
return bytes;
}
/**
* 将float型数值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(float data) {
int intBits = Float.floatToIntBits(data);
return getBytes(intBits);
}
/**
* 将double型数值转换为字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(double data) {
long intBits = Double.doubleToLongBits(data);
return getBytes(intBits);
}
/**
* 将字符串按照charsetName编码格式的字节数组
*
* @param data
* 字符串
* @param charsetName
* 编码格式
* @return
*/
public static byte[] getBytes(String data, String charsetName) {
Charset charset = Charset.forName(charsetName);
return data.getBytes(charset);
}
/**
* 将字符串按照GBK编码格式的字节数组
*
* @param data
* @return
*/
public static byte[] getBytes(String data) {
return getBytes(data, GBK);
}
/**
* 将字节数组第0字节转换为布尔值
*
* @param bytes
* @return
*/
public static boolean getBoolean(byte[] bytes) {
return bytes[0] == 1;
}
/**
* 将字节数组的第index字节转换为布尔值
*
* @param bytes
* @param index
* @return
*/
public static boolean getBoolean(byte[] bytes, int index) {
return bytes[index] == 1;
}
/**
* 将字节数组前2字节转换为short整型数值
*
* @param bytes
* @return
*/
public static short getShort(byte[] bytes) {
return (short) ((0xff00 & (bytes[0] << 8)) | (0xff & bytes[1]));
}
/**
* 将字节数组从startIndex开始的2个字节转换为short整型数值
*
* @param bytes
* @param startIndex
* @return
*/
public static short getShort(byte[] bytes, int startIndex) {
return (short) ((0xff00 & (bytes[startIndex] << 8)) | (0xff & bytes[startIndex + 1]));
}
/**
* 将字节数组前2字节转换为字符
*
* @param bytes
* @return
*/
public static char getChar(byte[] bytes) {
return (char) ((0xff00 & (bytes[0] << 8)) | (0xff & bytes[1]));
}
/**
* 将字节数组从startIndex开始的2个字节转换为字符
*
* @param bytes
* @param startIndex
* @return
*/
public static char getChar(byte[] bytes, int startIndex) {
return (char) ((0xff00 & (bytes[startIndex] << 8)) | (0xff & bytes[startIndex + 1]));
}
/**
* 将字节数组前4字节转换为整型数值
*
* @param bytes
* @return
*/
public static int getInt(byte[] bytes) {
return (0xff000000 & (bytes[0] << 24) | (0xff0000 & (bytes[1] << 16))
| (0xff00 & (bytes[2] << 8)) | (0xff & bytes[3]));
}
/**
* 将字节数组从startIndex开始的4个字节转换为整型数值
*
* @param bytes
* @param startIndex
* @return
*/
public static int getInt(byte[] bytes, int startIndex) {
return (0xff000000 & (bytes[startIndex] << 24)
| (0xff0000 & (bytes[startIndex + 1] << 16))
| (0xff00 & (bytes[startIndex + 2] << 8)) | (0xff & bytes[startIndex + 3]));
}
/**
* 将字节数组前8字节转换为long整型数值
*
* @param bytes
* @return
*/
public static long getLong(byte[] bytes) {
return (0xff00000000000000L & ((long) bytes[0] << 56)
| (0xff000000000000L & ((long) bytes[1] << 48))
| (0xff0000000000L & ((long) bytes[2] << 40))
| (0xff00000000L & ((long) bytes[3] << 32))
| (0xff000000L & ((long) bytes[4] << 24))
| (0xff0000L & ((long) bytes[5] << 16))
| (0xff00L & ((long) bytes[6] << 8)) | (0xffL & (long) bytes[7]));
}
/**
* 将字节数组从startIndex开始的8个字节转换为long整型数值
*
* @param bytes
* @param startIndex
* @return
*/
public static long getLong(byte[] bytes, int startIndex) {
return (0xff00000000000000L & ((long) bytes[startIndex] << 56)
| (0xff000000000000L & ((long) bytes[startIndex + 1] << 48))
| (0xff0000000000L & ((long) bytes[startIndex + 2] << 40))
| (0xff00000000L & ((long) bytes[startIndex + 3] << 32))
| (0xff000000L & ((long) bytes[startIndex + 4] << 24))
| (0xff0000L & ((long) bytes[startIndex + 5] << 16))
| (0xff00L & ((long) bytes[startIndex + 6] << 8)) | (0xffL & (long) bytes[startIndex + 7]));
}
/**
* 将字节数组前4字节转换为float型数值
*
* @param bytes
* @return
*/
public static float getFloat(byte[] bytes) {
return Float.intBitsToFloat(getInt(bytes));
}
/**
* 将字节数组从startIndex开始的4个字节转换为float型数值
*
* @param bytes
* @param startIndex
* @return
*/
public static float getFloat(byte[] bytes, int startIndex) {
byte[] result = new byte[4];
System.arraycopy(bytes, startIndex, result, 0, 4);
return Float.intBitsToFloat(getInt(result));
}
/**
* 将字节数组前8字节转换为double型数值
*
* @param bytes
* @return
*/
public static double getDouble(byte[] bytes) {
long l = getLong(bytes);
return Double.longBitsToDouble(l);
}
/**
* 将字节数组从startIndex开始的8个字节转换为double型数值
*
* @param bytes
* @param startIndex
* @return
*/
public static double getDouble(byte[] bytes, int startIndex) {
byte[] result = new byte[8];
System.arraycopy(bytes, startIndex, result, 0, 8);
long l = getLong(result);
return Double.longBitsToDouble(l);
}
/**
* 将charsetName编码格式的字节数组转换为字符串
*
* @param bytes
* @param charsetName
* @return
*/
public static String getString(byte[] bytes, String charsetName) {
return new String(bytes, Charset.forName(charsetName));
}
/**
* 将GBK编码格式的字节数组转换为字符串
*
* @param bytes
* @return
*/
public static String getString(byte[] bytes) {
return getString(bytes, GBK);
}
/**
* 将16进制字符串转换为字节数组
*
* @param hex
* @return
*/
public static byte[] hexStringToBytes(String hex) {
if (hex == null || "".equals(hex)) {
return null;
}
int len = hex.length() / 2;
byte[] result = new byte[len];
char[] chArr = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(chArr[pos]) << 4 | toByte(chArr[pos + 1]));
}
return result;
}
/**
* 将BCD编码的字节数组转换为字符串
*
* @param bcds
* @return
*/
public static String bcdToString(byte[] bcds) {
if (bcds == null || bcds.length == 0) {
return null;
}
byte[] temp = new byte[2 * bcds.length];
for (int i = 0; i < bcds.length; i++) {
temp[i * 2] = (byte) ((bcds[i] >> 4) & 0x0f);
temp[i * 2 + 1] = (byte) (bcds[i] & 0x0f);
}
StringBuffer res = new StringBuffer();
for (int i = 0; i < temp.length; i++) {
res.append(ascii[temp[i]]);
}
return res.toString();
}
/**
* 字节转整形
* @param value
* @return
*/
public static int bcdToInt(byte value){
return ((value>>4) * 10) + (value&0x0F);
}
/**
* 字节数组转16进制字符串
* @param bs
* @return
*/
public static String bytesToHex(byte[] bs) {
StringBuilder sb = new StringBuilder();
for (byte b : bs) {
int high = (b >> 4) & 0x0f;
int low = b & 0x0f;
sb.append(HEX_VOCABLE[high]);
sb.append(HEX_VOCABLE[low]);
}
return sb.toString();
}
/**
* 字节数组取前len个字节转16进制字符串
* @param bs
* @param len
* @return
*/
public static String bytesToHex(byte[] bs, int len) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<len; i++ ) {
byte b = bs[i];
int high = (b >> 4) & 0x0f;
int low = b & 0x0f;
sb.append(HEX_VOCABLE[high]);
sb.append(HEX_VOCABLE[low]);
}
return sb.toString();
}
/**
* 字节数组偏移offset长度之后的取len个字节转16进制字符串
* @param bs
* @param offset
* @param len
* @return
*/
public static String bytesToHex(byte[] bs, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<len; i++ ) {
byte b = bs[offset + i];
int high = (b >> 4) & 0x0f;
int low = b & 0x0f;
sb.append(HEX_VOCABLE[high]);
sb.append(HEX_VOCABLE[low]);
}
return sb.toString();
}
/**
* 字节数组转16进制字符串
* @return
*/
public static String byteToHex(byte b) {
StringBuilder sb = new StringBuilder();
int high = (b >> 4) & 0x0f;
int low = b & 0x0f;
sb.append(HEX_VOCABLE[high]);
sb.append(HEX_VOCABLE[low]);
return sb.toString();
}
/**
* 将字节数组取反
*
* @param src
* @return
*/
public static String negate(byte[] src) {
if (src == null || src.length == 0) {
return null;
}
byte[] temp = new byte[2 * src.length];
for (int i = 0; i < src.length; i++) {
byte tmp = (byte) (0xFF ^ src[i]);
temp[i * 2] = (byte) ((tmp >> 4) & 0x0f);
temp[i * 2 + 1] = (byte) (tmp & 0x0f);
}
StringBuffer res = new StringBuffer();
for (int i = 0; i < temp.length; i++) {
res.append(ascii[temp[i]]);
}
return res.toString();
}
/**
* 比较字节数组是否相同
*
* @param a
* @param b
* @return
*/
public static boolean compareBytes(byte[] a, byte[] b) {
if (a == null || a.length == 0 || b == null || b.length == 0
|| a.length != b.length) {
return false;
}
if (a.length == b.length) {
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* 只比对指定长度byte
* @param a
* @param b
* @param len
* @return
*/
public static boolean compareBytes(byte[] a, byte[] b, int len) {
if (a == null || a.length == 0 || b == null || b.length == 0
|| a.length < len || b.length < len) {
return false;
}
for (int i = 0; i < len; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* 将字节数组转换为二进制字符串
*
* @param items
* @return
*/
public static String bytesToBinaryString(byte[] items) {
if (items == null || items.length == 0) {
return null;
}
StringBuffer buf = new StringBuffer();
for (byte item : items) {
buf.append(byteToBinaryString(item));
}
return buf.toString();
}
/**
* 将字节转换为二进制字符串
*
* @return
*/
public static String byteToBinaryString(byte item) {
byte a = item;
StringBuffer buf = new StringBuffer();
for (int i = 0; i < 8; i++) {
buf.insert(0, a % 2);
a = (byte) (a >> 1);
}
return buf.toString();
}
/**
* 对数组a,b进行异或运算
*
* @param a
* @param b
* @return
*/
public static byte[] xor(byte[] a, byte[] b) {
if (a == null || a.length == 0 || b == null || b.length == 0
|| a.length != b.length) {
return null;
}
byte[] result = new byte[a.length];
for (int i = 0; i < a.length; i++) {
result[i] = (byte) (a[i] ^ b[i]);
}
return result;
}
/**
* 对数组a,b进行异或运算 运算长度len
* @param a
* @param b
* @param len
* @return
*/
public static byte[] xor(byte[] a, byte[] b, int len) {
if (a == null || a.length == 0 || b == null || b.length == 0) {
return null;
}
if (a.length < len || b.length < len){
return null;
}
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = (byte) (a[i] ^ b[i]);
}
return result;
}
/**
* 将short整型数值转换为字节数组
*
* @param num
* @return
*/
public static byte[] shortToBytes(int num) {
byte[] temp = new byte[2];
for (int i = 0; i < 2; i++) {
temp[i] = (byte) ((num >>> (8 - i * 8)) & 0xFF);
}
return temp;
}
/**
* 将字节数组转为整型
*
* @return
*/
public static int bytesToShort(byte[] arr) {
int mask = 0xFF;
int temp = 0;
int result = 0;
for (int i = 0; i < 2; i++) {
result <<= 8;
temp = arr[i] & mask;
result |= temp;
}
return result;
}
/**
* 将整型数值转换为指定长度的字节数组
*
* @param num
* @return
*/
public static byte[] intToBytes(int num) {
byte[] temp = new byte[4];
for (int i = 0; i < 4; i++) {
temp[i] = (byte) ((num >>> (24 - i * 8)) & 0xFF);
}
return temp;
}
/**
* 将整型数值转换为指定长度的字节数组
*
* @param src
* @param len
* @return
*/
public static byte[] intToBytes(int src, int len) {
if (len < 1 || len > 4) {
return null;
}
byte[] temp = new byte[len];
for (int i = 0; i < len; i++) {
temp[len - 1 - i] = (byte) ((src >>> (8 * i)) & 0xFF);
}
return temp;
}
/**
* 将字节数组转换为整型数值
*
* @param arr
* @return
*/
public static int bytesToInt(byte[] arr) {
int mask = 0xFF;
int temp = 0;
int result = 0;
for (int i = 0; i < 4; i++) {
result <<= 8;
temp = arr[i] & mask;
result |= temp;
}
return result;
}
/**
* 将long整型数值转换为字节数组
*
* @param num
* @return
*/
public static byte[] longToBytes(long num) {
byte[] temp = new byte[8];
for (int i = 0; i < 8; i++) {
temp[i] = (byte) ((num >>> (56 - i * 8)) & 0xFF);
}
return temp;
}
/**
* 将字节数组转换为long整型数值
*
* @param arr
* @return
*/
public static long bytesToLong(byte[] arr) {
int mask = 0xFF;
int temp = 0;
long result = 0;
int len = Math.min(8, arr.length);
for (int i = 0; i < len; i++) {
result <<= 8;
temp = arr[i] & mask;
result |= temp;
}
return result;
}
/**
* 将16进制字符转换为字节
*
* @param c
* @return
*/
public static byte toByte(char c) {
byte b = (byte) "0123456789ABCDEF".indexOf(c);
return b;
}
/**
* 功能描述:把两个字节的字节数组转化为整型数据,高位补零,例如:<br/>
* 有字节数组byte[] data = new byte[]{1,2};转换后int数据的字节分布如下:<br/>
* 00000000 00000000 00000001 00000010,函数返回258
* @param lenData 需要进行转换的字节数组
* @return 字节数组所表示整型值的大小
*/
public static int bytesToIntWhereByteLengthEquals2(byte lenData[]) {
if(lenData.length != 2){
return -1;
}
byte fill[] = new byte[]{0,0};
byte real[] = new byte[4];
System.arraycopy(fill, 0, real, 0, 2);
System.arraycopy(lenData, 0, real, 2, 2);
int len = byteToInt(real);
return len;
}
/**
* 功能描述:将byte数组转化为int类型的数据
* @param byteVal 需要转化的字节数组
* @return 字节数组所表示的整型数据
*/
public static int byteToInt(byte[] byteVal) {
int result = 0;
for(int i = 0;i < byteVal.length;i++) {
int tmpVal = (byteVal[i]<<(8*(3-i)));
switch(i) {
case 0:
tmpVal = tmpVal & 0xFF000000;
break;
case 1:
tmpVal = tmpVal & 0x00FF0000;
break;
case 2:
tmpVal = tmpVal & 0x0000FF00;
break;
case 3:
tmpVal = tmpVal & 0x000000FF;
break;
}
result = result | tmpVal;
}
return result;
}
public static byte CheckXORSum(byte[] bData){
byte sum = 0x00;
for (int i = 0; i < bData.length; i++) {
sum ^= bData[i];
}
return sum;
}
/**
* 从offset开始 将后续长度为len的byte字节转为int
* @param data
* @param offset
* @param len
* @return
*/
public static int bytesToInt(byte[] data, int offset, int len){
int mask = 0xFF;
int temp = 0;
int result = 0;
len = Math.min(len, 4);
for (int i = 0; i < len; i++) {
result <<= 8;
temp = data[offset + i] & mask;
result |= temp;
}
return result;
}
/**
* byte字节数组中的字符串的长度
* @param data
* @return
*/
public static int getBytesStringLen(byte[] data)
{
int count = 0;
for (byte b : data) {
if(b == 0x00)
break;
count++;
}
return count;
}
}
\ No newline at end of file
package com.lwby.marketing.util;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
/**
* @author martin.ad
* @Description: 非线程安全工具类,且把buf曝露在外不安全,只为内存复用和高性能,慎用。
* @date 创建时间: 2022/11/17 18:21
*/
public
class CharStream extends Writer {
protected char buf[];
protected int count;
public CharStream() {
this(32);
}
public CharStream(String initStr){
this(initStr.length());
this.buf = initStr.toCharArray();
this.count = initStr.length();
}
public char[] getChars(){
return this.buf;
}
public int getCount(){
return this.count;
}
public void copy(CharStream cs){
reset();
write(cs.getChars(),0,cs.getCount());
}
public void copy(String str){
reset();
write(str,0,str.length());
}
public CharStream clone(){
CharStream _cs = new CharStream(this.count);
_cs.write(buf,0,this.count);
return _cs;
}
public void group(CharStream[] csa,char separtor,CharStream defalut){
reset();
for(CharStream _cs:csa){
write(_cs.getCount() == 0?defalut:_cs);
write(separtor);
}
count--;
}
public boolean equals(char[] chars){
int n = chars.length;
if (count == n) {
for(int i=0;i<n;i++){
if (buf[i] != chars[i])
return false;
}
return true;
}
return false;
}
public CharStream(int initialSize) {
if (initialSize < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ initialSize);
}
buf = new char[initialSize];
}
public void write(int c) {
int newcount = count + 1;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (char)c;
count = newcount;
}
public void write(CharStream cs) {
write(cs.getChars(),0,cs.getCount());
}
public void write(char c[], int off, int len) {
if ((off < 0) || (off > c.length) || (len < 0) ||
((off + len) > c.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newcount = count + len;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
System.arraycopy(c, off, buf, count, len);
count = newcount;
}
public void write(String str, int off, int len) {
int newcount = count + len;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
str.getChars(off, off + len, buf, count);
count = newcount;
}
public void writeTo(Writer out) throws IOException {
out.write(buf, 0, count);
}
public CharStream append(CharSequence csq) {
String s = (csq == null ? "null" : csq.toString());
write(s, 0, s.length());
return this;
}
public CharStream append(CharSequence csq, int start, int end) {
String s = (csq == null ? "null" : csq).subSequence(start, end).toString();
write(s, 0, s.length());
return this;
}
public CharStream append(char c) {
write(c);
return this;
}
public void reset() {
count = 0;
}
public char toCharArray()[] {
return Arrays.copyOf(buf, count);
}
public int size() {
return count;
}
public String toString() {
return new String(buf, 0, count);
}
public void flush() { }
public void close() { }
}
\ No newline at end of file
package com.lwby.marketing.util;
import org.apache.commons.lang3.time.FastDateFormat;
import java.time.ZoneId;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
public class DateTimUtils {
public static final FastDateFormat DATE_TIME_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")));
public static final FastDateFormat DATE_BOTTOM_FORMAT = FastDateFormat.getInstance("yyyy_MM_dd", TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")));
/**
* 是否为当天时间
*
* @param date1
* @return
*/
public static boolean isCurrentDayTime(Date date1) {
Date date2 = new Date();
return date1.getYear() == date2.getYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate();
}
public static String getCurrentDateString(){
return DATE_BOTTOM_FORMAT.format(new Date());
}
/**
* 时间是否大于7天
*
* @param date
* @return
*/
public static boolean isTimeGreaterThanSevenDays(Date date) {
return isTimeGreaterThanDays(date,7);
}
/**
* 时间是否大于指定天数
*
* @param date
* @return
*/
public static boolean isTimeGreaterThanDays(Date date,long day) {
return date.before(new Date(System.currentTimeMillis() - 60 * 60 * 24 * day * 1000));
}
/**
* 计算日期区间天数
* @return
*/
public static long calculateDaysBetweenDates(Date startDate, Date endDate) {
return TimeUnit.DAYS.convert(Math.abs(endDate.getTime() - startDate.getTime()), TimeUnit.MILLISECONDS);
}
/**
* 计算指定的时间,是否在给定最小最大天数据区间
* @param registrationDate
* @param minDays
* @param maxDays
* @return
*/
public static boolean isWithinDaysRange(Date registrationDate, int minDays, int maxDays) {
long daysSinceRegistration = calculateDaysBetweenDates(registrationDate, new Date());
return daysSinceRegistration >= minDays && daysSinceRegistration <= maxDays;
}
}
package com.lwby.marketing.util;
import org.apache.commons.lang3.StringUtils;
public class NumberUtils {
public static int parseInteger(String value) {
return StringUtils.isNotBlank(value) ? Integer.parseInt(value) : -1;
}
}
package com.lwby.marketing.vo;
import lombok.Data;
import java.util.Date;
import java.util.Map;
@Data
public class BookStoreEvent {
public static final String ADD_USER_READ_TIME = "addUserReadTime";
public static final String USER_ADD_BOOK_SHELF = "userAddBookShelf";
public static final String USER_DELETE_BOOK_SHELF = "userDeleteBookShelf";
public static final String PART_ID = "partId";
public static final String PART_NUM = "partNum";
public static final String BOOK_ID = "bookId";
private String id;
private Integer bookStoreEvent;
private ClientInfo clientInfo;
private String pushProvider;
private String pushToken;
private Map<String, Object> extraData;
private Date createTime;
}
\ No newline at end of file
package com.lwby.marketing.vo;
import lombok.Data;
@Data
public class ClientInfo {
private String sessionid;
private UserProfile user;
private String xClient;
private String imei;
private String imsi;
private String mac;
private String phoneModel;
private String systemVersion;
private String screenSize;
private String cellId;
private String version;
private String rootPath;
private String skin;
private String rn;
private String tdcn;
private Integer platformId;
private Integer mainVersion;
private Integer subVersion;
private Integer channel;
private Integer platformGroupId;
private Integer apiType;
private String visitor;
private String sid;
private Integer signVersion;
private String dID;
private Integer pkv = 1;
private boolean isVisitor = false;
private Integer fixVersion;
private String pm;
private String firm;
private String idfa;
private String os;
private String androidid;
private String h5UserId;
private String oaid;
private String ddid;
private String clientIp;
private String ua;
}
package com.lwby.marketing.vo;
import com.lwby.marketing.po.CrossCallback;
import lombok.Data;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@Data
public class CrossPlatformAccount {
/**
* 媒体名称
*/
private String mediaName;
/**
* 跨包纯新用户最大配置天数
*/
private Integer spanCheckMaxDay;
/**
* 跨平台帐号
*/
private Set<String> account;
public CrossPlatformAccount(CrossCallback ccb){
setMediaName(ccb.getMediaName());
setSpanCheckMaxDay(ccb.getSpanCheckMaxDay());
setAccount(Arrays.stream(ccb.getNewAccount().split(","))
.map(String::trim)
.filter(_s -> _s.matches("\\d+"))
.collect(Collectors.toSet()));
}
}
package com.lwby.marketing.vo;
import lombok.Data;
@Data
public class DeliveryDeviceInfo {
private String device_id; //设备id,和其他业务表一致
private String ocpc_device_id; //ocpc业务原始设备id,可能是明文或者密文
private String ocpc_device_id_type; // 设备id类型(idfa,imei,oaid...)
private String encryption_type; // 加密类型,MD5,明文等
private String platform_id; // (监测链接)
private String channel; // 渠道id
private String dj_channel; //点击渠道
private String book_id; // 书籍id
private String media; // 媒体标识(如'jrtt','gdt')
private String os; // 系统
private String advertiser_id; // 账号
private String ad_group_id; //广告组id
private String ad_plan_id; //广告计划id
private String ad_creative_id; // 广告创意id
private String ip;
private String ua;
private String callback_url;
private String parameter; //(所有参数以json的形式存在这里)
private long click_time = 0; // 点击时间
private Long active_time; //激活时间
private Integer is_call; // 是否回调 2 回调 3 不回调 4 扣量 5 老用户xx天活跃不回传 6短剧注册发消息 7 老用户在配置活跃天数内不回传 8 关键行为 9 使用ip只发bi消息
private String click_id; //点击id
private String uuid;// 唯一id
private String ad_platform_type;
private String target_audience;
//通投智选使用字段-点击来自穿山甲的广告位编码
private String union_site;
//客户在小米渠道投放的渠道包的 id 值
private String appId;
//头条回传上报途径 巨量引擎的关键广告信息
private String callback_param;
//巨量广告体验版中特有的宏参,代表巨量广告体验版的项目ID
private String promotion_id;
//巨量广告体验版中的广告名称
private String project_id;
//巨量广告体验版中的广告名称
private String promotion_name;
//巨量广告体验版中的项目名称
private String project_name;
//请求id vivo 商店投放 带监测链接
private String request_id;
//存oaid
private String oaid;
//章节id
private String partId;
/**
* 百度专用账户key
*/
private String aKey;
/**
* 用户id
*/
private Long userId;
/**
* 投放来源
*/
private Integer source;
//落地页url
private String link;
private String ctype;
/**
* 存媒体下发的imeiMd5给广告使用
*/
private String imeiMd5;
/**
* 微信小程序appid
*/
private String wechatAppId;
/**
* 微信小程序openid
*/
private String wechatOpenId;
/**
* 短剧剧名id
*/
private String productId;
/**
* 短剧剧名
*/
private String productName;
/**
* 短剧付费金额
*/
private Double value;
/**
* 小程序短剧广点通 下单上报媒体字段
*/
private String orderObject;
private Integer device_status;
//手机型号
private String model;
private String videoResourceId;
private String videoId;
private String registerTime;
private String ecpmAvgCount;
private String motivateCount;
private String arpuCount;
//每次ecpm达标
private String pecpmCount;
//达标次数,与激励视频阈值比较
private String perecpmSize;
//素材
private String mid1;
private String mid2;
private String mid3;
private String mid4;
private String mid5;
private String mid6;
}
package com.lwby.marketing.vo;
import com.lwby.marketing.att.novel.DeviceType;
import com.lwby.marketing.flow.Action;
import com.lwby.marketing.att.novel.media.Media;
import lombok.Data;
import java.util.Date;
@Data
public class NovelAction extends Action {
ClientInfo clientInfo;
DeliveryDeviceInfo deliveryDeviceInfo;
int platformId;
long userId;
int channelId;
int planId;
String advertiserId;
String deviceId;
String mediaName;
boolean isNewUser;
Date RegistrationDate;
CrossPlatformAccount cpa;
String body;
DeviceType deviceType;
String currentDateStr;
Media media;
public NovelAction(ClientInfo clientInfo,String msg){
this(clientInfo,null,msg);
}
public NovelAction(ClientInfo clientInfo, DeliveryDeviceInfo deliveryDeviceInfo) {
this(clientInfo,deliveryDeviceInfo,null);
}
public NovelAction(ClientInfo clientInfo, DeliveryDeviceInfo deliveryDeviceInfo,String msg){
this.clientInfo = clientInfo;
this.deliveryDeviceInfo = deliveryDeviceInfo;
this.body = msg;
}
}
\ No newline at end of file
package com.lwby.marketing.vo;
import lombok.Data;
import java.util.Date;
@Data
public class UserProfile {
private Long id;
private String username;
private String password;
private Boolean isKaiqiUser;
private String signature;
private String gender;
private Integer level;
private Integer experience;
private Integer tadou;
private String nickname;
private String city;
private String contact;
private String hobbies;
private String phoneNumber;
private String career;
private Date birthday;
private Date lastLogin;
private Boolean isMajia;
private Date registrationDate;
private String deviceId;
private String bindingNum;
private String bloodType;
private String email;
private Boolean isPrivate;
private String cellPhone;
private String myBackup;
private Integer numOfMedals;
private Integer medalId;
private Integer authorId;
private String penName;
private String headImage;
private String cellEmail;
private String viewPassword;
private Integer platformId;
private Integer mainversion;
private Integer subversion;
private Integer channel;
private Integer userStatus;
}
package com.lwby.marketing.web;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@Controller
@RequestMapping(value = "/api")
public class ApiService {
@CrossOrigin(originPatterns = "*",allowCredentials = "true")
@ResponseBody
@RequestMapping(value = "query")
public void query(int pid,String expr) {
}
}
\ No newline at end of file
spring:
datasource:
lwby:
jdbc-url: jdbc:mysql://rm-2zem654n5267sl284.mysql.rds.aliyuncs.com/lwby_novel?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true
username: lwby_read
password: TXlEjAy0zO2S
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
marketing:
jdbc-url: jdbc:mysql://rm-2ze1e2ykb87kj2592.mysql.rds.aliyuncs.com:3306/lwby_marketing_growth?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true
username: read01
password: read01!@#
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
redis:
host: 127.0.0.1
port: 6379
kafka:
bootstrap-servers: 172.17.243.58:9092,172.17.243.59:9092,172.17.243.60:9092,172.17.243.61:9092,172.17.243.62:9092
producer:
retries: 3
acks: 1
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
enable-auto-commit: true
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
max-poll-records: 100
listener:
ack-mode: RECORD
\ No newline at end of file
spring:
datasource:
lwby:
jdbc-url: jdbc:mysql://rm-2zem654n5267sl284.mysql.rds.aliyuncs.com/lwby_novel?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true
username: lwby_read
password: TXlEjAy0zO2S
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
marketing:
jdbc-url: jdbc:mysql://rm-2ze1e2ykb87kj2592.mysql.rds.aliyuncs.com:3306/lwby_marketing_growth?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true
username: read01
password: read01!@#
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
redis:
host: 127.0.0.1
port: 6379
kafka:
bootstrap-servers: 172.17.255.143:9092
producer:
retries: 3
acks: 1
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
enable-auto-commit: true
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
max-poll-records: 500
listener:
ack-mode: RECORD
\ No newline at end of file
server:
port: 8081
tomcat:
accept-count: 10000
max-threads: 100
min-spare-threads: 100
max-connections: 2000
connection-timeout: 1000
max-http-form-post-size: 10MB
spring:
application:
name: ssa
profiles:
active: test
#prometheus监控平台配置
management:
endpoints:
web:
exposure:
include: "*"
exclude: configprops
endpoint:
health:
show-details: ALWAYS
metrics:
tags:
application: ${spring.application.name}
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
local:
default:
type: linkedhashmap
keyConvertor: fastjson
limit: 10000
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<timestamp key="DATETIME" datePattern="yyyy-MM-dd HH:mm:ss" />
<!-- 控制台打印 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n</pattern>
</encoder>
</appender>
<!-- Logger 根目录 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment