first commit

This commit is contained in:
Ray
2026-02-19 03:37:37 +08:00
commit ccfd8c79a4
2813 changed files with 453657 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

View File

@@ -0,0 +1,17 @@
package project.mall;
public enum LanguageEnum {
EN( "en"),//英文
CN( "cn"),//中文
TW( "tw"),//繁体
;
private String lang;
LanguageEnum(String lang) {
this.lang = lang;
}
public String getLang() {
return lang;
}
}

View File

@@ -0,0 +1,71 @@
package project.mall;
public class MallRedisKeys {
/**
* 会员锁key
*/
public final static String MALL_ORDER_USER_LOCK = "MALL_ORDER_USER_LOCK:";
/**
* 密码错误锁key
*/
public final static String MALL_PASSWORD_ERROR_LOCK = "MALL_PASSWORD_ERROR_LOCK:";
/**
* 邮箱校验次数锁key
*/
public final static String MALL_EMAIL_VERIFY_TIME_LOCK = "MALL_EMAIL_VERIFY_LOCK:";
/**
* 电话号码校验次数锁key
*/
public final static String MALL_PHONE_VERIFY_TIME_LOCK = "MALL_PHONE_VERIFY_LOCK:";
/**
* Verify token before changing new email or phone,you need to set it after checking old email or phone success.
*/
public final static String MALL_MODIFY_BEFORE_VERIFY_TOKEN = "MALL_MODIFY_BEFORE_VERIFY_TOKEN:";
/**
* 商品多语言
*/
public final static String MALL_GOODS_LANG = "MALL_GOODS_LANG:";
/**
* 分类多语言
*/
public final static String TYPE_LANG = "TYPE_LANG:";
/**
* 商城国家
*/
public final static String MALL_COUNTRY = "MALL_COUNTRY:";
/**
* 商城省
*/
public final static String MALL_STATE = "MALL_STATE:";
/**
* 商城城市
*/
public final static String MALL_CITY = "MALL_CITY:";
/**
* 直通车多语言
*/
public final static String MALL_COMBO_LANG = "MALL_COMBO_LANG:";
/**
* 虚拟币价格
*/
public final static String BRUSH_VIRTUAL_CURRENCY_PRICE = "BRUSH_VIRTUAL_CURRENCY_PRICE:";
/**
* 店铺销售排名,后缀:起止时间
*/
//public static final String SELLER_TOPN_CACHE = "CACHE_SELLER_TOPN:";
}

View File

@@ -0,0 +1,51 @@
//package project.mall.activity;
//
//import cn.hutool.core.util.StrUtil;
//import project.mall.activity.event.message.BaseActivityMessage;
//import project.mall.activity.event.message.ActivityUserRechargeMessage;
//
///**
// * 系统活动相关的事件类型
// */
//public enum ActivityTouchEventTypeEnum {
// USER_RECHARGE("user_recharge", ActivityUserRechargeMessage.class, "用户充值"),
//
// ;
//
// private String eventType;
// private Class<? extends BaseActivityMessage> eventInfoClazz;
// private String description;
//
// private ActivityTouchEventTypeEnum(String eventType, Class<? extends BaseActivityMessage> eventInfoClazz, String description) {
// this.eventType = eventType;
// this.eventInfoClazz = eventInfoClazz;
// this.description = description;
// }
//
// public static ActivityTouchEventTypeEnum typeOf(String eventType) {
// if (StrUtil.isBlank(eventType)) {
// return null;
// }
//
// ActivityTouchEventTypeEnum values[] = ActivityTouchEventTypeEnum.values();
// for (ActivityTouchEventTypeEnum one : values) {
// if (one.getEventType().equalsIgnoreCase(eventType)) {
// return one;
// }
// }
//
// return null;
// }
//
// public String getEventType() {
// return eventType;
// }
//
// public Class<? extends BaseActivityMessage> getEventInfoClazz() {
// return eventInfoClazz;
// }
//
// public String getDescription() {
// return description;
// }
//}

View File

@@ -0,0 +1,247 @@
package project.mall.activity;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import kernel.annotation.BeanFieldName;
import kernel.util.ClassTools;
import kernel.util.ObjectTools;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.handler.ActivityHandler;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.handler.SimpleLotteryActivityHandler;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.FruitDialActivityConfig;
import project.mall.activity.rule.SimpleLotteryActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.award.DemoActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import project.mall.activity.rule.join.DemoActivityJoinRule;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 活动枚举类型
*/
public enum ActivityTypeEnum {
// 首充参加抽奖活动
FRUIT_DIAL_LOTTERY("fruit_dial_lottery", 2, FruitDialActivityConfig.class, DemoActivityJoinRule.class, DemoActivityAwardRule.class, FirstRechargeFruitDialActivityHandler.class,"首充抽奖活动"),
SIMPLE_LOTTERY("simple_lottery", 1, SimpleLotteryActivityConfig.class, DemoActivityJoinRule.class, DemoActivityAwardRule.class, SimpleLotteryActivityHandler.class,"简单抽奖活动"),
;
// 活动编码
private String type;
// 兼容有的地方使用数字代表活动类型
private int index;
private Class<? extends BaseActivityConfig> activityConfig;
// 活动参加规则
private Class<? extends BaseActivityJoinRule> joinRule;
// 活动奖励规则
private Class<? extends BaseActivityAwardRule> awardRule;
private Class<? extends ActivityHandler> handler;
private String description;
private ActivityTypeEnum(String type,
int index,
Class<? extends BaseActivityConfig> activityConfig,
Class<? extends BaseActivityJoinRule> joinRule,
Class<? extends BaseActivityAwardRule> awardRule,
Class<? extends ActivityHandler> handler,
String description) {
this.type = type;
this.index = index;
this.activityConfig = activityConfig;
this.joinRule = joinRule;
this.awardRule = awardRule;
this.handler = handler;
this.description = description;
}
public static ActivityTypeEnum typeOf(String type) {
if (StrUtil.isBlank(type)) {
return null;
}
ActivityTypeEnum values[] = ActivityTypeEnum.values();
for (ActivityTypeEnum one : values) {
if (Objects.equals(one.getType(), type)) {
return one;
}
}
return null;
}
public static ActivityTypeEnum indexOf(int inputIdx) {
ActivityTypeEnum values[] = ActivityTypeEnum.values();
for (ActivityTypeEnum one : values) {
if (one.getIndex() == inputIdx) {
return one;
}
}
return null;
}
public Object initActivityConfig(List<ActivityParam> paramList) {
try {
final Class unsafeClass = Class.forName("sun.misc.Unsafe");
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
final Object unsafe = theUnsafeField.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
Object obj = allocateInstance.invoke(unsafe, this.activityConfig);
Map<String, ActivityParam> paramMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(paramList)) {
for (ActivityParam oneParam : paramList) {
paramMap.put(oneParam.getCode(), oneParam);
}
}
if (!paramMap.isEmpty()) {
List<Field> allFields = ClassTools.getAllFields(this.activityConfig);
for (Field oneField : allFields) {
String fieldName = oneField.getName();
BeanFieldName ann = oneField.getAnnotation(BeanFieldName.class);
if (ann != null) {
fieldName = ann.value();
}
ActivityParam paramValue = paramMap.get(fieldName);
if (paramValue != null) {
ObjectTools.setValue(obj, fieldName, paramValue.getValue());
}
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public Object initJoinRule(List<ActivityParam> paramList) {
try {
final Class unsafeClass = Class.forName("sun.misc.Unsafe");
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
final Object unsafe = theUnsafeField.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
Object obj = allocateInstance.invoke(unsafe, this.joinRule);
Map<String, ActivityParam> paramMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(paramList)) {
for (ActivityParam oneParam : paramList) {
paramMap.put(oneParam.getCode(), oneParam);
}
}
if (!paramMap.isEmpty()) {
List<Field> allFields = ClassTools.getAllFields(this.joinRule);
for (Field oneField : allFields) {
String fieldName = oneField.getName();
BeanFieldName ann = oneField.getAnnotation(BeanFieldName.class);
if (ann != null) {
fieldName = ann.value();
}
ActivityParam paramValue = paramMap.get(fieldName);
if (paramValue != null) {
ObjectTools.setValue(obj, fieldName, paramValue.getValue());
}
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public Object initAwardRule(List<ActivityParam> paramList) {
try {
final Class unsafeClass = Class.forName("sun.misc.Unsafe");
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
final Object unsafe = theUnsafeField.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
Object obj = allocateInstance.invoke(unsafe, this.awardRule);
Map<String, ActivityParam> paramMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(paramList)) {
for (ActivityParam oneParam : paramList) {
paramMap.put(oneParam.getCode(), oneParam);
}
}
if (!paramMap.isEmpty()) {
List<Field> allFields = ClassTools.getAllFields(this.awardRule);
for (Field oneField : allFields) {
String fieldName = oneField.getName();
BeanFieldName ann = oneField.getAnnotation(BeanFieldName.class);
if (ann != null) {
fieldName = ann.value();
}
ActivityParam paramValue = paramMap.get(fieldName);
if (paramValue != null) {
ObjectTools.setValue(obj, fieldName, paramValue.getValue());
}
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public String getType() {
return type;
}
public int getIndex() {
return index;
}
public Class<? extends BaseActivityConfig> getActivityConfig() {
return activityConfig;
}
public Class<? extends BaseActivityJoinRule> getJoinRule() {
return joinRule;
}
public Class<? extends BaseActivityAwardRule> getAwardRule() {
return awardRule;
}
public Class<? extends ActivityHandler> getHandler() {
return handler;
}
public String getDescription() {
return description;
}
}

View File

@@ -0,0 +1,52 @@
package project.mall.activity.attribute;
/**
* 水果转盘活动规则属性枚举
*/
public enum FruitDialActivityConfigAttrEnum {
INIT_LOTTERY_SCORE( "initLotteryScore", "默认抽奖积分", "每个卖家默认抽奖积分"),
INVITE_AWARD_SCORE( "inviteAwardScore", "邀请获得积分奖励", "每邀请一个卖家获得积分"),
FIRST_RECHARGE_AMOUNT_LIMIT( "firstRechargeAmountLimit", "抽奖条件(首充金额)", "卖家首次充值金额,满足条件可获得抽奖次数"),
MIN_RECEIVE_MONEY_THRESHOLD( "minReceiveMoneyThreshold", "活动最小领取彩金", "累计最小领取彩金"),
SCORE_EXCHANGE_LOTTERY_TIME_RATIO( "scoreExchangeLotteryTimeRatio", "积分兑换抽奖次数", "积分,兑换一次抽奖"),
;
private String code;
private String title;
private String description;
private FruitDialActivityConfigAttrEnum(String code, String title, String description) {
this.code = code;
this.title = title;
this.description = description;
}
public static FruitDialActivityConfigAttrEnum codeOf(String inputCode) {
FruitDialActivityConfigAttrEnum[] values = FruitDialActivityConfigAttrEnum.values();
for (FruitDialActivityConfigAttrEnum one : values) {
if (one.getCode().equalsIgnoreCase(inputCode)) {
return one;
}
}
return null;
}
public String getCode() {
return code;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
}

View File

@@ -0,0 +1,18 @@
package project.mall.activity.core;
import lombok.Data;
/**
* 活动处理器的上下文
*
* @author caster
*
*/
@Data
public class ActivityContext {
private long joinActivityKey;
private long triggerTraceKey;
}

View File

@@ -0,0 +1,176 @@
package project.mall.activity.core;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import kernel.service.TransactionMethodFragmentFun;
import kernel.util.JsonUtils;
import lombok.extern.log4j.Log4j2;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.core.vo.ActivityUserResultInfo;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.handler.ActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import project.mall.activity.service.ActivityLibraryService;
import util.concurrent.gofun.core.FunParams;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 用户相关事件满足活动要求,将统一通过本入口校验、驱动活动进展
*
* @author caster
*/
@Component
public class ActivityEventDispatcher {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
ActivityLibraryService activityLibraryService;
@Resource
private TransactionMethodFragmentFun transactionMethodFragmentFun;
//@RedisMQListener("activity_touch_event")
public void onMessage(BaseActivityMessage activityMessage) {
logger.info("ActivityEventDispatcher 监听到活动相关事件: {}, 将触发活动处理逻辑...", JsonUtils.bean2Json(activityMessage));
List<ActivityLibrary> optionActivityList = getValidActivitys();
if (CollectionUtil.isEmpty(optionActivityList)) {
return;
}
// 针对活动触发事件较多的情况,建议先将活动事件存储起来,再在异步工作中进行处理... TODO
Date now = new Date();
// 先通过同步方式处理
// 一个活动类型对应一个 ActivityHandler 子类,但是一个事件可能有多个活动处理器感兴趣.
for (ActivityLibrary activityEntity : optionActivityList) {
// 判断活动是否过期 TODO
if (StrUtil.isNotBlank(activityMessage.getActivityId())) {
if (!Objects.equals(activityMessage.getActivityId(), activityEntity.getId().toString())) {
// 指定了活动ID的情况不匹配则直接退出
continue;
}
}
if (activityEntity.getStatus() != 1) {
// 活动无效状态
continue;
}
if (activityEntity.getStartTime().getTime() > activityMessage.getRefTime()
|| activityEntity.getEndTime().getTime() < activityMessage.getRefTime()) {
// 越过了活动的有效时间边界
continue;
}
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityEntity.getType());
final ActivityHandler handler = ActivityHandlerLoader.getInstance().getHandler(activityType);
final ActivityInfo activityInfo = new ActivityInfo();
activityInfo.setActivityId(activityEntity.getId().toString());
activityInfo.setTitle(activityEntity.getTitleCn());
//activityInfo.setTriggerMode(activityEntity.getTriggerMode());
activityInfo.setType(activityEntity.getType());
activityInfo.setStartTime(activityEntity.getStartTime());
activityInfo.setEndTime(activityEntity.getEndTime());
// 转成对应的 javabean
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
if (StrUtil.isNotBlank(activityEntity.getActivityConfig())) {
List<ActivityParam> activityConfigParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getActivityConfig(), paramType);
BaseActivityConfig activityConfig = (BaseActivityConfig)activityType.initActivityConfig(activityConfigParamList);
activityInfo.setActivityConfig(activityConfig);
}
if (StrUtil.isNotBlank(activityEntity.getJoinRule())) {
List<ActivityParam> joinRuleParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getJoinRule(), paramType);
BaseActivityJoinRule joinRuleObj = (BaseActivityJoinRule)activityType.initJoinRule(joinRuleParamList);
activityInfo.setJoinRule(joinRuleObj);
}
if (StrUtil.isNotBlank(activityEntity.getAwardRule())) {
List<ActivityParam> awardRuleParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getAwardRule(), paramType);
BaseActivityAwardRule awardRuleObj = (BaseActivityAwardRule)activityType.initAwardRule(awardRuleParamList);
activityInfo.setAwardRule(awardRuleObj);
}
try {
FunParams inputParam = FunParams.newParam();
transactionMethodFragmentFun.runInTransaction(inputParam, param -> {
ActivityMultiState joinCheckState = handler.checkJoin(activityMessage, activityInfo);
if (joinCheckState.can() == ThreeStateEnum.TRUE) {
// 如果一个活动用户只能有一次参加记录,那么第二次走本流程,建议此时的 can 返回 false。本处也要做好防御处理
handler.initJoin(activityMessage, activityInfo, joinCheckState);
joinCheckState = handler.join(activityMessage, activityInfo, joinCheckState);
}
ActivityMultiState awardCheckState = handler.checkAward(activityMessage, activityInfo, joinCheckState);
if (awardCheckState.can() == ThreeStateEnum.TRUE) {
handler.award(activityMessage, activityInfo, awardCheckState);
}
// 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
ActivityUserResultInfo activityUserResultInfo = handler.getActivityResultData("activity_user_result_info").getAs(ActivityUserResultInfo.class);
handler.postActivity(activityMessage, activityInfo, joinCheckState, awardCheckState, activityUserResultInfo);
return param;
});
// ActivityMultiState joinCheckState = handler.checkJoin(activityMessage, activityInfo);
// if (joinCheckState.can() == ThreeStateEnum.TRUE) {
// // 如果一个活动用户只能有一次参加记录,那么第二次走本流程,建议此时的 can 返回 false。本处也要做好防御处理
// handler.initJoin(activityMessage, activityInfo, joinCheckState);
//
// joinCheckState = handler.join(activityMessage, activityInfo, joinCheckState);
// }
//
// ActivityMultiState awardCheckState = handler.checkAward(activityMessage, activityInfo, joinCheckState);
// if (awardCheckState.can() == ThreeStateEnum.TRUE) {
// handler.award(activityMessage, activityInfo, awardCheckState);
// }
//
// // 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
// ActivityUserResultInfo activityUserResultInfo = handler.getActivityResultData("activity_user_result_info").getAs(ActivityUserResultInfo.class);
// handler.postActivity(activityMessage, activityInfo, joinCheckState, awardCheckState, activityUserResultInfo);
} catch (Exception e) {
logger.error("[ActivityEventDispatcher onMessage] 用户:{} 参加活动:{} 处理逻辑报错, 用户事件消息:{}, error: ",
activityMessage.getUserId(), (activityType.getType() + " - " + activityEntity.getId()), JsonUtils.bean2Json(activityMessage), e);
}
}
}
/**
* 返回可用作事件处理的活动记录
*
* @return
*/
private List<ActivityLibrary> getValidActivitys() {
List<ActivityLibrary> retActivityList = activityLibraryService.listRunningActivity();
return retActivityList;
}
public void setActivityLibraryService(ActivityLibraryService activityLibraryService) {
this.activityLibraryService = activityLibraryService;
}
public void setTransactionMethodFragmentFun(TransactionMethodFragmentFun transactionMethodFragmentFun) {
this.transactionMethodFragmentFun = transactionMethodFragmentFun;
}
}

View File

@@ -0,0 +1,73 @@
package project.mall.activity.core;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* @author caster
*/
public class ActivityHandlerAssignableTypeFilter extends AbstractTypeHierarchyTraversingFilter {
private final Class<?> targetType;
/**
* Create a new AssignableTypeFilter for the given type.
* @param targetType the type to match
*/
public ActivityHandlerAssignableTypeFilter(Class<?> targetType) {
super(true, true);
this.targetType = targetType;
}
/**
* Return the {@code type} that this instance is using to filter candidates.
* @since 5.0
*/
public final Class<?> getTargetType() {
return this.targetType;
}
@Override
protected boolean matchClassName(String className) {
if (this.targetType.getName().equals(className)) {
// 不支持将接口类设置为 bean
return false;
}
return matchTargetType(className);
}
@Override
@Nullable
protected Boolean matchSuperClass(String superClassName) {
return matchTargetType(superClassName);
}
@Override
@Nullable
protected Boolean matchInterface(String interfaceName) {
return matchTargetType(interfaceName);
}
@Nullable
protected Boolean matchTargetType(String typeName) {
if (this.targetType.getName().equals(typeName)) {
// 剔除接口类
return false;
} else if (Object.class.getName().equals(typeName)) {
return false;
} else if (typeName.startsWith("project.mall.activity")) {
try {
Class<?> clazz = ClassUtils.forName(typeName, getClass().getClassLoader());
boolean isMatch = this.targetType.isAssignableFrom(clazz);
return isMatch;
}
catch (Throwable ex) {
// Class not regularly loadable - can't determine a match that way.
}
}
return false;
}
}

View File

@@ -0,0 +1,85 @@
package project.mall.activity.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.handler.ActivityHandler;
import java.util.*;
/**
* 统一管理活动处理器
*
* @author caster
*
*/
public class ActivityHandlerLoader {
private final static Logger logger = LoggerFactory.getLogger(ActivityHandlerLoader.class);
private static Map<ActivityTypeEnum, ActivityHandler> handlerMap = new HashMap();
private final static ActivityHandlerLoader instance = new ActivityHandlerLoader();
private ActivityHandlerLoader() {
}
static {
ActivityHandlerScanningProvider provider = new ActivityHandlerScanningProvider(false);
// 改写实现
provider.addIncludeFilter(new ActivityHandlerAssignableTypeFilter(ActivityHandler.class));
// 没标注为 bean 的不设置
// provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
Set<BeanDefinition> components = provider.findCandidateComponents("project.mall.activity");
logger.info("========> [ActivityHandlerDispatcher _init] components:" + components.size());
// List<ActivityHandler> allActivityHandlers = new ArrayList();
Set<ActivityTypeEnum> repeatCheck = new HashSet<>();
for (BeanDefinition component : components) {
// Object serviceObj = component.getSource(); // 拿到的不是 bean
String handlerClazzName = component.getBeanClassName();
Class<? extends ActivityHandler> apiServiceClazz = null;
try {
apiServiceClazz = (Class<? extends ActivityHandler>)Class.forName(handlerClazzName);
} catch (Exception e) {
logger.error("[ActivityHandlerDispatcher _init] 构建类:{} 的对象报错", handlerClazzName, e);
continue;
}
ActivityHandler serviceBean = null;
try {
serviceBean = wac.getBean(apiServiceClazz);
} catch (Exception e) {
logger.warn("========> [ActivityHandlerDispatcher _init] handlerClazzName:{} 不是一个SpringBean将创建给类的普通实例...", handlerClazzName);
try {
serviceBean = apiServiceClazz.newInstance();
} catch (Exception e2) {
logger.error("========> [ActivityHandlerDispatcher _init] 创建活动处理器类:{} 的实例对象失败", handlerClazzName, e2);
}
}
if (repeatCheck.contains(serviceBean.supportActivityType())) {
throw new RuntimeException("活动处理器:" + handlerClazzName + " 使用了重复的活动类型:" + serviceBean.supportActivityType());
}
handlerMap.put(serviceBean.supportActivityType(), serviceBean);
repeatCheck.add(serviceBean.supportActivityType());
}
}
public static ActivityHandlerLoader getInstance() {
return instance;
}
public ActivityHandler getHandler(ActivityTypeEnum activityType) {
if (activityType == null) {
throw new RuntimeException("未指定活动类型");
}
return handlerMap.get(activityType);
}
}

View File

@@ -0,0 +1,58 @@
package project.mall.activity.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
/**
* 注意:本扫描器只提取 ActivityHandler 类型的实现类
*
* @author caster
*/
public class ActivityHandlerScanningProvider extends ClassPathScanningCandidateComponentProvider {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* Protected constructor for flexible subclass initialization.
* @since 4.3.6
*/
protected ActivityHandlerScanningProvider() {
}
/**
* Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
* @param useDefaultFilters whether to register the default filters for the
* {@link Component @Component}, {@link Repository @Repository},
* {@link Service @Service}, and {@link Controller @Controller}
* stereotype annotations
* @see #registerDefaultFilters()
*/
public ActivityHandlerScanningProvider(boolean useDefaultFilters) {
super(useDefaultFilters);
}
/**
* Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}.
* @param useDefaultFilters whether to register the default filters for the
* {@link Component @Component}, {@link Repository @Repository},
* {@link Service @Service}, and {@link Controller @Controller}
* stereotype annotations
* @param environment the Environment to use
* @see #registerDefaultFilters()
*/
public ActivityHandlerScanningProvider(boolean useDefaultFilters, Environment environment) {
super(useDefaultFilters, environment);
}
// /**
// * 只要接口类
// * @param beanDefinition
// * @return
// */
// protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// AnnotationMetadata metadata = beanDefinition.getMetadata();
// return (metadata.isIndependent() && metadata.isInterface());
// }
}

View File

@@ -0,0 +1,288 @@
package project.mall.activity.core;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.exception.JoinActivityFailException;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.core.vo.ActivityUserResultInfo;
import project.mall.activity.core.vo.ValueOptional;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.handler.ActivityHandler;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import project.mall.activity.service.ActivityLibraryService;
import project.mall.activity.service.ActivityUserJoinLogService;
import project.mall.activity.service.ActivityUserService;
import java.util.List;
import java.util.Objects;
public class ActivityHelper {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
// protected ActivityHandleStatService activityHandleStatService;
protected ActivityLibraryService activityLibraryService;
protected ActivityUserJoinLogService activityUserJoinLogService;
protected ActivityUserService activityUserService;
// @Autowired
// RedisMQSender redisMQSender;
@Transactional
public void joinActivity(String activityId, String userId, String action, BaseActivityMessage extraInfo) {
ActivityLibrary activityEntity = activityLibraryService.findById(activityId);
if (activityEntity == null) {
throw new BusinessException(1, "活动不存在");
}
if (extraInfo.getEventTime() <= 0) {
extraInfo.setEventTime(System.currentTimeMillis());
}
if (extraInfo.getRefTime() <= 0) {
extraInfo.setRefTime(extraInfo.getEventTime());
}
if (activityEntity.getStatus() != 1) {
// 活动无效状态
throw new BusinessException(4, "禁用活动");
}
if (activityEntity.getStartTime().getTime() > extraInfo.getEventTime()) {
// 越过了活动的有效时间边界
throw new BusinessException(5, "活动未开始");
}
if (activityEntity.getEndTime().getTime() < extraInfo.getEventTime()) {
throw new BusinessException(6, "活动结束");
}
extraInfo.setActivityId(activityId);
extraInfo.setEventType(action);
extraInfo.setUserId(userId);
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityEntity.getType());
ActivityHandler handler = ActivityHandlerLoader.getInstance().getHandler(activityType);
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.setActivityId(activityEntity.getId().toString());
activityInfo.setTitle(activityEntity.getTitleCn());
activityInfo.setType(activityEntity.getType());
activityInfo.setStartTime(activityEntity.getStartTime());
activityInfo.setEndTime(activityEntity.getEndTime());
TypeReference paramType = new TypeReference<List<ActivityParam>>() {
};
if (StrUtil.isNotBlank(activityEntity.getActivityConfig())) {
List<ActivityParam> activityConfigParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getActivityConfig(), paramType);
BaseActivityConfig activityConfig = (BaseActivityConfig) activityType.initActivityConfig(activityConfigParamList);
activityInfo.setActivityConfig(activityConfig);
}
if (StrUtil.isNotBlank(activityEntity.getJoinRule())) {
List<ActivityParam> joinRuleParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getJoinRule(), paramType);
BaseActivityJoinRule joinRuleObj = (BaseActivityJoinRule) activityType.initJoinRule(joinRuleParamList);
activityInfo.setJoinRule(joinRuleObj);
}
if (StrUtil.isNotBlank(activityEntity.getAwardRule())) {
List<ActivityParam> awardRuleParamList = (List<ActivityParam>) JsonUtils.readValue(activityEntity.getAwardRule(), paramType);
BaseActivityAwardRule awardRuleObj = (BaseActivityAwardRule) activityType.initAwardRule(awardRuleParamList);
activityInfo.setAwardRule(awardRuleObj);
}
// 用户正式参加活动 + 顺利处理奖励逻辑,才算一次完整流程
boolean canJoin = false;
String joinLogId = "";
ActivityMultiState joinCheckState = handler.checkJoin(extraInfo, activityInfo);
if (joinCheckState.can() == ThreeStateEnum.TRUE) {
// 录入 activityUser 记录和 joinLog 记录 TODO
// 如果一个活动用户只能有一次参加记录,那么第二次走本流程,建议此时的 can 返回 false。本处也要做好防御处理
handler.initJoin(extraInfo, activityInfo, joinCheckState);
canJoin = true;
joinCheckState = handler.join(extraInfo, activityInfo, joinCheckState);
} else {
// 特殊处理,用户参加指定活动返回 false则抛异常
String errMsg = joinCheckState.getDescription();
if (StrUtil.isBlank(errMsg)) {
errMsg = "用户参加活动的行为:" + action + " 失败";
}
throw new JoinActivityFailException(errMsg);
}
ActivityMultiState awardCheckState = handler.checkAward(extraInfo, activityInfo, joinCheckState);
if (awardCheckState.can() == ThreeStateEnum.TRUE) {
handler.award(extraInfo, activityInfo, awardCheckState);
}
// 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
ActivityUserResultInfo activityUserResultInfo = handler.getActivityResultData("activity_user_result_info").getAs(ActivityUserResultInfo.class);
handler.postActivity(extraInfo, activityInfo, joinCheckState, awardCheckState, activityUserResultInfo);
}
public ValueOptional getActivityResult(String currentActivityType, String resultName) {
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(currentActivityType);
ActivityHandler handler = ActivityHandlerLoader.getInstance().getHandler(activityType);
ValueOptional result = handler.getActivityResultData(resultName);
return result;
}
public ActivityUser getActivityUser(String activityId, String userId, String actionType) {
ActivityUser activityUser = activityUserService.getActivityUser(activityId, userId, actionType);
return activityUser;
}
public BaseActivityConfig getActivityConfig(ActivityLibrary activityEntity) {
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityEntity.getType());
String configJson = activityEntity.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {
};
List<ActivityParam> configInfoList = (List<ActivityParam>) JsonUtils.readValue(configJson, paramType);
return (BaseActivityConfig) activityType.initActivityConfig(configInfoList);
}
/**
* 管理后台创建活动
*
* @param activityInfo
* @return
*/
public ActivityLibrary createActivity(SaveActivityRequestDTO activityInfo, long tenantId) {
if (activityInfo == null
|| (StrUtil.isNotBlank(activityInfo.getActivityId())
&& !Objects.equals(activityInfo.getActivityId(), "0"))) {
throw new BusinessException("新建活动记录不能指定活动ID");
}
ActivityLibrary activityEntity = new ActivityLibrary();
BeanUtil.copyProperties(activityInfo, activityEntity);
if (StrUtil.isNotBlank(activityInfo.getValidBeginTime())) {
activityEntity.setStartTime(DateUtil.parseDateTime(activityInfo.getValidBeginTime()));
}
if (StrUtil.isNotBlank(activityInfo.getValidEndTime())) {
activityEntity.setEndTime(DateUtil.parseDateTime(activityInfo.getValidEndTime()));
}
activityEntity.setLocation(99999);
activityEntity.setStatus(0);
activityEntity.setId(null);
//activityLibraryService.save(activityEntity);
return activityEntity;
}
/**
* 管理后台修改活动 或 租户管理员修改活动
*
* @param activityInfo
* @return
*/
public ActivityLibrary editActivity(SaveActivityRequestDTO activityInfo, long currentUserTenantId) {
if (activityInfo == null
|| StrUtil.isBlank(activityInfo.getActivityId())
|| Objects.equals(activityInfo.getActivityId(), "0")) {
throw new BusinessException("未指定活动ID");
}
long activityId = Long.parseLong(activityInfo.getActivityId());
ActivityLibrary activityEntity = null;//activityLibraryService.getById(activityId);
if (activityEntity == null) {
throw new BusinessException("活动记录不存在:" + activityInfo.getActivityId());
}
// 识别活动是否允许修改相关字段 TODO
if (StrUtil.isNotBlank(activityInfo.getTitle())) {
activityEntity.setTitleCn(activityInfo.getTitle());
}
activityEntity.setTags(activityInfo.getTags());
activityEntity.setDescriptionCn(activityInfo.getDescription());
activityEntity.setImageUrl(activityInfo.getImageUrl());
// if (activityEntity.getState() == 1 || activityEntity.getState() == 2) {
//
// } else {
activityEntity.setStartTime(DateUtil.parseDateTime(activityInfo.getValidBeginTime()));
activityEntity.setEndTime(DateUtil.parseDateTime(activityInfo.getValidEndTime()));
// }
activityEntity.setStatus(activityInfo.getState());
activityEntity.setLocation(activityInfo.getLocation());
activityEntity.setDetailUrl(activityInfo.getDetailUrl());
// if (activityInfo.getInheritMode() != null) {
// activityEntity.setInheritMode(activityInfo.getInheritMode());
// }
//activityLibraryService.updateById(activityEntity);
return activityEntity;
}
/**
* 加载活动详情
* 加载活动本身的简单数据
*
* @param id
* @return
*/
public ActivityLibrary loadActivity(String id) {
if (StrUtil.isBlank(id) || Objects.equals(id, "0")) {
throw new BusinessException("未指定活动ID");
}
long activityId = Long.parseLong(id);
ActivityLibrary activityEntity = null;//activityLibraryService.getById(activityId);
return activityEntity;
}
/**
* 提取活动详情
* 包含一些统计数据,活动周边数据等
*
* @param id
* @return
*/
public ActivityLibrary getActivityDetail(String id) {
if (StrUtil.isBlank(id) || Objects.equals(id, "0")) {
throw new BusinessException("未指定活动ID");
}
// 更多完善。。。 TODO
long activityId = Long.parseLong(id);
ActivityLibrary activityEntity = null;//activityLibraryService.getById(activityId);
return activityEntity;
}
// public void setActivityHandleStatService(ActivityHandleStatService activityHandleStatService) {
// this.activityHandleStatService = activityHandleStatService;
// }
public void setActivityLibraryService(ActivityLibraryService activityLibraryService) {
this.activityLibraryService = activityLibraryService;
}
public void setActivityTriggerEventService(ActivityUserJoinLogService activityTriggerEventService) {
this.activityUserJoinLogService = activityTriggerEventService;
}
public void setActivityUserService(ActivityUserService activityUserService) {
this.activityUserService = activityUserService;
}
}

View File

@@ -0,0 +1,60 @@
package project.mall.activity.core;
import lombok.Data;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import java.util.Date;
/**
* 活动信息表
*
* @author daydayup
* @date 2022-09-17 14:14:57
*/
@Data
public class ActivityInfo {
private String activityId;
/**
* 活动类型标记:
*/
private String type;
/**
* 活动标题
*/
private String title;
/**
* 活动有效开始时间,为空代表直接立即生效
*/
private Date startTime;
/**
* 活动有效截止时间,为空代表永久生效
*/
private Date endTime;
/**
* 用户触发活动模式0-全局被动生效1-租户内用户被动生效2-用户主动参与生效3-用户行为触发
*/
private Integer triggerMode;
private Integer allowJoinTimes;
private BaseActivityConfig activityConfig;
/**
* 活动加入规则,已经被解析成了对应的类型,业务处理器可以进行强制类型转换
*/
private BaseActivityJoinRule joinRule;
/**
* 活动奖励规则,已经被解析成了对应的类型,业务处理器可以进行强制类型转换
*/
private BaseActivityAwardRule awardRule;
}

View File

@@ -0,0 +1,58 @@
package project.mall.activity.core;
import lombok.Data;
/**
* 活动处理相关的双状态
*
*/
@Data
public class ActivityMultiState {
/**
* 是否可以
*/
private ThreeStateEnum can;
/**
* 是否已执行
*/
private ThreeStateEnum has;
// 对应次数
private int times;
private String description;
public ThreeStateEnum can() {
return this.can;
}
public void can(ThreeStateEnum can) {
this.can = can;
}
public ThreeStateEnum has() {
return this.has;
}
public void has(ThreeStateEnum has) {
this.has = has;
}
public int getTimes() {
return this.times;
}
public void setTimes(int times) {
this.times = times;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@@ -0,0 +1,81 @@
package project.mall.activity.core;
import lombok.Data;
@Data
public class SaveActivityRequestDTO {
private String activityId;
/**
* 活动类型标记:
*/
private String type;
/**
* 活动标题
*/
private String title;
/**
* 活动标签,多个标签之间用","隔开
*/
private String tags;
/**
* 活动有效开始时间,为空代表直接立即生效
*/
private String validBeginTime;
/**
* 活动有效截止时间,为空代表永久生效
*/
private String validEndTime;
/**
* 用户触发活动模式0-全局被动生效1-租户内用户被动生效2-用户主动参与生效3-用户行为触发
*/
private Integer triggerMode = 1;
private Integer inheritMode = -1;
/**
* 活动状态0-未启动1-启动
*/
private Integer state = 0;
/**
* 活动加入规则json结构
*/
private String joinRule;
/**
* 活动奖励规则json结构
*/
private String awardRule;
/**
* 活动详情网页地址
*/
private String detailUrl;
/**
* 活动封面图片地址
*/
private String imageUrl;
/**
* 活动排序位置值越小排序越靠前默认值99999
*/
private Integer location = 99999;
/**
* 活动备注
*/
private String description;
/**
* 活动介绍,支持富文本
*/
private String content;
}

View File

@@ -0,0 +1,47 @@
package project.mall.activity.core;
import cn.hutool.core.util.StrUtil;
public enum ThreeStateEnum {
TRUE("true", ""),
FALSE("false", ""),
UNKNOW("unknow", "未知"),
;
private String state;
private String stateName;
ThreeStateEnum(String state, String stateName) {
this.state = state;
this.stateName = stateName;
}
public static ThreeStateEnum typeOf(String state) {
if (StrUtil.isBlank(state)) {
return UNKNOW;
}
ThreeStateEnum values[] = ThreeStateEnum.values();
for (ThreeStateEnum one : values) {
if (one.getState().equalsIgnoreCase(state)) {
return one;
}
}
return UNKNOW;
}
public String getState() {
return this.state;
}
public String getStateName() {
return stateName;
}
}

View File

@@ -0,0 +1,8 @@
package project.mall.activity.core.exception;
public class JoinActivityFailException extends RuntimeException {
public JoinActivityFailException(String errMsg) {
super(errMsg);
}
}

View File

@@ -0,0 +1,21 @@
package project.mall.activity.core.vo;
import lombok.Data;
@Data
public class ActivityParam {
/**
* 参数类型参考枚举类型, 例如FruitDialActivityJoinRuleAttrEnum
* 关联枚举类型后,方便业务代码做判断调用
*/
private String code;
// 对外展示的参数标题
private String title;
// 活动实例使用,对应参数值
private String value;
// 参数说明,可用于支持前端展示
private String description;
}

View File

@@ -0,0 +1,55 @@
package project.mall.activity.core.vo;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 活动处理过程中的返回信息放到这里
*/
public class ActivityResultDataContext {
private static Logger logger = LoggerFactory.getLogger(ActivityResultDataContext.class);
private boolean contextDataInited = false;
private Map<String, ValueOptional> resultData = new HashMap();
public void set(String attrName, Object value) {
if (StrUtil.isBlank(attrName)) {
return;
}
ValueOptional option = new ValueOptional(value);
this.resultData.put(attrName, option);
}
public ValueOptional get(String attrName) {
if (StrUtil.isBlank(attrName)) {
return null;
}
return this.resultData.get(attrName);
}
void initContext(Map<String, Object> initData) {
if (contextDataInited) {
logger.error("[ActivityResultDataContext initContext] 已经初始化过了");
return;
}
if (initData == null) {
return;
}
Set<Map.Entry<String, Object>> entrySets = initData.entrySet();
for (Map.Entry<String, Object> oneEntry : entrySets) {
ValueOptional option = new ValueOptional(oneEntry.getValue());
this.resultData.put(oneEntry.getKey(), option);
}
this.contextDataInited = true;
}
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.core.vo;
import lombok.Data;
import java.util.Map;
@Data
public class ActivityUserResultInfo {
private String refId;
private int refType;
private Map<String, Object> extraInfo;
}

View File

@@ -0,0 +1,53 @@
package project.mall.activity.core.vo;
public class ValueOptional {
private Object value;
public ValueOptional(Object value) {
this.value = value;
}
public Object getValue() {
return this.value;
}
public Integer getAsInt() {
if (this.value == null) {
return null;
}
return Integer.parseInt(this.value.toString());
}
public Long getAsLong() {
if (this.value == null) {
return null;
}
return Long.parseLong(this.value.toString());
}
public Double getAsDouble() {
if (this.value == null) {
return null;
}
return Double.parseDouble(this.value.toString());
}
public String getAsString() {
if (this.value == null) {
return null;
}
return this.value.toString();
}
public <T> T getAs(Class<T> clazz) {
if (this.value == null) {
return null;
}
return (T)this.value;
}
}

View File

@@ -0,0 +1,100 @@
package project.mall.activity.dto;
import lombok.Data;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Data
public class ActivityEditInfoDTO implements Serializable {
/**
* 活动ID
*/
private String id;
private String templateId;
private String type;
// // 全局唯一,和 id 一一对应
// private String activityCode;
/**
* 活动名称
*/
private String title;
/**
* 活动地址
*/
private String detailUrl;
/**
* 活动图片
*/
private String imageUrl;
/**
* 活动开始时间
*/
private String startTime;
/**
* 活动开始结束
*/
private String endTime;
/**
* 通用的活动规则信息集合
*/
private List<ActivityParam> activityConfigInfos;
/**
*
*/
private List<ActivityParam> joinRules;
/**
*
*/
private List<ActivityParam> awardRules;
private List<MultiLanguageField> i18nTitles;
private List<MultiLanguageField> i18nDescriptions;
// 取代 i18nTitles 和 i18nDescriptions
private List<ActivityI18nContent> i18nContents;
/**
* 活动包含的奖品列表
*/
private List<ActivityPrizeDTO> prizeList;
/**
* 活动状态 (1-启用,0-禁用)
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 操作用户账号
*/
private String createBy;
}

View File

@@ -0,0 +1,14 @@
package project.mall.activity.dto;
import lombok.Data;
@Data
public class ActivityI18nContent {
private String lang;
private String title;
private String description;
}

View File

@@ -0,0 +1,56 @@
package project.mall.activity.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrizeDTO {
private String id;
private String poolId;
/**
* 奖品名称
*/
private String prizeName;
/**
* 奖品类型, 1-实物、2-彩金
*/
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 当所有随机奖励都没有获取时,当前奖品是否是默认奖品 1、默认奖品谢谢惠顾
*/
private Integer defaultPrize;
/**
* 奖品数量
*/
private int maxQuantity;
/**
* 中奖几率
*/
private BigDecimal odds;
/**
* 图片地址
*/
private String image;
}

View File

@@ -0,0 +1,41 @@
package project.mall.activity.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrizePoolEditDTO {
/**
* 奖品池ID
*/
private String id;
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
private String image;
private String remark;
/**
* 奖品名称
*/
private List<MultiLanguageField> i18nNames;
/**
* 操作用户账号
*/
private String createBy;
}

View File

@@ -0,0 +1,40 @@
package project.mall.activity.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrizePoolShowDTO {
/**
* 奖品池ID
*/
private String id;
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
private String image;
/**
* 奖品名称
*/
private String prizeName;
private String createTime;
private String createBy;
private String remark;
}

View File

@@ -0,0 +1,97 @@
package project.mall.activity.dto;
import lombok.Data;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.rule.BaseActivityConfig;
import project.mall.activity.rule.award.BaseActivityAwardRule;
import project.mall.activity.rule.join.BaseActivityJoinRule;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Data
public class ActivitySaveInfoDTO<T1 extends BaseActivityConfig, T2 extends BaseActivityJoinRule, T3 extends BaseActivityAwardRule> implements Serializable {
/**
* 活动ID
*/
private String id;
private String templateId;
/**
* 活动名称
*/
private String title;
/**
* 活动地址
*/
private String detailUrl;
/**
* 活动图片
*/
private String imageUrl;
/**
* 活动开始时间
*/
private String startTime;
/**
* 活动开始结束
*/
private String endTime;
/**
* 默认抽奖积分
*/
private Integer initLotteryScore;
/**
* 通用的活动规则信息集合
*/
private T1 activityConfig;
/**
*
*/
private T2 joinRule;
/**
*
*/
private T3 awardRule;
private List<MultiLanguageField> i18nTitles;
private List<MultiLanguageField> i18nDescriptions;
/**
* 活动包含的奖品列表
*/
private List<ActivityPrizeDTO> prizeList;
/**
* 活动状态 (1-启用,0-禁用)
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
}

View File

@@ -0,0 +1,105 @@
package project.mall.activity.dto;
import kernel.bo.EntityObject;
import lombok.Data;
import java.util.Date;
/**
* 系统活动库表
*
* @author caster
*/
@Data
public class ActivityShowDTO {
private String id;
private String templateId;
/**
* 活动类型编码参考枚举类型ActivityTypeEnum
*/
private String type;
//private String activityCode;
/**
* 活动标题
*/
private String title;
/**
* 活动标签,多个标签之间用","隔开
*/
private String tags;
/**
* 活动有效开始时间,不给空值
*/
private Date startTime;
/**
* 活动有效截止时间,不给空值
*/
private Date endTime;
/**
* 活动允许重复参加的次数0-无限制n 代表只能参加 N 次)
*/
private Integer allowJoinTimes;
/**
* 活动状态0-未启动1-启动2-结束活动
*/
private Integer status;
/**
* 活动配置信息,难以区分分类时可以直接取代 jonRule 和 awardRule
* 参考枚举类型ActivityTypeEnum
* 存储结构为: ActivityParam 对象集合
*/
private String activityConfig;
/**
* 活动加入规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String joinRule;
/**
* 活动奖励规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String awardRule;
/**
* 活动详情网页地址
*/
private String detailUrl;
/**
* 活动封面图片地址
*/
private String imageUrl;
/**
* 活动排序位置值越小排序越靠前默认值99999
*/
private Integer location;
/**
* 活动详情内容
*/
private String description;
private Date createTime;
private Date updateTime;
/**
* 活动是否显示状态0-不显示1-显示
*/
private Integer isShow;
private String lastOperator;
}

View File

@@ -0,0 +1,12 @@
package project.mall.activity.dto;
import lombok.Data;
@Data
public class MultiLanguageField {
private String lang;
private String content;
}

View File

@@ -0,0 +1,92 @@
package project.mall.activity.dto.lottery;
import lombok.Data;
import project.mall.activity.dto.ActivityPrizeDTO;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Data
public class ActivityLotteryInfoDTO implements Serializable {
/**
* 活动ID
*/
private String id;
/**
* 活动名称
*/
private String title;
/**
* 活动地址
*/
private String detailUrl;
/**
* 活动图片
*/
private String imageUrl;
/**
* 活动开始时间
*/
private String startTime;
/**
* 活动开始结束
*/
private String endTime;
/**
* 默认抽奖积分
*/
private Integer initLotteryScore;
/**
* 默认多少积分兑换一次抽奖
*/
private Integer scoreExchangeLotteryTimeRatio;
/**
* 邀请获得积分奖励
*/
private Integer inviteAwardScore;
/**
* 活动最小领取彩金
*/
private Integer minReceiveMoneyThreshold;
/**
* 抽奖条件(首充金额)
*/
private Double firstRechargeAmountLimit;
/**
* 活动状态 (1-启用,0-禁用)
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 活动包含的奖品列表
*/
private List<ActivityPrizeDTO> prizeList;
}

View File

@@ -0,0 +1,122 @@
package project.mall.activity.dto.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import project.mall.activity.dto.ActivityPrizeDTO;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
@Data
public class LotteryDTO implements Serializable {
/**
* 活动ID
*/
private String id;
/**
* 活动名称
*/
private String name;
/**
* 当前用户可用积分
*/
private Integer points;
/**
* 活动地址
*/
private String link;
/**
* 活动图片
*/
private String images;
/**
* 活动开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String startTime;
/**
* 活动开始结束
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String endTime;
/**
* 默认抽奖次数
*/
private Integer lotteryNumber;
/**
* 默认多少积分兑换一次抽奖
*/
private Integer pointsToNumber;
/**
* 邀请获得积分奖励
*/
private Integer invitePoints;
/**
* 活动最小领取彩金
*/
private BigDecimal minPoints;
/**
* 抽奖条件
*/
private Double lotteryCondition;
/**
* 活动状态 (1-启用,0-禁用)
*/
private Integer state;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String updateTime;
/**
* 创建人
*/
private String createBy;
private String description;
/**
* 活动描述英文
*/
private String descEn;
/**
* 活动描述中文
*/
private String descCn;
/**
* 奖品ID主键
*/
List<String> prizeIds;
/**
* 活动包含的奖品列表
*/
private List<ActivityPrizeDTO> prizeList;
}

View File

@@ -0,0 +1,29 @@
package project.mall.activity.dto.lottery;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class LotteryPrizeDTO {
/**
* 奖品ID
*/
private String id ;
/**
* 奖品名称
*/
private String prizeName;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品数量
*/
private Integer num;
}

View File

@@ -0,0 +1,96 @@
package project.mall.activity.dto.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class LotteryReceiveDTO {
/**
* 领取用户ID
*/
private String partyId;
private String uid;
/**
* 领取用户名称
*/
private String partyName;
/**
* 奖品ID
*/
private String prizeIds;
/**
* 奖品名称
*/
private String lotteryName;
/**
* 推荐人名称
*/
private String recommendName;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String applyTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String createTime;
/**
* 派发时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String issueTime;
/**
* 派发状态 (1-已派发,0-未派发)
*/
private Integer state;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品类型, 1-实物、2-彩金
*/
private Integer prizeType;
/**
* 邮箱
*/
private String email;
/**
* 电话号码
*/
private String phone;
/**
* 账号
*/
private String username;
/**
* 店铺名
*/
private String sellerName;
/**
* 记录ID
*/
private String id;
}

View File

@@ -0,0 +1,43 @@
package project.mall.activity.dto.lottery;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class LotteryReceivePrizeDTO {
private String id;
/**
* 领取用户ID
*/
private String partyId;
/**
* 店铺名
*/
private String sellerName;
/**
* 奖品类型, 1-实物、2-彩金
*/
private Integer prizeType;
/**
* 备注
*/
private String remark;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 实物奖品记录集合
*/
private List<LotteryPrizeDTO> prizeDTOList;
}

View File

@@ -0,0 +1,107 @@
package project.mall.activity.dto.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class LotteryRecordDTO {
/**
* 领取用户ID
*/
private String partyId;
private String uid;
/**
* 活动ID
*/
private String lotteryId;
/**
* 活动名称
*/
private String lotteryName;
/**
* 领取用户名
*/
private String partyName;
/**
* 奖品ID
*/
private String prizeId;
/**
* 奖品名称
*/
private String prizeName;
/**
* 推荐人名称
*/
private String recommendName;
/**
* 中奖时间
*/
private String lotteryTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String createTime;
/**
* 领取时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String receiveTime;
/**
* 是否领取 (1-已领取,0-未领取)
*/
private Integer receiveState;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品类型, 1-实物、2-彩金
*/
private Integer prizeType;
/**
* 邮箱
*/
private String email;
/**
* 电话号码
*/
private String phone;
/**
* 账号
*/
private String username;
/**
* 店铺名
*/
private String sellerName;
/**
* 记录ID
*/
private String id;
}

View File

@@ -0,0 +1,20 @@
package project.mall.activity.dto.lottery;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class LotteryRecordSumDTO implements Serializable {
/**
* 累计彩金
*/
private BigDecimal amount;
/**
* 累计实物
*/
private Long goodsNum;
}

View File

@@ -0,0 +1,92 @@
package project.mall.activity.event;
import cn.hutool.core.collection.CollectionUtil;
import kernel.util.JsonUtils;
import lombok.extern.log4j.Log4j2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import project.blockchain.event.message.RechargeSuccessEvent;
import project.blockchain.event.model.RechargeInfo;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.*;
import project.mall.activity.event.message.ActivityUserRechargeMessage;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.seller.SellerService;
import project.mall.seller.model.Seller;
import project.party.PartyService;
import project.party.model.Party;
import project.party.model.UserRecom;
import project.party.recom.UserRecomService;
import project.wallet.WalletLog;
import project.wallet.WalletLogService;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 监听用户充值,触发活动
*
* @author caster
*/
//@Log4j2 无效
//@Component
public class ActivityUserRechargeListener implements ApplicationListener<RechargeSuccessEvent> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private ActivityEventDispatcher activityEventDispatcher;
private SellerService sellerService;
private WalletLogService walletLogService;
@Override
public void onApplicationEvent(RechargeSuccessEvent event) {
logger.info("ActivityUserRechargeListener 监听到活动相关事件: {}, 将触发活动处理逻辑...", JsonUtils.bean2Json(event.getRechargeInfo()));
RechargeInfo rechargeInfo = event.getRechargeInfo();
Seller sellerEntity = sellerService.getSeller(rechargeInfo.getApplyUserId());
if (sellerEntity == null) {
// 非商家用户,充值不参加水果转盘活动
return;
}
WalletLog walletLog = walletLogService.findById(rechargeInfo.getWalletLogId());
if (walletLog == null) {
logger.error("ActivityUserRechargeListener 监听到的充值记录: {} 不存在", rechargeInfo.getWalletLogId());
return;
}
ActivityUserRechargeMessage activityMessage = new ActivityUserRechargeMessage();
Date now = new Date();
String eventId = rechargeInfo.getWalletLogId() + ":" + now.getTime();
activityMessage.setEventId(eventId);
activityMessage.setEventTime(now.getTime());
activityMessage.setRefTime(walletLog.getCreateTime().getTime());
activityMessage.setEventType(FirstRechargeFruitDialActivityHandler.ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
activityMessage.setUserId(rechargeInfo.getApplyUserId());
activityMessage.setTransId(rechargeInfo.getWalletLogId());
activityMessage.setUsdtAmount(rechargeInfo.getAmount());
activityEventDispatcher.onMessage(activityMessage);
}
public void setActivityEventDispatcher(ActivityEventDispatcher activityEventDispatcher) {
this.activityEventDispatcher = activityEventDispatcher;
}
public void setSellerService(SellerService sellerService) {
this.sellerService = sellerService;
}
public void setWalletLogService(WalletLogService walletLogService) {
this.walletLogService = walletLogService;
}
}

View File

@@ -0,0 +1,13 @@
package project.mall.activity.event.message;
import lombok.Data;
/**
* 用户点击转盘抽奖消息,专用于活动模块
*
*/
@Data
public class ActivityUserLotteryMessage extends BaseActivityMessage {
private String lang;
}

View File

@@ -0,0 +1,21 @@
package project.mall.activity.event.message;
import lombok.Data;
/**
* 用户充值消息,专用于活动模块
*
*/
@Data
public class ActivityUserRechargeMessage extends BaseActivityMessage {
private String userName;
private Double usdtAmount;
// private int currency;
//
// private Double rmbAmount;
private String transId;
}

View File

@@ -0,0 +1,44 @@
package project.mall.activity.event.message;
import lombok.Data;
/**
* 活动触发事件相关的信息类
*/
@Data
public class BaseActivityMessage {
/**
* 事件触发时间毫秒时间戳
*/
private long eventTime;
/**
* 业务判断可能不是使用事件时间,可能是另外一个时间,因此增加一个辅助的 refTime
*/
private long refTime;
/**
* 事件类型参考枚举类型ActivityTouchEventTypeEnum
*/
private String eventType;
/**
* 事件全局唯一id
*/
private String eventId;
/**
* 某个用户触发的相关事件
*/
private String userId;
/**
* 指定了属于某个活动id非必填
*/
private String activityId;
/**
* 批量多次执行活动事件
*/
private int batchJoinTimes = 1;
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.event.source;
import project.mall.activity.event.message.ActivityUserRechargeMessage;
/**
* 用户充值事件,专用于活动模块
*
*/
public class ActivityPlayerRechargeEvent extends ActivityTriggerEvent<ActivityUserRechargeMessage> {
private ActivityUserRechargeMessage info;
public ActivityPlayerRechargeEvent(Object source, ActivityUserRechargeMessage info) {
super(source, info);
}
}

View File

@@ -0,0 +1,20 @@
package project.mall.activity.event.source;
import org.springframework.context.ApplicationEvent;
import project.mall.activity.event.message.BaseActivityMessage;
/**
* 活动触发事件父类
*/
public class ActivityTriggerEvent<T extends BaseActivityMessage> extends ApplicationEvent {
private T message;
public ActivityTriggerEvent(Object source, T info) {
super(source);
this.message = info;
}
public T getInfo() {
return this.message;
}
}

View File

@@ -0,0 +1,439 @@
package project.mall.activity.handler;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.ActivityInfo;
import project.mall.activity.core.ActivityMultiState;
import project.mall.activity.core.ThreeStateEnum;
import project.mall.activity.core.vo.ActivityResultDataContext;
import project.mall.activity.core.vo.ActivityUserResultInfo;
import project.mall.activity.core.vo.ValueOptional;
import project.mall.activity.dto.ActivityEditInfoDTO;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityUserJoinLog;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.service.*;
import java.util.Date;
import java.util.Objects;
/**
* 活动处理器,管理活动的整个生命周期的关键事件
*
* 一个活动类型,对应一个 ActivityHandler 子类,
* 注意注意活动处理器的子类必须位于包或更深级的子包project.mall.activity 之下 !!!
*
*/
public abstract class ActivityHandler {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected ThreadLocal<ActivityResultDataContext> ACTIVITY_RESULT_DATA_CONTEXT = new ThreadLocal<ActivityResultDataContext>(){
protected synchronized ActivityResultDataContext initialValue() {
return new ActivityResultDataContext();
}
};
protected ActivityTemplateService activityTemplateService;
protected ActivityLibraryService activityLibraryService;
protected ActivityUserJoinLogService activityUserJoinLogService;
protected ActivityUserService activityUserService;
protected ActivityPrizePoolService activityPrizePoolService;
protected ActivityPrizeService activityPrizeService;
/**
* 活动处理器服务于指定活动
*
* @return
*/
public abstract ActivityTypeEnum supportActivityType();
/**
* 创建/修改活动
*
* @param activityInfo
* @return
*/
public abstract ActivityLibrary saveActivity(ActivityEditInfoDTO activityInfo);
/**
* 加载活动配置详情
*
* @param activityId
* @return
*/
public abstract ActivityEditInfoDTO getActivityDetail(String activityId, String lang);
/**
* 新建活动前,加载活动模板的参数信息
*
* @param templateId
* @return
*/
public abstract ActivityEditInfoDTO loadActivityTemplate(String templateId);
/**
* 启动活动的额外操作,例如启动定时任务监控....
*
* @param activityId
*/
public abstract void start(String activityId);
/**
* 判断用户是否已经参加了该活动以及是否允许参加该活动的最基础判断,
* 注意:
* 本方法仅做最基础的判断,如果本次判断未能返回明确的结果,或者具体业务有更特殊的判断,可由子类复用,或覆盖父类的判断逻辑
*
* @param activityMessage
* @param activityInfo
* @return
*/
public ActivityMultiState checkJoin(BaseActivityMessage activityMessage, ActivityInfo activityInfo) {
// 防止线程池问题导致的其他线程处理结果泄露
ACTIVITY_RESULT_DATA_CONTEXT.remove();
// 如果本次判断无法明确判定结论,需要设置为 UNKNOW交由具体业务实现再次判断
ActivityMultiState retState = new ActivityMultiState();
// 默认要是 UNKNOW需要业务代码做二次把关但是如果返回 false则代表明确不能加入活动
retState.can(ThreeStateEnum.UNKNOW);
// 默认要是 UNKNOW
retState.has(ThreeStateEnum.UNKNOW);
if (StrUtil.isNotBlank(activityMessage.getActivityId())) {
if (!Objects.equals(activityMessage.getActivityId(), activityInfo.getActivityId())) {
// 指定了活动ID的情况不匹配则直接退出
retState.can(ThreeStateEnum.FALSE);
retState.has(ThreeStateEnum.FALSE);
retState.setDescription("当前活动不支持该活动事件");
return retState;
}
}
String userId = activityMessage.getUserId();
if (StrUtil.isBlank(userId) || Objects.equals(userId, "0")) {
retState.setDescription("未指定当前参与活动的用户");
return retState;
}
/**
* 首先,根据通用逻辑识别当前用户是否已参与过当前活动
*/
ActivityUser userJoinRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), userId, activityMessage.getEventType());
if (userJoinRecord != null && userJoinRecord.getFirstTriggerTime() > 0) {
// 被动触发的 activityUser 记录FirstTriggerTime 值为0
int joinTimes = userJoinRecord.getJoinTimes();
int maxAllowedTimes = userJoinRecord.getAllowJoinTimes();
retState.setTimes(joinTimes);
retState.has(ThreeStateEnum.TRUE);
if (userJoinRecord.getValidBeginTime().getTime() > activityMessage.getEventTime()
|| userJoinRecord.getValidEndTime().getTime() < activityMessage.getEventTime()) {
// 越过了活动的有效时间边界
retState.can(ThreeStateEnum.FALSE);
retState.setDescription("已错过活动时间");
return retState;
}
// 有记录不代表当前次数下参与过
// 可参与一次或多次
if (maxAllowedTimes == 0) {
// 代表无限次数
// 不能武断,需要交给子类进一步判断
//retState.can(ThreeStateEnum.TRUE);
return retState;
}
if (joinTimes + activityMessage.getBatchJoinTimes() - 1 > maxAllowedTimes) {
// 已达最大次数,可能当前 join 事件属于上次 join 的一个收尾处理(不累加 joinTimes所以此处要减 1
// 例如:允许最多参加活动 10 次,此时观察到历史参加次数达到了 10 次,但是第 10 次是个长期事件,在具体业务中属于刚刚开始还未结束
// 的状态,所以本次仍然允许 join可以看到 join 事件有个 status 属性,值为 0 代表该 join 事件并未完结
retState.can(ThreeStateEnum.FALSE);
retState.setDescription("超过了参加活动的允许次数");
return retState;
} else if (joinTimes + activityMessage.getBatchJoinTimes() - 1 < maxAllowedTimes) {
// 允许次数还未消耗完,原则上可以继续参与
// 不能武断,需要交给子类进一步判断
// retState.can(ThreeStateEnum.TRUE);
return retState;
} else {
// 特殊情况:达到上限次数,但是最后一次还没完成的情况 TODO
if (userJoinRecord.getStatus().intValue() == 0) {
// 可以多次参加的活动,并且当前场次还没结束
// 不能武断,需要交给子类进一步判断
// retState.can(ThreeStateEnum.TRUE);
} else {
// 可以多次参加的活动,并且最近一次的参与已经结束
// 所以需要注意:即使返回 has 值为 false也不能代表用户一次都没有参加活动而是代表当前场次下是否参加过
retState.can(ThreeStateEnum.FALSE);
retState.setDescription("超过了参加活动的允许次数");
}
}
} else {
retState.has(ThreeStateEnum.FALSE);
}
return retState;
}
public final void initJoin(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState preState) {
ActivityLibrary activityLibrary = activityLibraryService.findById(activityInfo.getActivityId());
// 最近一次 joinLog 记录
ActivityUserJoinLog lastLog = activityUserJoinLogService.lastJoinLog(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
// 放进事务
ActivityUserJoinLog triggerEvent = new ActivityUserJoinLog();
triggerEvent.setActivityId(activityInfo.getActivityId());
triggerEvent.setUserId(activityMessage.getUserId());
triggerEvent.setEventType(activityMessage.getEventType());
triggerEvent.setEventKey(activityMessage.getEventId());
// 没有关联业务表
triggerEvent.setRefId(null);
triggerEvent.setRefType(0);
triggerEvent.setStatus(1);
triggerEvent.setTriggerTime(activityMessage.getEventTime());
triggerEvent.setExtraInfo(null);
if (lastLog == null) {
// 从没产生过当前类型的事件历史记录
triggerEvent.setTimes(1);
} else {
triggerEvent.setTimes(lastLog.getTimes() + 1);
}
try {
activityUserJoinLogService.save(triggerEvent);
cacheActivityResultData("activity_user_join_log_id", triggerEvent.getId());
} catch (Exception e) {
// 如果是并发场景下的索引冲突,则忽略异常 TODO
logger.error("initJoin error: ", e);
throw new BusinessException(e);
}
// 强制产生一条活动用户记录
ActivityUser existActivityUser = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (existActivityUser != null) {
cacheActivityResultData("activity_user_id", existActivityUser.getId());
if (existActivityUser.getJoinTimes() <= 0) {
existActivityUser.setJoinTimes(1);
existActivityUser.setStatus(0);
}
if (existActivityUser.getStatus() == 1) {
// 上次已结束,开启新的次数
existActivityUser.setJoinTimes(existActivityUser.getJoinTimes() + 1);
existActivityUser.setStatus(0);
}
if (existActivityUser.getValidBeginTime() == null) {
existActivityUser.setValidBeginTime(activityLibrary.getStartTime());
}
if (existActivityUser.getValidEndTime() == null) {
existActivityUser.setValidEndTime(activityLibrary.getEndTime());
}
if (existActivityUser.getFirstTriggerTime() <= 0L) {
existActivityUser.setFirstTriggerTime(activityMessage.getEventTime());
}
if (existActivityUser.getLastTriggerTime() <= 0L) {
existActivityUser.setLastTriggerTime(activityMessage.getEventTime());
}
existActivityUser.setActivityType(activityInfo.getType());
activityUserService.save(existActivityUser);
} else {
ActivityUser activityUser = new ActivityUser();
activityUser.setUserId(activityMessage.getUserId());
activityUser.setActivityId(activityInfo.getActivityId());
activityUser.setActivityType(activityInfo.getType());
activityUser.setTriggerType(activityMessage.getEventType());
activityUser.setFirstTriggerTime(activityMessage.getEventTime());
activityUser.setLastTriggerTime(activityMessage.getEventTime());
activityUser.setUserType(0);
// 此处不填充,则在查询时返回的是 null
activityUser.setUserRegistTime(0L);
if (activityLibrary.getAllowJoinTimes() == 0) {
activityUser.setAllowJoinTimes(0);
} else {
activityUser.setAllowJoinTimes(activityLibrary.getAllowJoinTimes());
}
activityUser.setJoinTimes(1);
// 默认设置活动有效期,如果具体业务有不同的逻辑,需要子类修正
activityUser.setValidBeginTime(activityLibrary.getStartTime());
activityUser.setValidEndTime(activityLibrary.getEndTime());
activityUser.setStatus(0);
try {
activityUserService.save(activityUser);
cacheActivityResultData("activity_user_id", activityUser.getId());
} catch (Exception e) {
// 如果是并发场景下的索引冲突,则忽略异常 TODO
logger.error("initJoin error: ", e);
throw new BusinessException(e);
}
}
//ACTIVITY_CONTEXT.get().setJoinActivityKey(activityUser.getId());
//ACTIVITY_CONTEXT.get().setTriggerTraceKey(triggerEvent.getId());
}
/**
* 用户参加活动的相关处理包括生成ActivityUser 记录ActivityTriggerEvent 记录。
*
* @param activityMessage
* @param activityInfo
*/
public abstract ActivityMultiState join(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState checkJoinState);
/**
* 判断用户当前相关事件是否能够得到奖励以及是否已经得到了奖励;
* 注意:
* 有些活动不是每次行为都能触发奖励的,所以此处返回 false 不至于需要外层方法抛异常
*
* @param activityMessage
* @param activityInfo
* @return
*/
public ActivityMultiState checkAward(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState) {
String userId = activityMessage.getUserId();
ActivityMultiState retState = new ActivityMultiState();
// 是否能够奖励将交由具体业务来做判断,默认为 UNKNOW此处如果返回 false 则代表明确不能给与奖励
// 此处如果返回 UNKNOW 则需要业务代码做二次把关
retState.can(ThreeStateEnum.UNKNOW);
retState.has(ThreeStateEnum.UNKNOW);
if (StrUtil.isBlank(userId) || Objects.equals(userId, "0")) {
retState.setDescription("未指定当前参与活动的用户");
return retState;
}
if (StrUtil.isNotBlank(activityMessage.getActivityId())) {
if (!Objects.equals(activityMessage.getActivityId(), activityInfo.getActivityId())) {
// 指定了活动ID的情况不匹配则直接退出
retState.can(ThreeStateEnum.FALSE);
// 默认要是 UNKNOW
retState.has(ThreeStateEnum.FALSE);
return retState;
}
}
// 完善:有 ActivityUser 记录不代表已经获取过奖励,或者判断是否能够获取奖励
// // 首先,检查用户加入该活动的场次
// ActivityUser userJoinRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), userId, activityMessage.getEventType());
// if (userJoinRecord == null || userJoinRecord.getFirstTriggerTime() <= 0) {
// // 如果用户还未参加活动,则一定不能获取奖励
// retState.has(ThreeStateEnum.FALSE);
// // 不能武断,需要交给子类进一步判断
// // retState.can(ThreeStateEnum.FALSE);
// // retState.setDescription("用户未参与该活动");
// return retState;
// } else {
// retState.has(ThreeStateEnum.TRUE);
// return retState;
// }
return retState;
}
/**
* 进行奖励处理
*
* @param activityMessage
* @param activityInfo
*/
public abstract void award(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState checkAwardState);
/**
* 此处可填写当前尝试参与活动的一段处理流程之后的收尾处理逻辑,此处可放置具体业务模式下的个性化逻辑,包括但不限于:
* 1. 修改 activityUserLog 记录的 status 状态
* 2. 填充 activityUserLog 里的 extraInfo 信息(可从 ACTIVITY_RESULT_DATA_CONTEXT 上下文里提取结果数据做额外支撑)
* 3. 根据具体业务,决定是否更新 activityUser 记录的 status 状态
* 4. 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
* 5. 其他
*
* @param activityMessage
* @param activityInfo
* @param awardState
*/
public void postActivity(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState, ActivityMultiState awardState, ActivityUserResultInfo activityUserResultInfo) {
ActivityUserJoinLog lastLog = activityUserJoinLogService.lastJoinLog(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (lastLog != null) {
// 没资格参加活动的用户不会产生 ActivityUserJoinLog 记录
Date now = new Date();
lastLog.setStatus(2);
lastLog.setFinishTime(now.getTime());
if (activityUserResultInfo != null) {
if (StrUtil.isNotBlank(activityUserResultInfo.getRefId())) {
lastLog.setRefId(activityUserResultInfo.getRefId());
lastLog.setRefType(activityUserResultInfo.getRefType());
}
if (activityUserResultInfo.getExtraInfo() != null && !activityUserResultInfo.getExtraInfo().isEmpty()) {
lastLog.setExtraInfo(JsonUtils.bean2Json(activityUserResultInfo.getExtraInfo()));
}
}
activityUserJoinLogService.save(lastLog);
}
// 其他扩展,子类实现或覆盖
}
/**
* 停止活动的额外操作
*
* @param activityId
*/
public abstract void stop(String activityId);
public void cacheActivityResultData(String key, Object value) {
ACTIVITY_RESULT_DATA_CONTEXT.get().set(key, value);
}
public ValueOptional getActivityResultData(String key) {
if (StrUtil.isBlank(key)) {
return (new ValueOptional(null));
}
ValueOptional optional = ACTIVITY_RESULT_DATA_CONTEXT.get().get(key);
if (optional == null) {
return (new ValueOptional(null));
}
return optional;
}
public void setActivityUserJoinLogService(ActivityUserJoinLogService activityTriggerEventService) {
this.activityUserJoinLogService = activityTriggerEventService;
}
public void setActivityTemplateService(ActivityTemplateService activityTemplateService) {
this.activityTemplateService = activityTemplateService;
}
// public void setActivityHandleStatService(ActivityHandleStatService activityHandleStatService) {
// this.activityHandleStatService = activityHandleStatService;
// }
public void setActivityLibraryService(ActivityLibraryService activityLibraryService) {
this.activityLibraryService = activityLibraryService;
}
public void setActivityUserService(ActivityUserService activityUserService) {
this.activityUserService = activityUserService;
}
public void setActivityPrizePoolService(ActivityPrizePoolService activityPrizePoolService) {
this.activityPrizePoolService = activityPrizePoolService;
}
public void setActivityPrizeService(ActivityPrizeService activityPrizeService) {
this.activityPrizeService = activityPrizeService;
}
}

View File

@@ -0,0 +1,797 @@
package project.mall.activity.handler;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import org.apache.commons.collections.CollectionUtils;
import org.codehaus.jackson.type.TypeReference;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.ActivityInfo;
import project.mall.activity.core.ActivityMultiState;
import project.mall.activity.core.ThreeStateEnum;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.core.vo.ActivityUserResultInfo;
import project.mall.activity.dto.ActivityEditInfoDTO;
import project.mall.activity.dto.ActivityI18nContent;
import project.mall.activity.dto.ActivityPrizeDTO;
import project.mall.activity.event.message.ActivityUserLotteryMessage;
import project.mall.activity.event.message.ActivityUserRechargeMessage;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.helper.ActivityRechargeAndLotteryHelper;
import project.mall.activity.model.*;
import project.mall.activity.model.lottery.ActivityUserPoints;
import project.mall.activity.rule.FruitDialActivityConfig;
import project.mall.activity.service.ActivityUserPointsService;
import project.party.PartyService;
import project.party.model.Party;
import project.party.model.UserRecom;
import project.party.recom.UserRecomService;
import project.wallet.WalletLog;
import project.wallet.WalletLogService;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 用户首次登录奖励 10 积分活动
*/
public class FirstRechargeFruitDialActivityHandler extends ActivityHandler {
private WalletLogService walletLogService;
private UserRecomService userRecomService;
private PartyService partyService;
private ActivityRechargeAndLotteryHelper activityRechargeAndLotteryHelper;
private ActivityUserPointsService activityUserPointsService;
@Override
public ActivityTypeEnum supportActivityType() {
return ActivityTypeEnum.FRUIT_DIAL_LOTTERY;
}
@Override
public void start(String activityId) {
// do nothing
}
@Override
public ActivityMultiState checkJoin(BaseActivityMessage activityMessage, ActivityInfo activityInfo) {
// 首先继承父类通用的判断逻辑,识别能否参加该活动,以及是否已经加入该活动;
// 如果通用逻辑已经不支持参加活动了,则当前首充抽奖活动没必要继续判断,可直接表明不能加入活动
ActivityMultiState retState = super.checkJoin(activityMessage, activityInfo);
if (retState.can() == ThreeStateEnum.FALSE) {
// 不能参加活动,直接跳过
return retState;
}
if (retState.has() == ThreeStateEnum.TRUE) {
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 当前抽奖活动类型下,已经参加过首充奖励活动,直接跳过,否则会导致继续执行下面的 join 方法
return retState;
}
}
/**
* 注意:一个活动处理器可能支持多种事件,未必只支持一种事件!
*/
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
ActivityUserRechargeMessage rechargeMessage = (ActivityUserRechargeMessage)activityMessage;
retState.setCan(ThreeStateEnum.FALSE);
// 临时使用,方便调试:
//retState.setCan(ThreeStateEnum.TRUE);
// 首先,检查相对于当前的活动,当前用户的首充是否满足参加活动的要求
Date limitTime = activityInfo.getStartTime();
WalletLog firstRechargeLog = walletLogService.getFirstRechargeLogInTimeRange(activityMessage.getUserId(), limitTime);
// 测试虚拟 TODO
if (firstRechargeLog == null) {
firstRechargeLog = new WalletLog();
firstRechargeLog.setId(rechargeMessage.getTransId());
firstRechargeLog.setUsdtAmount(rechargeMessage.getUsdtAmount());
firstRechargeLog.setCreateTime(new Date());
}
FruitDialActivityConfig activityConfig = (FruitDialActivityConfig)activityInfo.getActivityConfig();
double rechargeAmountLimit = activityConfig.getFirstRechargeAmountLimit();
if (firstRechargeLog.getUsdtAmount() < rechargeAmountLimit) {
// 不满足首充资金条件,不能参加活动
//logger.info("[FirstRechargeFruitDialActivityHandler checkJoin] 用户:{} 本次充值金额为:{}, 不满足首充资金限额:{} 条件", activityMessage.getUserId(), firstRechargeLog.getUsdtAmount(), rechargeAmountLimit);
retState.setDescription("首充金额不满足参加活动的条件");
return retState;
}
// 本块逻辑取代下面的判断逻辑
ActivityUser rechargeActivityUser = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
if (rechargeActivityUser == null) {
// 尚未产生充值事件的 activityUser 记录,为兼容错过处理这种特殊场景,此处可强化判断:
// 识别当前活动奖励门槛规则的最新变更时间,判断是否需要补上奖励
if (!Objects.equals(rechargeMessage.getTransId(), firstRechargeLog.getId().toString())) {
// 满足资金条件,但是本次充值事件不是首充
// 为上次有效首充补上 activityUser 记录,并且替换当前充值事件,让它代替触发后续的奖励流程 TODO
// 此处不必创建 activityUser 记录,交由正常流程中的 initJoin 方法来处理
//rechargeActivityUser = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
// 考虑到允许管理员在活动进行期间修改参加活动的条件、奖励等配置,暂时屏蔽以下逻辑,如果放开了以下逻辑,则此处不需要 return
//rechargeMessage.setEventTime(firstRechargeLog.getCreateTime().getTime());
//rechargeMessage.setTransId(firstRechargeLog.getId().toString());
//rechargeMessage.setUsdtAmount(firstRechargeLog.getUsdtAmount());
logger.warn("[FirstRechargeFruitDialActivityHandler checkJoin] 用户:{} 本次充值金额为:{}, 当前充值事件不是首充", activityMessage.getUserId(), firstRechargeLog.getUsdtAmount());
return retState;
}
} else {
// 已经产生了首充相关的记录了,直接返回
logger.warn("[FirstRechargeFruitDialActivityHandler checkJoin] 用户:{} 本次充值金额为:{}, 满足首充资金限额:{} 条件但不是首充", activityMessage.getUserId(), firstRechargeLog.getUsdtAmount(), rechargeAmountLimit);
return retState;
}
// if (!Objects.equals(rechargeMessage.getTransId(), firstRechargeLog.getId().toString())) {
// // 满足资金条件,但是本次充值事件不是首充
// logger.warn("[FirstRechargeFruitDialActivityHandler checkJoin] 用户:{} 本次充值金额为:{}, 满足首充资金限额:{} 条件但不是首充", activityMessage.getUserId(), firstRechargeLog.getUsdtAmount(), rechargeAmountLimit);
// return retState;
// }
logger.info("[FirstRechargeFruitDialActivityHandler checkJoin] 用户:{} 本次充值金额为:{}, 满足首充抽奖活动的资金限额:{}", activityMessage.getUserId(), firstRechargeLog.getUsdtAmount(), rechargeAmountLimit);
retState.can(ThreeStateEnum.TRUE);
return retState;
} else {
// 能参与抽奖的前提是要已经产生了有效的充值记录
ActivityUser userJoinRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
if (userJoinRecord == null) {
// 如果不存在首充记录,则必定不能参加抽奖活动
retState.can(ThreeStateEnum.FALSE);
retState.setDescription("不满足抽奖资格");
return retState;
}
// 父类方法super.checkJoin 判断了用户没有超过抽奖次数限制,则直接允许抽奖
retState.can(ThreeStateEnum.TRUE);
return retState;
}
}
@Override
public ActivityMultiState join(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState) {
/**
* 当前用户加入活动的判断逻辑通过了 preJoin 的验证,允许用户参加首次登录奖励的活动,此处仅处理用户加入活动的逻辑;
* 当前业务特征比较简单,直接录入相关的活动记录即可
*/
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 当前是首充事件,初始化积分奖励将放到 award 方法里执行
ActivityUserRechargeMessage rechargeMessage = (ActivityUserRechargeMessage) activityMessage;
} else {
// 抽奖事件,奖励逻辑写到了 award 方法里
}
// 填充 activityUser 记录里的一些辅助业务字段
ActivityUser activityUser = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (activityUser.getUserRegistTime().longValue() == 0) {
// 尚未填充用户相关的辅助信息
Party party = this.partyService.getById(activityMessage.getUserId());
activityUser.setUserType(1);
activityUser.setUserRegistTime(party.getCreateTime().getTime());
activityUserService.save(activityUser);
}
return joinState;
}
@Override
public ActivityMultiState checkAward(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState) {
ActivityMultiState retState = super.checkAward(activityMessage, activityInfo, joinState);
if (retState.can() == ThreeStateEnum.FALSE) {
// 如果明确判断不允许获取奖励,则跳过奖励
return retState;
}
if (joinState.has() == ThreeStateEnum.TRUE) {
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 如果判断首充事件不是第一次发生,则不重复给与奖励
retState.setCan(ThreeStateEnum.FALSE);
retState.setHas(ThreeStateEnum.TRUE);
retState.setDescription("已领取过首充积分奖励");
return retState;
}
}
// 判断是否存在 activityUser 记录,如果不存在对应的 activityUser 记录,则该次事件不能触发奖励
ActivityUser userFirstRechargeRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
if (userFirstRechargeRecord == null) {
// 没有充值事件记录,代表当前用户没有资格获取奖励,因为当前业务特征是:首充金额满足条件才能参加活动
retState.setCan(ThreeStateEnum.FALSE);
retState.setDescription("不满足首充条件");
return retState;
}
// 过了参加活动资格判断,下面根据不同的业务场景处理不同的奖励判断
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 当前用户首充条件满足,但是本次充值未必是本次活动的第一次满足条件的首充事件,其他时间的充值事件不能给与奖励
// 只有首充才给奖励
if (joinState.getCan() == ThreeStateEnum.TRUE) {
retState.setCan(ThreeStateEnum.TRUE);
return retState;
} else {
retState.setCan(ThreeStateEnum.FALSE);
retState.setDescription("首充金额不满足参加活动的条件");
return retState;
}
} else if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.LOTTERY.getEventType())) {
// 抽奖事件,因为前面已经判断了存在有效的首充记录,再,复用 join 操作中的 joinState 状态加强判断
//
if (joinState.can() == ThreeStateEnum.FALSE) {
// 本次抽奖事件明确无资格参加,则直接不给奖励
retState.setCan(ThreeStateEnum.FALSE);
retState.setDescription(joinState.getDescription());
} else {
// 其他情况直接允许给奖励
retState.setCan(ThreeStateEnum.TRUE);
}
}
return retState;
}
@Override
public void award(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState checkAwardState) {
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 发放首充奖励,自己的积分奖励 TODO
ActivityUserRechargeMessage rechargeMessage = (ActivityUserRechargeMessage) activityMessage;
FruitDialActivityConfig activityConfig = (FruitDialActivityConfig)activityInfo.getActivityConfig();
int initActivityScore = activityConfig.getInitLotteryScore();
ActivityUserPoints userPoints = activityUserPointsService.saveOrGetUserPoints(activityInfo.getType(), activityInfo.getActivityId(), activityMessage.getUserId());
activityUserPointsService.updatePoints(userPoints.getId().toString(), initActivityScore);
} else if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.LOTTERY.getEventType())) {
// 发放抽奖奖励
ActivityUserLotteryMessage lotteryMessage = (ActivityUserLotteryMessage)activityMessage;
// 抽奖逻辑写到这里
String loginPartyId = lotteryMessage.getUserId();
// // 抽奖结果写到具体活动的相关辅助表里
// ActivityPrize defaultPrize = new ActivityPrize();
// defaultPrize.setId("0");
// defaultPrize.setStatus(1);
// defaultPrize.setPrizeNameEn("Thank you join activity");
// defaultPrize.setPrizeNameCn("谢谢惠顾");
// defaultPrize.setPrizeType(1);
// defaultPrize.setPrizeAmount(0.0);
// defaultPrize.setOdds(100.0F);
// defaultPrize.setActivityId(activityInfo.getActivityId());
List<ActivityPrize> drawedPrizeList = activityRechargeAndLotteryHelper.draw(lotteryMessage.getActivityId(),
loginPartyId, lotteryMessage.getLang(), lotteryMessage.getBatchJoinTimes(), null);
// 写成常量好维护些
cacheActivityResultData("lottery_activity_drawed_prizes", drawedPrizeList);
}
}
/**
* 此处可填写当前尝试参与活动的一段处理流程之后的收尾处理逻辑,此处可放置具体业务模式下的个性化逻辑,包括但不限于:
* 1. 修改 activityUserLog 记录的 status 状态
* 2. 填充 activityUserLog 里的 extraInfo 信息(可从 ACTIVITY_RESULT_DATA_CONTEXT 上下文里提取结果数据做额外支撑)
* 3. 根据具体业务,决定是否更新 activityUser 记录的 status 状态
* 4. 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
* 5. 其他
*
* @param activityMessage
* @param activityInfo
* @param joinState
*/
@Override
public void postActivity(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState, ActivityMultiState awardState, ActivityUserResultInfo activityUserResultInfo) {
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 提取当前活动用户的上级推荐人信息
// 充值事件才会影响上级用户
String parentPartyId = "";
UserRecom firstRecom = userRecomService.findByPartyId(activityMessage.getUserId());
if (firstRecom != null) {
Party parentParty = this.partyService.getById(firstRecom.getReco_id().toString());
if (parentParty != null) {
parentPartyId = parentParty.getId().toString();
}
}
if (StrUtil.isBlank(parentPartyId)) {
// 没有上级用户,则无需处理给参加活动的上级用户充积分的奖励
} else {
// 有上级用户,需要判断上级用户是否已经参加了活动,如果上级用户未参加活动,则错过本次奖励
ActivityUser parentUserRechargeRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), parentPartyId, ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType());
if (parentUserRechargeRecord == null) {
// 上级用户未参加首充抽奖活动,则会错过本奖励
} else {
// 判断当前用户的充值是否满足给上级用户增加积分奖励的条件
ActivityUserRechargeMessage rechargeMessage = (ActivityUserRechargeMessage)activityMessage;
double rechargeAmount = rechargeMessage.getUsdtAmount();
FruitDialActivityConfig activityConfig = (FruitDialActivityConfig)activityInfo.getActivityConfig();
double rechargeAmountLimit = activityConfig.getFirstRechargeAmountLimit();
if (rechargeAmount >= rechargeAmountLimit) {
// 当前用户充值金额满足临界值,则可以给上级用户增加积分
logger.info("[FirstRechargeFruitDialActivityHandler postActivity] 用户:{} 本次充值金额为:{}, 满足给上级用户增加积分的资金限额:{} 条件", activityMessage.getUserId(), rechargeAmount, rechargeAmountLimit);
int inviteAwardScore = activityConfig.getInviteAwardScore();
// 给上级用户奖励活动积分
ActivityUserPoints userPoints = activityUserPointsService.saveOrGetUserPoints(activityInfo.getType(), activityInfo.getActivityId(), parentPartyId);
activityUserPointsService.updatePoints(userPoints.getId().toString(), inviteAwardScore);
}
}
}
}
ActivityUserJoinLog lastLog = activityUserJoinLogService.lastJoinLog(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (lastLog != null) {
// 没资格参加活动的用户不会产生 ActivityUserJoinLog 记录
lastLog.setStatus(2);
lastLog.setFinishTime(System.currentTimeMillis());
if (activityMessage.getEventType().equalsIgnoreCase(ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType())) {
// 充值
ActivityUserRechargeMessage rechargeMessage = (ActivityUserRechargeMessage)activityMessage;
lastLog.setRefType(1);
lastLog.setRefId(rechargeMessage.getTransId());
} else {
// 抽奖事件才有的返回值
List<ActivityPrize> lotteryPrizeList = (List<ActivityPrize>) getActivityResultData("lottery_activity_drawed_prizes").getValue();
Map<String, Object> logExtraInfo = new HashMap<>();
if (CollectionUtil.isNotEmpty(lotteryPrizeList)) {
StringBuffer prizeIdBuf = new StringBuffer();
for (ActivityPrize onePrize : lotteryPrizeList) {
prizeIdBuf.append(onePrize.getId().toString()).append(",");
}
prizeIdBuf.deleteCharAt(prizeIdBuf.length() - 1);
logExtraInfo.put("prizeIds", prizeIdBuf.toString());
}
lastLog.setExtraInfo(JsonUtils.bean2Json(logExtraInfo));
}
activityUserJoinLogService.save(lastLog);
}
// 更新 activityUser 字段
ActivityUser currentActivityUserRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (currentActivityUserRecord != null) {
// 无论是充值事件,还是抽奖事件,都是一次性耗时短的事件,每次事件完成都可以更新状态
currentActivityUserRecord.setStatus(1);
currentActivityUserRecord.setLastTriggerTime(System.currentTimeMillis());
activityUserService.save(currentActivityUserRecord);
}
}
@Override
public void stop(String activityId) {
}
@Override
public ActivityLibrary saveActivity(ActivityEditInfoDTO activityInfo) {
ActivityLibrary existActivityLibrary = null;
//String activityCode = activityInfo.getActivityCode();
if (StrUtil.isNotBlank(activityInfo.getId()) && !Objects.equals(activityInfo.getId(), "0")) {
existActivityLibrary = activityLibraryService.findById(activityInfo.getId());
if (existActivityLibrary == null) {
throw new BusinessException("不存在的活动");
}
//activityCode = existActivityLibrary.getActivityCode();
}
if (StrUtil.isBlank(activityInfo.getTemplateId())) {
throw new BusinessException("未指定活动类型");
}
ActivityTemplate activityTemplate = activityTemplateService.getById(activityInfo.getTemplateId());
if (activityTemplate == null) {
throw new BusinessException("该类型的活动已下线");
}
boolean isNewActivity = false;
if (existActivityLibrary == null) {
isNewActivity = true;
existActivityLibrary = new ActivityLibrary();
existActivityLibrary.setTemplateId(activityTemplate.getId().toString());
existActivityLibrary.setType(activityTemplate.getType());
existActivityLibrary.setLocation(99999);
existActivityLibrary.setIsShow(0);
}
//existActivityLibrary.setActivityCode(activityCode);
existActivityLibrary.setLastOperator(activityInfo.getCreateBy());
existActivityLibrary.setDeleted(0);
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> activityTemplateConfigInfoList = (List<ActivityParam>)JsonUtils.readValue(activityTemplate.getActivityConfig(), paramType);
Map<String, ActivityParam> activityConfigAttrMap = new HashMap<>();
for (ActivityParam oneConfig : activityTemplateConfigInfoList) {
activityConfigAttrMap.put(oneConfig.getCode(), oneConfig);
}
//ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityTemplate.getType());
List<ActivityParam> lotteryConfigList = activityInfo.getActivityConfigInfos();
if (CollectionUtil.isNotEmpty(lotteryConfigList)) {
for (ActivityParam oneParam : lotteryConfigList) {
ActivityParam templateConfig = activityConfigAttrMap.get(oneParam.getCode());
if (templateConfig == null) {
logger.error("当前提交的活动配置参数:{} 在活动模板中不存在", oneParam.getCode());
continue;
}
oneParam.setTitle(templateConfig.getTitle());
oneParam.setDescription(templateConfig.getDescription());
}
}
existActivityLibrary.setActivityConfig(JsonUtils.bean2Json(lotteryConfigList));
existActivityLibrary.setStatus(activityInfo.getStatus());
// 程序生成 detail 规则? TODO
//existActivityLibrary.setDetailUrl();
existActivityLibrary.setImageUrl(activityInfo.getImageUrl());
existActivityLibrary.setAllowJoinTimes(0);
existActivityLibrary.setStartTime(DateUtil.parseDateTime(activityInfo.getStartTime()));
existActivityLibrary.setEndTime(DateUtil.parseDateTime(activityInfo.getEndTime()));
List<ActivityI18nContent> i18nContents = activityInfo.getI18nContents();
if (CollectionUtil.isNotEmpty(i18nContents)) {
for (ActivityI18nContent oneLangContent : i18nContents) {
if (oneLangContent.getLang().equalsIgnoreCase("cn")) {
existActivityLibrary.setTitleCn(oneLangContent.getTitle());
existActivityLibrary.setDescriptionCn(oneLangContent.getDescription());
} else if (oneLangContent.getLang().equalsIgnoreCase("en")) {
existActivityLibrary.setTitleEn(oneLangContent.getTitle());
existActivityLibrary.setDescriptionEn(oneLangContent.getDescription());
}
}
}
activityLibraryService.saveActivity(existActivityLibrary);
if (isNewActivity) {
// 设置活动详情地址
activityLibraryService.updateLotteryActivityUrl(existActivityLibrary.getId().toString());
}
// 提取所有的配置的奖品记录(包括废弃了的历史记录)
List<ActivityPrize> oriPrizeList = activityPrizeService.listByActivityId(existActivityLibrary.getId().toString(), -1);
Map<String, ActivityPrize> oriPrizeMap = oriPrizeList.stream()
.collect(Collectors.toMap(entity -> entity.getPoolId(), Function.identity(), (key1, key2) -> key2));
// 本地奖品变更,旧奖品保持下来的 id 集合
Map<String, String> keepedPrizeIdMap = new HashMap();
List<String> prizePoolIdList = null;
List<ActivityPrizeDTO> prizeList = activityInfo.getPrizeList();
if (CollectionUtil.isNotEmpty(prizeList)) {
prizePoolIdList = prizeList.stream().map(prize -> prize.getPoolId()).collect(Collectors.toList());
}
List<ActivityPrizePool> prizePoolEntityList = null;
if (CollectionUtil.isNotEmpty(prizePoolIdList)) {
prizePoolEntityList = activityPrizePoolService.listByIds(prizePoolIdList);
}
Map<String, ActivityPrizePool> prizePoolMap = new HashMap();
if (CollectionUtil.isNotEmpty(prizePoolEntityList)) {
for (ActivityPrizePool onePrizePool : prizePoolEntityList) {
prizePoolMap.put(onePrizePool.getId().toString(), onePrizePool);
}
}
// 如果提交的 prizeList 集合为空,则代表历史奖品记录都要废弃
if (CollectionUtils.isNotEmpty(activityInfo.getPrizeList())) {
// double totalOdds = 0.0;
for (ActivityPrizeDTO onePrizeDto : activityInfo.getPrizeList()) {
// if (onePrizeDto.getOdds() < 0) {
// throw new BusinessException("中奖几率不能小于 0");
// }
// totalOdds = totalOdds + onePrizeDto.getOdds();
// if (totalOdds > 1.0) {
// throw new BusinessException("累计中奖几率不能大于 1");
// }
if (StrUtil.isBlank(onePrizeDto.getPoolId()) || Objects.equals(onePrizeDto.getPoolId(), "0")) {
// 防御处理,如果提交了类似:谢谢惠顾 这种记录不存在的奖品,则跳过
continue;
}
ActivityPrizePool prizePool = prizePoolMap.get(onePrizeDto.getPoolId());
if (prizePool == null || prizePool.getStatus() != 1) {
// 考虑到创建活动后会复制一份奖品池的记录,允许活动奖品脱离奖品池中的原始记录(奖品池记录可能会被删除),此处允许活动奖品在
// 奖品池中找不到对应的记录
ActivityPrize oldActivityPrize = oriPrizeMap.get(onePrizeDto.getPoolId());
if (oldActivityPrize == null || oldActivityPrize.getStatus() != 1) {
// 不存在有效的旧记录
throw new BusinessException("奖品不存在");
}
}
ActivityPrize onePrizeEntity = null;
ActivityPrize oriPrizeEntity = oriPrizeMap.get(onePrizeDto.getPoolId());
if (oriPrizeEntity == null) {
// 新增的奖品
// 基于前面的校验过滤逻辑,此处必定存在 prizePool 记录
onePrizeEntity = new ActivityPrize();
onePrizeEntity.setActivityId(existActivityLibrary.getId().toString());
// 严谨做法:需要判断奖品池记录是否存在
onePrizeEntity.setPoolId(onePrizeDto.getPoolId());
onePrizeEntity.setStatus(1);
onePrizeEntity.setImage(prizePool.getImage());
onePrizeEntity.setMaxQuantity(onePrizeDto.getMaxQuantity());
onePrizeEntity.setLeftQuantity(onePrizeDto.getMaxQuantity());
onePrizeEntity.setOdds(onePrizeDto.getOdds()); // 不能为负值
onePrizeEntity.setPrizeAmount(prizePool.getPrizeAmount());
onePrizeEntity.setPrizeNameCn(prizePool.getPrizeNameCn());
onePrizeEntity.setPrizeNameEn(prizePool.getPrizeNameEn());
onePrizeEntity.setPrizeType(prizePool.getPrizeType());
onePrizeEntity.setRemark(prizePool.getRemark());
onePrizeEntity.setCreateBy(activityInfo.getCreateBy());
onePrizeEntity.setDefaultPrize(0);
} else {
// 保持的旧奖品记录,至于要不要用对应的奖品池记录的最新奖品值刷新活动奖品属性,看业务,暂时是奖品最新属性值随着奖品池记录的最新配置刷新
keepedPrizeIdMap.put(oriPrizeEntity.getPoolId(), oriPrizeEntity.getId().toString());
onePrizeEntity = oriPrizeEntity;
onePrizeEntity.setStatus(1);
if (prizePool != null) {
onePrizeEntity.setImage(prizePool.getImage());
onePrizeEntity.setPrizeNameCn(prizePool.getPrizeNameCn());
onePrizeEntity.setPrizeNameEn(prizePool.getPrizeNameEn());
onePrizeEntity.setPrizeType(prizePool.getPrizeType());
onePrizeEntity.setRemark(prizePool.getRemark());
onePrizeEntity.setPrizeAmount(prizePool.getPrizeAmount());
} else {
// 根据 oriPrizeEntity 来填充
}
onePrizeEntity.setOdds(onePrizeDto.getOdds()); // 不能为负值
onePrizeEntity.setCreateBy(activityInfo.getCreateBy());
}
activityPrizeService.save(onePrizeEntity);
}
}
// 清理被替换掉的奖品
for (String oneOldPoolId : oriPrizeMap.keySet()) {
if (keepedPrizeIdMap.containsKey(oneOldPoolId)) {
// 该奖品被保持了
} else {
// 该奖品被清除了,修改状态
ActivityPrize invalidPrize = oriPrizeMap.get(oneOldPoolId);
invalidPrize.setStatus(0);
activityPrizeService.save(invalidPrize);
}
}
return existActivityLibrary;
}
/**
* 对管理员展示活动信息
*
* @param activityId
* @return
*/
@Override
public ActivityEditInfoDTO getActivityDetail(String activityId, String lang) {
if (StrUtil.isBlank(activityId) || Objects.equals(activityId, "0")) {
throw new BusinessException("未指定活动");
}
if (StrUtil.isBlank(lang)) {
lang = "cn";
}
ActivityLibrary existActivityLibrary = activityLibraryService.findById(activityId);
if (existActivityLibrary == null) {
throw new BusinessException("不存在的活动");
}
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(existActivityLibrary.getType());
String lotteryConfigJson = existActivityLibrary.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> lotteryConfigList = (List<ActivityParam>)JsonUtils.readValue(lotteryConfigJson, paramType);
ActivityEditInfoDTO retDto = new ActivityEditInfoDTO();
retDto.setId(existActivityLibrary.getId().toString());
retDto.setType(existActivityLibrary.getType());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setStatus(existActivityLibrary.getStatus());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setDetailUrl(existActivityLibrary.getDetailUrl());
retDto.setImageUrl(existActivityLibrary.getImageUrl());
retDto.setStartTime(DateUtil.formatDateTime(existActivityLibrary.getStartTime()));
retDto.setEndTime(DateUtil.formatDateTime(existActivityLibrary.getEndTime()));
retDto.setTemplateId(existActivityLibrary.getTemplateId());
//retDto.setActivityCode(existActivityLibrary.getActivityCode());
List<ActivityI18nContent> i18nContents = new ArrayList();
retDto.setI18nContents(i18nContents);
ActivityI18nContent i18nContent1 = new ActivityI18nContent();
ActivityI18nContent i18nContent2 = new ActivityI18nContent();
i18nContent1.setLang("cn");
i18nContent1.setTitle(existActivityLibrary.getTitleCn());
i18nContent1.setDescription(existActivityLibrary.getDescriptionCn());
i18nContent2.setLang("en");
i18nContent2.setTitle(existActivityLibrary.getTitleEn());
i18nContent2.setDescription(existActivityLibrary.getDescriptionEn());
i18nContents.add(i18nContent1);
i18nContents.add(i18nContent2);
List<ActivityPrize> activityPrizeList = activityPrizeService.listByActivityId(existActivityLibrary.getId().toString(), 1);
if (CollectionUtils.isNotEmpty(activityPrizeList)) {
retDto.setPrizeList(new ArrayList<>());
// // 增加一个谢谢惠顾虚拟奖品记录
// ActivityPrizeDTO mockPrizeDto = new ActivityPrizeDTO();
// retDto.getPrizeList().add(mockPrizeDto);
// mockPrizeDto.setOdds(0.0F);
// mockPrizeDto.setId("0");
// mockPrizeDto.setDefaultPrize(1);
// mockPrizeDto.setImage("");
// mockPrizeDto.setMaxQuantity(0);
// mockPrizeDto.setPoolId("0");
// mockPrizeDto.setPrizeType(2);
// mockPrizeDto.setPrizeAmount(0.0);
// if (lang.equals("cn")) {
// mockPrizeDto.setPrizeName("谢谢惠顾");
// } else {
// mockPrizeDto.setPrizeName("Thanks");
// }
for (ActivityPrize onePrizeEntity : activityPrizeList) {
ActivityPrizeDTO onePrizeDto = new ActivityPrizeDTO();
onePrizeDto.setId(onePrizeEntity.getId().toString());
// 严谨做法:需要判断奖品池记录是否存在
onePrizeDto.setPoolId(onePrizeEntity.getPoolId());
onePrizeDto.setMaxQuantity(onePrizeEntity.getMaxQuantity());
onePrizeDto.setPrizeAmount(onePrizeEntity.getPrizeAmount());
onePrizeDto.setPrizeName(onePrizeEntity.getPrizeNameCn());
onePrizeDto.setDefaultPrize(onePrizeEntity.getDefaultPrize());
onePrizeDto.setPrizeType(onePrizeEntity.getPrizeType());
onePrizeDto.setImage(onePrizeEntity.getImage());
onePrizeDto.setOdds(onePrizeEntity.getOdds());
retDto.getPrizeList().add(onePrizeDto);
}
}
return retDto;
}
@Override
public ActivityEditInfoDTO loadActivityTemplate(String templateId) {
if (StrUtil.isBlank(templateId) || Objects.equals(templateId, "0")) {
throw new BusinessException("未指定活动");
}
ActivityTemplate existActivityTemplate = activityTemplateService.getById(templateId);
if (existActivityTemplate == null) {
throw new BusinessException("不存在的活动");
}
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(existActivityTemplate.getType());
String lotteryConfigJson = existActivityTemplate.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> lotteryConfigList = (List<ActivityParam>)JsonUtils.readValue(lotteryConfigJson, paramType);
ActivityEditInfoDTO retDto = new ActivityEditInfoDTO();
retDto.setId("");
retDto.setType(existActivityTemplate.getType());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setTemplateId(existActivityTemplate.getId().toString());
retDto.setStatus(existActivityTemplate.getStatus());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setImageUrl("");
retDto.setTitle("");
ActivityPrizePool defaultPrize = null;
List<ActivityPrizePool> defaultPrizeList = activityPrizePoolService.listDefaltPrize(1, 1);
if (CollectionUtil.isNotEmpty(defaultPrizeList)) {
defaultPrize = defaultPrizeList.get(0);
}
int prizeCount = 9;
if (defaultPrize != null) {
prizeCount = 8;
}
List<ActivityPrizePool> prizeList0 = activityPrizePoolService.listLotteryPrize(prizeCount);
List<ActivityPrizePool> prizeList = new ArrayList<>();
if (defaultPrize != null) {
prizeList.add(defaultPrize);
}
prizeList.addAll(prizeList0);
if (CollectionUtils.isNotEmpty(prizeList)) {
retDto.setPrizeList(new ArrayList<>());
for (ActivityPrizePool prizeObj : prizeList) {
ActivityPrizeDTO onePrizeDto = new ActivityPrizeDTO();
onePrizeDto.setId("");
// 严谨做法:需要判断奖品池记录是否存在
onePrizeDto.setPoolId(prizeObj.getId().toString());
onePrizeDto.setMaxQuantity(0);
onePrizeDto.setOdds(BigDecimal.ZERO); // 不能为负值
onePrizeDto.setPrizeAmount(prizeObj.getPrizeAmount());
onePrizeDto.setPrizeName(StrUtil.isBlank(prizeObj.getPrizeNameCn()) ? prizeObj.getPrizeNameEn() : prizeObj.getPrizeNameCn());
onePrizeDto.setPrizeType(prizeObj.getPrizeType());
retDto.getPrizeList().add(onePrizeDto);
}
}
return retDto;
}
/**
* 当前活动内置支持的事件枚举
*
*/
public static enum ActivityTouchEventTypeEnum {
USER_RECHARGE("user_recharge", ActivityUserRechargeMessage.class, "用户充值"),
LOTTERY("lottery", ActivityUserLotteryMessage.class, "抽奖"),
;
private String eventType;
private Class<? extends BaseActivityMessage> eventInfoClazz;
private String description;
private ActivityTouchEventTypeEnum(String eventType, Class<? extends BaseActivityMessage> eventInfoClazz, String description) {
this.eventType = eventType;
this.eventInfoClazz = eventInfoClazz;
this.description = description;
}
public static ActivityTouchEventTypeEnum typeOf(String eventType) {
if (StrUtil.isBlank(eventType)) {
return null;
}
ActivityTouchEventTypeEnum values[] = ActivityTouchEventTypeEnum.values();
for (ActivityTouchEventTypeEnum one : values) {
if (one.getEventType().equalsIgnoreCase(eventType)) {
return one;
}
}
return null;
}
public String getEventType() {
return eventType;
}
public Class<? extends BaseActivityMessage> getEventInfoClazz() {
return eventInfoClazz;
}
public String getDescription() {
return description;
}
}
public void setWalletLogService(WalletLogService walletLogService) {
this.walletLogService = walletLogService;
}
public void setUserRecomService(UserRecomService userRecomService) {
this.userRecomService = userRecomService;
}
public void setPartyService(PartyService partyService) {
this.partyService = partyService;
}
public void setActivityRechargeAndLotteryHelper(ActivityRechargeAndLotteryHelper activityLotteryHelper) {
this.activityRechargeAndLotteryHelper = activityLotteryHelper;
}
public void setActivityUserPointsService(ActivityUserPointsService activityUserPointsService) {
this.activityUserPointsService = activityUserPointsService;
}
}

View File

@@ -0,0 +1,617 @@
package project.mall.activity.handler;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import org.apache.commons.collections.CollectionUtils;
import org.codehaus.jackson.type.TypeReference;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.ActivityInfo;
import project.mall.activity.core.ActivityMultiState;
import project.mall.activity.core.ThreeStateEnum;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.core.vo.ActivityUserResultInfo;
import project.mall.activity.dto.ActivityEditInfoDTO;
import project.mall.activity.dto.ActivityI18nContent;
import project.mall.activity.dto.ActivityPrizeDTO;
import project.mall.activity.event.message.ActivityUserLotteryMessage;
import project.mall.activity.event.message.BaseActivityMessage;
import project.mall.activity.helper.ActivityRechargeAndLotteryHelper;
import project.mall.activity.helper.ActivitySimpleLotteryHelper;
import project.mall.activity.model.*;
import project.mall.activity.service.ActivityUserPointsService;
import project.party.PartyService;
import project.party.recom.UserRecomService;
import project.wallet.WalletLogService;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 普通的抽奖活动,该活动为全局单例活动,长期有效,仅仅有一条活动记录
*
*/
public class SimpleLotteryActivityHandler extends ActivityHandler {
private WalletLogService walletLogService;
private UserRecomService userRecomService;
private PartyService partyService;
private ActivitySimpleLotteryHelper activitySimpleLotteryHelper;
private ActivityUserPointsService activityUserPointsService;
@Override
public ActivityTypeEnum supportActivityType() {
return ActivityTypeEnum.SIMPLE_LOTTERY;
}
@Override
public void start(String activityId) {
// do nothing
}
/**
* 判断当前事件下的用户是否能参加该活动
*
* @param activityMessage
* @param activityInfo
* @return
*/
@Override
public ActivityMultiState checkJoin(BaseActivityMessage activityMessage, ActivityInfo activityInfo) {
// 首先继承父类通用的判断逻辑,识别能否参加该活动,以及是否已经加入该活动;
// 如果通用逻辑已经不支持参加活动了,则当前首充抽奖活动没必要继续判断,可直接表明不能加入活动
ActivityMultiState retState = super.checkJoin(activityMessage, activityInfo);
if (retState.can() == ThreeStateEnum.FALSE) {
// 不能参加活动,直接跳过
return retState;
}
if (!activityMessage.getEventType().equals(ActivityTouchEventTypeEnum.LOTTERY.getEventType())) {
retState.can(ThreeStateEnum.FALSE);
return retState;
}
/**
* 只要在活动期间,都允许抽奖,没有其他抽奖资格限制
*/
// 父类方法super.checkJoin 判断了用户没有超过抽奖次数限制,则直接允许抽奖
retState.can(ThreeStateEnum.TRUE);
return retState;
}
@Override
public ActivityMultiState join(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState) {
// initJoin 方法里已经产生了一条 activityUser 记录,此处根据具体业务看看是否还需要补充该记录的某些字段值
ActivityUser activityUser = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
return joinState;
}
@Override
public ActivityMultiState checkAward(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState) {
ActivityMultiState retState = super.checkAward(activityMessage, activityInfo, joinState);
if (retState.can() == ThreeStateEnum.FALSE) {
// 如果明确判断不允许获取奖励,则跳过奖励
return retState;
}
// 能够参与抽奖的,都允许立即计算奖品(转盘奖品)
// 抽奖事件,因为前面已经判断了当前用户是否允许参与抽奖,复用 join 操作中的 joinState 状态加强判断
if (joinState.can() == ThreeStateEnum.FALSE) {
// 本次抽奖事件明确无资格参加,则直接不给奖励
retState.setCan(ThreeStateEnum.FALSE);
retState.setDescription(joinState.getDescription());
} else {
// 其他情况直接允许给奖励
retState.setCan(ThreeStateEnum.TRUE);
}
return retState;
}
@Override
public void award(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState checkAwardState) {
// 发放抽奖奖励
ActivityUserLotteryMessage lotteryMessage = (ActivityUserLotteryMessage)activityMessage;
// 抽奖逻辑写到这里
String loginPartyId = lotteryMessage.getUserId();
List<ActivityPrize> drawedPrizeList = activitySimpleLotteryHelper.draw(lotteryMessage.getActivityId(),
loginPartyId, lotteryMessage.getLang(), lotteryMessage.getBatchJoinTimes(), null);
// 写成常量好维护些
cacheActivityResultData("lottery_activity_drawed_prizes", drawedPrizeList);
}
/**
* 此处可填写当前尝试参与活动的一段处理流程之后的收尾处理逻辑,此处可放置具体业务模式下的个性化逻辑,包括但不限于:
* 1. 修改 activityUserLog 记录的 status 状态
* 2. 填充 activityUserLog 里的 extraInfo 信息(可从 ACTIVITY_RESULT_DATA_CONTEXT 上下文里提取结果数据做额外支撑)
* 3. 根据具体业务,决定是否更新 activityUser 记录的 status 状态
* 4. 有些活动,用户没资格参与活动,或者没资格获取奖励,但是可能会对其他有资格的用户产生影响,这些逻辑也可以写到本方法里
* 5. 其他
*
* @param activityMessage
* @param activityInfo
* @param joinState
*/
@Override
public void postActivity(BaseActivityMessage activityMessage, ActivityInfo activityInfo, ActivityMultiState joinState, ActivityMultiState awardState, ActivityUserResultInfo activityUserResultInfo) {
ActivityUserJoinLog lastLog = activityUserJoinLogService.lastJoinLog(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (lastLog != null) {
// 没资格参加活动的用户不会产生 ActivityUserJoinLog 记录
lastLog.setStatus(2);
lastLog.setFinishTime(System.currentTimeMillis());
// 抽奖事件才有的返回值
List<ActivityPrize> lotteryPrizeList = (List<ActivityPrize>) getActivityResultData("lottery_activity_drawed_prizes").getValue();
Map<String, Object> logExtraInfo = new HashMap<>();
if (CollectionUtil.isNotEmpty(lotteryPrizeList)) {
StringBuffer prizeIdBuf = new StringBuffer();
for (ActivityPrize onePrize : lotteryPrizeList) {
prizeIdBuf.append(onePrize.getId().toString()).append(",");
}
prizeIdBuf.deleteCharAt(prizeIdBuf.length() - 1);
logExtraInfo.put("prizeIds", prizeIdBuf.toString());
}
lastLog.setExtraInfo(JsonUtils.bean2Json(logExtraInfo));
activityUserJoinLogService.save(lastLog);
}
// 更新 activityUser 字段
ActivityUser currentActivityUserRecord = activityUserService.getActivityUser(activityInfo.getActivityId(), activityMessage.getUserId(), activityMessage.getEventType());
if (currentActivityUserRecord != null) {
// 无论是充值事件,还是抽奖事件,都是一次性耗时短的事件,每次事件完成都可以更新状态
currentActivityUserRecord.setStatus(1);
currentActivityUserRecord.setLastTriggerTime(System.currentTimeMillis());
activityUserService.save(currentActivityUserRecord);
}
}
@Override
public void stop(String activityId) {
}
@Override
public ActivityLibrary saveActivity(ActivityEditInfoDTO activityInfo) {
if (StrUtil.isBlank(activityInfo.getTemplateId())) {
throw new BusinessException("未指定活动类型");
}
ActivityLibrary existActivityLibrary = null;
//String activityCode = activityInfo.getActivityCode();
if (StrUtil.isNotBlank(activityInfo.getId()) && !Objects.equals(activityInfo.getId(), "0")) {
existActivityLibrary = activityLibraryService.findById(activityInfo.getId());
if (existActivityLibrary == null) {
throw new BusinessException("不存在的活动");
}
} else {
// existActivityLibrary = activityLibraryService.findByTemplate(activityInfo.getTemplateId());
// if (existActivityLibrary != null && existActivityLibrary.getDeleted() == 0) {
// throw new BusinessException("该类型活动只能单例存在");
// }
}
ActivityTemplate activityTemplate = activityTemplateService.getById(activityInfo.getTemplateId());
if (activityTemplate == null) {
throw new BusinessException("该类型的活动已下线");
}
boolean isNewActivity = false;
if (existActivityLibrary == null) {
isNewActivity = true;
existActivityLibrary = new ActivityLibrary();
existActivityLibrary.setTemplateId(activityTemplate.getId().toString());
existActivityLibrary.setType(activityTemplate.getType());
existActivityLibrary.setLocation(99999);
existActivityLibrary.setIsShow(0);
}
existActivityLibrary.setLastOperator(activityInfo.getCreateBy());
existActivityLibrary.setDeleted(0);
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> activityTemplateConfigInfoList = (List<ActivityParam>)JsonUtils.readValue(activityTemplate.getActivityConfig(), paramType);
Map<String, ActivityParam> activityConfigAttrMap = new HashMap<>();
for (ActivityParam oneConfig : activityTemplateConfigInfoList) {
activityConfigAttrMap.put(oneConfig.getCode(), oneConfig);
}
//ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityTemplate.getType());
List<ActivityParam> lotteryConfigList = activityInfo.getActivityConfigInfos();
if (CollectionUtil.isNotEmpty(lotteryConfigList)) {
for (ActivityParam oneParam : lotteryConfigList) {
ActivityParam templateConfig = activityConfigAttrMap.get(oneParam.getCode());
if (templateConfig == null) {
logger.error("当前提交的活动配置参数:{} 在活动模板中不存在", oneParam.getCode());
continue;
}
oneParam.setTitle(templateConfig.getTitle());
oneParam.setDescription(templateConfig.getDescription());
}
}
existActivityLibrary.setActivityConfig(JsonUtils.bean2Json(lotteryConfigList));
existActivityLibrary.setStatus(activityInfo.getStatus());
// 程序生成 detail 规则? TODO
//existActivityLibrary.setDetailUrl();
existActivityLibrary.setImageUrl(activityInfo.getImageUrl());
existActivityLibrary.setAllowJoinTimes(0);
existActivityLibrary.setStartTime(DateUtil.parseDateTime(activityInfo.getStartTime()));
existActivityLibrary.setEndTime(DateUtil.parseDateTime(activityInfo.getEndTime()));
List<ActivityI18nContent> i18nContents = activityInfo.getI18nContents();
if (CollectionUtil.isNotEmpty(i18nContents)) {
for (ActivityI18nContent oneLangContent : i18nContents) {
if (oneLangContent.getLang().equalsIgnoreCase("cn")) {
existActivityLibrary.setTitleCn(oneLangContent.getTitle());
existActivityLibrary.setDescriptionCn(oneLangContent.getDescription());
} else if (oneLangContent.getLang().equalsIgnoreCase("en")) {
existActivityLibrary.setTitleEn(oneLangContent.getTitle());
existActivityLibrary.setDescriptionEn(oneLangContent.getDescription());
}
}
}
activityLibraryService.saveActivity(existActivityLibrary);
if (isNewActivity) {
// 设置活动详情地址
activityLibraryService.updateLotteryActivityUrl(existActivityLibrary.getId().toString());
}
// 提取所有的配置的奖品记录(包括废弃了的历史记录)
List<ActivityPrize> oriPrizeList = activityPrizeService.listByActivityId(existActivityLibrary.getId().toString(), -1);
Map<String, ActivityPrize> oriPrizeMap = oriPrizeList.stream()
.collect(Collectors.toMap(entity -> entity.getPoolId(), Function.identity(), (key1, key2) -> key2));
// 本地奖品变更,旧奖品保持下来的 id 集合
Map<String, String> keepedPrizeIdMap = new HashMap();
List<String> prizePoolIdList = null;
List<ActivityPrizeDTO> prizeList = activityInfo.getPrizeList();
if (CollectionUtil.isNotEmpty(prizeList)) {
prizePoolIdList = prizeList.stream().map(prize -> prize.getPoolId()).collect(Collectors.toList());
}
List<ActivityPrizePool> prizePoolEntityList = null;
if (CollectionUtil.isNotEmpty(prizePoolIdList)) {
prizePoolEntityList = activityPrizePoolService.listByIds(prizePoolIdList);
}
Map<String, ActivityPrizePool> prizePoolMap = new HashMap();
if (CollectionUtil.isNotEmpty(prizePoolEntityList)) {
for (ActivityPrizePool onePrizePool : prizePoolEntityList) {
prizePoolMap.put(onePrizePool.getId().toString(), onePrizePool);
}
}
// 如果提交的 prizeList 集合为空,则代表历史奖品记录都要废弃
if (CollectionUtils.isNotEmpty(activityInfo.getPrizeList())) {
// double totalOdds = 0.0;
for (ActivityPrizeDTO onePrizeDto : activityInfo.getPrizeList()) {
// if (onePrizeDto.getOdds() < 0) {
// throw new BusinessException("中奖几率不能小于 0");
// }
// totalOdds = totalOdds + onePrizeDto.getOdds();
// if (totalOdds > 1.0) {
// throw new BusinessException("累计中奖几率不能大于 1");
// }
if (StrUtil.isBlank(onePrizeDto.getPoolId()) || Objects.equals(onePrizeDto.getPoolId(), "0")) {
// 防御处理,如果提交了类似:谢谢惠顾 这种记录不存在的奖品,则跳过
continue;
}
ActivityPrizePool prizePool = prizePoolMap.get(onePrizeDto.getPoolId());
if (prizePool == null || prizePool.getStatus() != 1) {
// 考虑到创建活动后会复制一份奖品池的记录,允许活动奖品脱离奖品池中的原始记录(奖品池记录可能会被删除),此处允许活动奖品在
// 奖品池中找不到对应的记录
ActivityPrize oldActivityPrize = oriPrizeMap.get(onePrizeDto.getPoolId());
if (oldActivityPrize == null || oldActivityPrize.getStatus() != 1) {
// 不存在有效的旧记录
throw new BusinessException("奖品不存在");
}
}
ActivityPrize onePrizeEntity = null;
ActivityPrize oriPrizeEntity = oriPrizeMap.get(onePrizeDto.getPoolId());
if (oriPrizeEntity == null) {
// 新增的奖品
// 基于前面的校验过滤逻辑,此处必定存在 prizePool 记录
onePrizeEntity = new ActivityPrize();
onePrizeEntity.setActivityId(existActivityLibrary.getId().toString());
// 严谨做法:需要判断奖品池记录是否存在
onePrizeEntity.setPoolId(onePrizeDto.getPoolId());
onePrizeEntity.setStatus(1);
onePrizeEntity.setImage(prizePool.getImage());
onePrizeEntity.setMaxQuantity(onePrizeDto.getMaxQuantity());
onePrizeEntity.setLeftQuantity(onePrizeDto.getMaxQuantity());
onePrizeEntity.setOdds(onePrizeDto.getOdds()); // 不能为负值
onePrizeEntity.setPrizeAmount(prizePool.getPrizeAmount());
onePrizeEntity.setPrizeNameCn(prizePool.getPrizeNameCn());
onePrizeEntity.setPrizeNameEn(prizePool.getPrizeNameEn());
onePrizeEntity.setPrizeType(prizePool.getPrizeType());
onePrizeEntity.setRemark(prizePool.getRemark());
onePrizeEntity.setCreateBy(activityInfo.getCreateBy());
onePrizeEntity.setDefaultPrize(0);
} else {
// 保持的旧奖品记录,至于要不要用对应的奖品池记录的最新奖品值刷新活动奖品属性,看业务,暂时是奖品最新属性值随着奖品池记录的最新配置刷新
keepedPrizeIdMap.put(oriPrizeEntity.getPoolId(), oriPrizeEntity.getId().toString());
onePrizeEntity = oriPrizeEntity;
onePrizeEntity.setStatus(1);
if (prizePool != null) {
onePrizeEntity.setImage(prizePool.getImage());
onePrizeEntity.setPrizeNameCn(prizePool.getPrizeNameCn());
onePrizeEntity.setPrizeNameEn(prizePool.getPrizeNameEn());
onePrizeEntity.setPrizeType(prizePool.getPrizeType());
onePrizeEntity.setRemark(prizePool.getRemark());
onePrizeEntity.setPrizeAmount(prizePool.getPrizeAmount());
} else {
// 根据 oriPrizeEntity 来填充
}
onePrizeEntity.setOdds(onePrizeDto.getOdds()); // 不能为负值
onePrizeEntity.setCreateBy(activityInfo.getCreateBy());
}
activityPrizeService.save(onePrizeEntity);
}
}
// 清理被替换掉的奖品
for (String oneOldPoolId : oriPrizeMap.keySet()) {
if (keepedPrizeIdMap.containsKey(oneOldPoolId)) {
// 该奖品被保持了
} else {
// 该奖品被清除了,修改状态
ActivityPrize invalidPrize = oriPrizeMap.get(oneOldPoolId);
invalidPrize.setStatus(0);
activityPrizeService.save(invalidPrize);
}
}
return existActivityLibrary;
}
/**
* 对管理员展示活动信息
*
* @param activityId
* @return
*/
@Override
public ActivityEditInfoDTO getActivityDetail(String activityId, String lang) {
if (StrUtil.isBlank(activityId) || Objects.equals(activityId, "0")) {
throw new BusinessException("未指定活动");
}
if (StrUtil.isBlank(lang)) {
lang = "cn";
}
ActivityLibrary existActivityLibrary = activityLibraryService.findById(activityId);
if (existActivityLibrary == null) {
throw new BusinessException("不存在的活动");
}
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(existActivityLibrary.getType());
String lotteryConfigJson = existActivityLibrary.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> lotteryConfigList = (List<ActivityParam>)JsonUtils.readValue(lotteryConfigJson, paramType);
ActivityEditInfoDTO retDto = new ActivityEditInfoDTO();
retDto.setId(existActivityLibrary.getId().toString());
retDto.setType(existActivityLibrary.getType());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setStatus(existActivityLibrary.getStatus());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setDetailUrl(existActivityLibrary.getDetailUrl());
retDto.setImageUrl(existActivityLibrary.getImageUrl());
retDto.setStartTime(DateUtil.formatDateTime(existActivityLibrary.getStartTime()));
retDto.setEndTime(DateUtil.formatDateTime(existActivityLibrary.getEndTime()));
retDto.setTemplateId(existActivityLibrary.getTemplateId());
//retDto.setActivityCode(existActivityLibrary.getActivityCode());
List<ActivityI18nContent> i18nContents = new ArrayList();
retDto.setI18nContents(i18nContents);
ActivityI18nContent i18nContent1 = new ActivityI18nContent();
ActivityI18nContent i18nContent2 = new ActivityI18nContent();
i18nContent1.setLang("cn");
i18nContent1.setTitle(existActivityLibrary.getTitleCn());
i18nContent1.setDescription(existActivityLibrary.getDescriptionCn());
i18nContent2.setLang("en");
i18nContent2.setTitle(existActivityLibrary.getTitleEn());
i18nContent2.setDescription(existActivityLibrary.getDescriptionEn());
i18nContents.add(i18nContent1);
i18nContents.add(i18nContent2);
List<ActivityPrize> activityPrizeList = activityPrizeService.listByActivityId(existActivityLibrary.getId().toString(), 1);
if (CollectionUtils.isNotEmpty(activityPrizeList)) {
retDto.setPrizeList(new ArrayList<>());
// // 增加一个谢谢惠顾虚拟奖品记录
// ActivityPrizeDTO mockPrizeDto = new ActivityPrizeDTO();
// retDto.getPrizeList().add(mockPrizeDto);
// mockPrizeDto.setOdds(0.0F);
// mockPrizeDto.setId("0");
// mockPrizeDto.setDefaultPrize(1);
// mockPrizeDto.setImage("");
// mockPrizeDto.setMaxQuantity(0);
// mockPrizeDto.setPoolId("0");
// mockPrizeDto.setPrizeType(2);
// mockPrizeDto.setPrizeAmount(0.0);
// if (lang.equals("cn")) {
// mockPrizeDto.setPrizeName("谢谢惠顾");
// } else {
// mockPrizeDto.setPrizeName("Thanks");
// }
for (ActivityPrize onePrizeEntity : activityPrizeList) {
ActivityPrizeDTO onePrizeDto = new ActivityPrizeDTO();
onePrizeDto.setId(onePrizeEntity.getId().toString());
// 严谨做法:需要判断奖品池记录是否存在
onePrizeDto.setPoolId(onePrizeEntity.getPoolId());
onePrizeDto.setMaxQuantity(onePrizeEntity.getMaxQuantity());
onePrizeDto.setPrizeAmount(onePrizeEntity.getPrizeAmount());
if (lang.equals("cn")) {
onePrizeDto.setPrizeName(onePrizeEntity.getPrizeNameCn());
} else {
onePrizeDto.setPrizeName(onePrizeEntity.getPrizeNameEn());
}
onePrizeDto.setDefaultPrize(onePrizeEntity.getDefaultPrize());
onePrizeDto.setPrizeType(onePrizeEntity.getPrizeType());
onePrizeDto.setImage(onePrizeEntity.getImage());
onePrizeDto.setOdds(onePrizeEntity.getOdds());
retDto.getPrizeList().add(onePrizeDto);
}
}
return retDto;
}
@Override
public ActivityEditInfoDTO loadActivityTemplate(String templateId) {
if (StrUtil.isBlank(templateId) || Objects.equals(templateId, "0")) {
throw new BusinessException("未指定活动");
}
ActivityTemplate existActivityTemplate = activityTemplateService.getById(templateId);
if (existActivityTemplate == null) {
throw new BusinessException("不存在的活动");
}
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(existActivityTemplate.getType());
String lotteryConfigJson = existActivityTemplate.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {};
List<ActivityParam> lotteryConfigList = (List<ActivityParam>)JsonUtils.readValue(lotteryConfigJson, paramType);
ActivityEditInfoDTO retDto = new ActivityEditInfoDTO();
retDto.setId("");
retDto.setType(existActivityTemplate.getType());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setTemplateId(existActivityTemplate.getId().toString());
retDto.setStatus(existActivityTemplate.getStatus());
retDto.setActivityConfigInfos(lotteryConfigList);
retDto.setImageUrl("");
retDto.setTitle("");
ActivityPrizePool defaultPrize = null;
List<ActivityPrizePool> defaultPrizeList = activityPrizePoolService.listDefaltPrize(1, 1);
if (CollectionUtil.isNotEmpty(defaultPrizeList)) {
defaultPrize = defaultPrizeList.get(0);
}
int prizeCount = 9;
if (defaultPrize != null) {
prizeCount = 8;
}
List<ActivityPrizePool> prizeList0 = activityPrizePoolService.listLotteryPrize(prizeCount);
List<ActivityPrizePool> prizeList = new ArrayList<>();
if (defaultPrize != null) {
prizeList.add(defaultPrize);
}
prizeList.addAll(prizeList0);
if (CollectionUtils.isNotEmpty(prizeList)) {
retDto.setPrizeList(new ArrayList<>());
for (ActivityPrizePool prizeObj : prizeList) {
ActivityPrizeDTO onePrizeDto = new ActivityPrizeDTO();
onePrizeDto.setId("");
// 严谨做法:需要判断奖品池记录是否存在
onePrizeDto.setPoolId(prizeObj.getId().toString());
onePrizeDto.setMaxQuantity(0);
onePrizeDto.setOdds(BigDecimal.ZERO); // 不能为负值
onePrizeDto.setPrizeAmount(prizeObj.getPrizeAmount());
onePrizeDto.setPrizeName(StrUtil.isBlank(prizeObj.getPrizeNameCn()) ? prizeObj.getPrizeNameEn() : prizeObj.getPrizeNameCn());
onePrizeDto.setPrizeType(prizeObj.getPrizeType());
retDto.getPrizeList().add(onePrizeDto);
}
}
return retDto;
}
/**
* 当前活动内置支持的事件枚举
*
*/
public static enum ActivityTouchEventTypeEnum {
LOTTERY("lottery", ActivityUserLotteryMessage.class, "抽奖"),
;
private String eventType;
private Class<? extends BaseActivityMessage> eventInfoClazz;
private String description;
private ActivityTouchEventTypeEnum(String eventType, Class<? extends BaseActivityMessage> eventInfoClazz, String description) {
this.eventType = eventType;
this.eventInfoClazz = eventInfoClazz;
this.description = description;
}
public static ActivityTouchEventTypeEnum typeOf(String eventType) {
if (StrUtil.isBlank(eventType)) {
return null;
}
ActivityTouchEventTypeEnum values[] = ActivityTouchEventTypeEnum.values();
for (ActivityTouchEventTypeEnum one : values) {
if (one.getEventType().equalsIgnoreCase(eventType)) {
return one;
}
}
return null;
}
public String getEventType() {
return eventType;
}
public Class<? extends BaseActivityMessage> getEventInfoClazz() {
return eventInfoClazz;
}
public String getDescription() {
return description;
}
}
public void setWalletLogService(WalletLogService walletLogService) {
this.walletLogService = walletLogService;
}
public void setUserRecomService(UserRecomService userRecomService) {
this.userRecomService = userRecomService;
}
public void setPartyService(PartyService partyService) {
this.partyService = partyService;
}
public void setActivitySimpleLotteryHelper(ActivitySimpleLotteryHelper activityLotteryHelper) {
this.activitySimpleLotteryHelper = activityLotteryHelper;
}
public void setActivityUserPointsService(ActivityUserPointsService activityUserPointsService) {
this.activityUserPointsService = activityUserPointsService;
}
}

View File

@@ -0,0 +1,249 @@
package project.mall.activity.helper;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import project.mall.LanguageEnum;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.model.lottery.ActivityUserPoints;
import project.mall.activity.rule.FruitDialActivityConfig;
import project.mall.activity.service.ActivityLibraryService;
import project.mall.activity.service.ActivityPrizeService;
import project.mall.activity.service.ActivityUserPointsService;
import project.mall.activity.service.ActivityUserService;
import project.mall.activity.service.lottery.LotteryRecordService;
import project.mall.activity.model.lottery.LotteryRecord;
import project.mall.seller.SellerService;
import project.mall.seller.model.Seller;
import project.party.PartyService;
import project.party.model.Party;
import project.party.model.UserRecom;
import project.party.recom.UserRecomService;
import java.util.*;
public class ActivityRechargeAndLotteryHelper {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private ActivityPrizeService activityPrizeService;
private LotteryRecordService lotteryRecordService;
private ActivityLibraryService activityLibraryService;
private SellerService sellerService;
private UserRecomService userRecomService;
private PartyService partyService;
private ActivityUserPointsService activityUserPointsService;
private ActivityUserService activityUserService;
// 注意:此处不建议依赖 ActivityHelper防止产生死循环
//private ActivityHelper activityHelper;
public FruitDialActivityConfig getActivityConfig(ActivityLibrary activityEntity) {
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityEntity.getType());
String configJson = activityEntity.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {
};
List<ActivityParam> configInfoList = (List<ActivityParam>) JsonUtils.readValue(configJson, paramType);
return (FruitDialActivityConfig) activityType.initActivityConfig(configInfoList);
}
public void beginLottery(ActivityLibrary activity, String partyId, int batchDrawTimes) {
if (activity == null || StrUtil.isBlank(partyId) || batchDrawTimes <= 0) {
throw new BusinessException(2, "不合规的参数");
}
String activityId = activity.getId().toString();
String actionType = FirstRechargeFruitDialActivityHandler.ActivityTouchEventTypeEnum.LOTTERY.getEventType();
ActivityUserPoints activityUserPoints = activityUserPointsService.saveOrGetUserPoints(activity.getType(), activityId, partyId);
ActivityUser activityUser = activityUserService.getActivityUser(activityId, partyId, actionType);
int leftLotteryTimes = 0;
if (activityUser != null) {
if (activityUser.getAllowJoinTimes() <= 0) {
// 抽奖次数不设限制以及支持随着业务逻辑可以动态增加上限时allowJoinTimes 的值一开始是为 0 的,此处需要特殊处理 TODO
leftLotteryTimes = 0;
} else {
// allowJoinTimes 值大于 0 代表有了具体的上限值,该值在业务中可能会动态变化
leftLotteryTimes = activityUser.getAllowJoinTimes() - activityUser.getJoinTimes();
}
} else {
// 一个上级用户还没来得及参加首充,但是其下级用户参加活动导致其产生的活动积分可用吗?
activityUser = activityUserService.saveOrGetActivityUser(activity.getType(), activityId, partyId, actionType);
}
FruitDialActivityConfig activityConfig = getActivityConfig(activity);
int exchangeRatio = activityConfig.getScoreExchangeLotteryTimeRatio();
if (leftLotteryTimes >= batchDrawTimes) {
return;
}
if (activityUserPoints.getPoints() / exchangeRatio + leftLotteryTimes < batchDrawTimes) {
throw new BusinessException(3, "积分不足");
}
// 减少积分的数量
int reducePoints = (batchDrawTimes - leftLotteryTimes) * exchangeRatio;
activityUserPointsService.updatePoints(activityUserPoints.getId().toString(), -reducePoints);
// 用户增加抽奖次数
activityUserService.updateAllowJoinTimes(activityUser.getId().toString(), batchDrawTimes - leftLotteryTimes);
}
/**
* 生成奖项
*
* @return
*/
@Transactional
public List<ActivityPrize> draw(String activityId, String partyId, String lang, int batchDrawTimes, ActivityPrize defaultPrize) {
ActivityLibrary lottery = activityLibraryService.findById(activityId);
List<ActivityPrize> drawedLotteryPrizes = new ArrayList<>();
ActivityPrize currentDrawedLotteryPrize = null;
//初始化记载所有奖品信息
List<ActivityPrize> lotteryPrizes = activityPrizeService.listByActivityId(activityId, 1);
if (CollectionUtil.isEmpty(lotteryPrizes)) {
return drawedLotteryPrizes;
}
if (batchDrawTimes <= 0) {
return drawedLotteryPrizes;
}
Seller seller = sellerService.getSeller(partyId);
Party currentParty = partyService.cachePartyBy(partyId, true);
UserRecom userRecom = userRecomService.findByPartyId(partyId);
String recomName = null;
if (Objects.nonNull(userRecom)) {
Party party = partyService.cachePartyBy(userRecom.getPartyId(), true);
recomName = party.getUsername();
}
if (defaultPrize == null) {
List<ActivityPrize> defaltPrizeList = activityPrizeService.listDefaltPrize(activityId);
if (CollectionUtil.isNotEmpty(defaltPrizeList)) {
// TODO
defaultPrize = defaltPrizeList.get(0);
}
}
for (int times = 0; times < batchDrawTimes; times++) {
long result = randomNum(1, 100000000);
int line = 0;
int temp = 0;
for (int i = 0; i < lotteryPrizes.size(); i++) {
ActivityPrize tempPrice = lotteryPrizes.get(i);
// 因此中奖几率最低是八位小数,不能再小了
double d = (tempPrice.getOdds().floatValue() * 100000000);
int c = (int) d;
temp = temp + c;
line = 100000000 - temp;
if (c != 0) {
if (result > line && result <= (line + c)) {
currentDrawedLotteryPrize = tempPrice;
break;
}
}
}
if (currentDrawedLotteryPrize == null) {
currentDrawedLotteryPrize = defaultPrize;
}
if (Objects.nonNull(currentDrawedLotteryPrize)
&& (currentDrawedLotteryPrize.getId() != null
&& StrUtil.isNotBlank(currentDrawedLotteryPrize.getId().toString())
&& !Objects.equals(currentDrawedLotteryPrize.getId().toString(), "0")
&& currentDrawedLotteryPrize.getPrizeType() != 3)
) {
LotteryRecord lotteryRecord = new LotteryRecord();
lotteryRecord.setPrizeId(currentDrawedLotteryPrize.getId().toString());
lotteryRecord.setPartyId(partyId);
lotteryRecord.setPrizeAmount(currentDrawedLotteryPrize.getPrizeAmount());
lotteryRecord.setPrizeType(currentDrawedLotteryPrize.getPrizeType());
lotteryRecord.setLotteryTime(new Date());
lotteryRecord.setActivityId(currentDrawedLotteryPrize.getActivityId());
if (LanguageEnum.CN.getLang().equals(lang)) {
lotteryRecord.setLotteryName(lottery.getTitleCn());
} else {
lotteryRecord.setLotteryName(lottery.getTitleEn());
}
lotteryRecord.setPartyName(currentParty.getUsername());
lotteryRecord.setRecommendName(recomName);
lotteryRecord.setSellerName(seller.getName());
lotteryRecord.setPrizeImage(currentDrawedLotteryPrize.getImage());
lotteryRecordService.add(lotteryRecord, lang);
drawedLotteryPrizes.add(currentDrawedLotteryPrize);
} else {
// 谢谢惠顾这种非入库奖品,不产生 record 记录
if (currentDrawedLotteryPrize != null) {
drawedLotteryPrizes.add(currentDrawedLotteryPrize);
}
}
}
return drawedLotteryPrizes;
}
// 获取2个值之间的随机数
private static long randomNum(int smin, int smax) {
int range = smax - smin;
double rand = Math.random();
return (smin + Math.round(rand * range));
}
public void setActivityPrizeService(ActivityPrizeService activityPrizeService) {
this.activityPrizeService = activityPrizeService;
}
public void setLotteryRecordService(LotteryRecordService lotteryRecordService) {
this.lotteryRecordService = lotteryRecordService;
}
public void setActivityLibraryService(ActivityLibraryService activityLibraryService) {
this.activityLibraryService = activityLibraryService;
}
public void setSellerService(SellerService sellerService) {
this.sellerService = sellerService;
}
public void setUserRecomService(UserRecomService userRecomService) {
this.userRecomService = userRecomService;
}
public void setPartyService(PartyService partyService) {
this.partyService = partyService;
}
public void setActivityUserPointsService(ActivityUserPointsService activityUserPointsService) {
this.activityUserPointsService = activityUserPointsService;
}
public void setActivityUserService(ActivityUserService activityUserService) {
this.activityUserService = activityUserService;
}
}

View File

@@ -0,0 +1,259 @@
package project.mall.activity.helper;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.JsonUtils;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import project.mall.LanguageEnum;
import project.mall.activity.ActivityTypeEnum;
import project.mall.activity.core.vo.ActivityParam;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.handler.SimpleLotteryActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.model.lottery.ActivityUserPoints;
import project.mall.activity.model.lottery.LotteryRecord;
import project.mall.activity.rule.SimpleLotteryActivityConfig;
import project.mall.activity.service.*;
import project.mall.activity.service.lottery.LotteryRecordService;
import project.mall.seller.SellerService;
import project.mall.seller.model.Seller;
import project.party.PartyService;
import project.party.model.Party;
import project.party.model.UserRecom;
import project.party.recom.UserRecomService;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class ActivitySimpleLotteryHelper {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private ActivityPrizeService activityPrizeService;
private LotteryRecordService lotteryRecordService;
private ActivityLibraryService activityLibraryService;
private SellerService sellerService;
private UserRecomService userRecomService;
private PartyService partyService;
private ActivityUserPointsService activityUserPointsService;
private ActivityUserService activityUserService;
private ActivityUserPointsLogService activityUserPointsLogService;
// 注意:此处不建议依赖 ActivityHelper防止产生死循环
//private ActivityHelper activityHelper;
public SimpleLotteryActivityConfig getActivityConfig(ActivityLibrary activityEntity) {
ActivityTypeEnum activityType = ActivityTypeEnum.typeOf(activityEntity.getType());
String configJson = activityEntity.getActivityConfig();
TypeReference paramType = new TypeReference<List<ActivityParam>>() {
};
List<ActivityParam> configInfoList = (List<ActivityParam>) JsonUtils.readValue(configJson, paramType);
return (SimpleLotteryActivityConfig) activityType.initActivityConfig(configInfoList);
}
public void beginLottery(ActivityLibrary activity, String partyId, int batchDrawTimes) {
if (activity == null || StrUtil.isBlank(partyId) || batchDrawTimes <= 0) {
throw new BusinessException(2, "不合规的参数");
}
String activityId = activity.getId().toString();
String actionType = SimpleLotteryActivityHandler.ActivityTouchEventTypeEnum.LOTTERY.getEventType();
// 共享积分
ActivityUserPoints activityUserPoints = activityUserPointsService.saveOrGetUserPoints(activity.getType(), "0", partyId);
ActivityUser activityUser = activityUserService.getActivityUser(activityId, partyId, actionType);
int leftLotteryTimes = 0;
if (activityUser != null) {
if (activityUser.getAllowJoinTimes() <= 0) {
// 抽奖次数不设限制以及支持随着业务逻辑可以动态增加上限时allowJoinTimes 的值一开始是为 0 的,此处需要特殊处理 TODO
leftLotteryTimes = 0;
} else {
// allowJoinTimes 值大于 0 代表有了具体的上限值,该值在业务中可能会动态变化
leftLotteryTimes = activityUser.getAllowJoinTimes() - activityUser.getJoinTimes();
}
} else {
// 一个上级用户还没来得及参加首充,但是其下级用户参加活动导致其产生的活动积分可用吗?
activityUser = activityUserService.saveOrGetActivityUser(activity.getType(), activityId, partyId, actionType);
}
SimpleLotteryActivityConfig activityConfig = getActivityConfig(activity);
int exchangeRatio = activityConfig.getScoreExchangeLotteryTimeRatio();
if (leftLotteryTimes >= batchDrawTimes) {
return;
}
if (activityUserPoints.getPoints() / exchangeRatio + leftLotteryTimes < batchDrawTimes) {
throw new BusinessException(3, "积分不足");
}
// 减少积分的数量
int reducePoints = (batchDrawTimes - leftLotteryTimes) * exchangeRatio;
activityUserPointsService.updatePoints(activityUserPoints.getId().toString(), -reducePoints);
// 用户增加抽奖次数
activityUserService.updateAllowJoinTimes(activityUser.getId().toString(), batchDrawTimes - leftLotteryTimes);
// 记录扣分历史记录
activityUserPointsLogService.saveLog(activityUser.getId().toString(), -reducePoints, "0", activity.getType(), activity.getId().toString());
}
/**
* 生成奖项
*
* @return
*/
@Transactional
public List<ActivityPrize> draw(String activityId, String partyId, String lang, int batchDrawTimes, ActivityPrize defaultPrize) {
ActivityLibrary lottery = activityLibraryService.findById(activityId);
List<ActivityPrize> drawedLotteryPrizes = new ArrayList<>();
ActivityPrize currentDrawedLotteryPrize = null;
//初始化记载所有奖品信息
List<ActivityPrize> lotteryPrizes = activityPrizeService.listByActivityId(activityId, 1);
if (CollectionUtil.isEmpty(lotteryPrizes)) {
return drawedLotteryPrizes;
}
if (batchDrawTimes <= 0) {
return drawedLotteryPrizes;
}
Seller seller = sellerService.getSeller(partyId);
Party currentParty = partyService.cachePartyBy(partyId, true);
UserRecom userRecom = userRecomService.findByPartyId(partyId);
String recomName = null;
if (Objects.nonNull(userRecom)) {
Party party = partyService.cachePartyBy(userRecom.getPartyId(), true);
recomName = party.getUsername();
}
if (defaultPrize == null) {
List<ActivityPrize> defaltPrizeList = activityPrizeService.listDefaltPrize(activityId);
if (CollectionUtil.isNotEmpty(defaltPrizeList)) {
// TODO
defaultPrize = defaltPrizeList.get(0);
}
}
for (int times = 0; times < batchDrawTimes; times++) {
long result = randomNum(1, 100000000);
int line = 0;
int temp = 0;
for (int i = 0; i < lotteryPrizes.size(); i++) {
ActivityPrize tempPrice = lotteryPrizes.get(i);
// 因此中奖几率最低是八位小数,不能再小了
double d = (tempPrice.getOdds().floatValue() * 100000000);
int c = (int) d;
temp = temp + c;
line = 100000000 - temp;
if (c != 0) {
if (result > line && result <= (line + c)) {
currentDrawedLotteryPrize = tempPrice;
break;
}
}
}
if (currentDrawedLotteryPrize == null) {
currentDrawedLotteryPrize = defaultPrize;
}
if (Objects.nonNull(currentDrawedLotteryPrize)
&& (currentDrawedLotteryPrize.getId() != null
&& StrUtil.isNotBlank(currentDrawedLotteryPrize.getId().toString())
&& !Objects.equals(currentDrawedLotteryPrize.getId().toString(), "0"))
&& currentDrawedLotteryPrize.getPrizeType() != 3) {
LotteryRecord lotteryRecord = new LotteryRecord();
lotteryRecord.setPrizeId(currentDrawedLotteryPrize.getId().toString());
lotteryRecord.setPartyId(partyId);
lotteryRecord.setPrizeAmount(currentDrawedLotteryPrize.getPrizeAmount());
lotteryRecord.setPrizeType(currentDrawedLotteryPrize.getPrizeType());
lotteryRecord.setLotteryTime(new Date());
lotteryRecord.setActivityId(currentDrawedLotteryPrize.getActivityId());
if (LanguageEnum.CN.getLang().equals(lang)) {
lotteryRecord.setLotteryName(lottery.getTitleCn());
} else {
lotteryRecord.setLotteryName(lottery.getTitleEn());
}
lotteryRecord.setPartyName(currentParty.getUsername());
lotteryRecord.setRecommendName(recomName);
lotteryRecord.setSellerName(seller.getName());
lotteryRecord.setPrizeImage(currentDrawedLotteryPrize.getImage());
lotteryRecordService.add(lotteryRecord, lang);
drawedLotteryPrizes.add(currentDrawedLotteryPrize);
} else {
// 谢谢惠顾这种非入库奖品,不产生 record 记录
if (currentDrawedLotteryPrize != null) {
drawedLotteryPrizes.add(currentDrawedLotteryPrize);
}
}
}
return drawedLotteryPrizes;
}
// 获取2个值之间的随机数
private static long randomNum(int smin, int smax) {
int range = smax - smin;
double rand = Math.random();
return (smin + Math.round(rand * range));
}
public void setActivityPrizeService(ActivityPrizeService activityPrizeService) {
this.activityPrizeService = activityPrizeService;
}
public void setLotteryRecordService(LotteryRecordService lotteryRecordService) {
this.lotteryRecordService = lotteryRecordService;
}
public void setActivityLibraryService(ActivityLibraryService activityLibraryService) {
this.activityLibraryService = activityLibraryService;
}
public void setSellerService(SellerService sellerService) {
this.sellerService = sellerService;
}
public void setUserRecomService(UserRecomService userRecomService) {
this.userRecomService = userRecomService;
}
public void setPartyService(PartyService partyService) {
this.partyService = partyService;
}
public void setActivityUserPointsService(ActivityUserPointsService activityUserPointsService) {
this.activityUserPointsService = activityUserPointsService;
}
public void setActivityUserService(ActivityUserService activityUserService) {
this.activityUserService = activityUserService;
}
public void setActivityUserPointsLogService(ActivityUserPointsLogService activityUserPointsLogService) {
this.activityUserPointsLogService = activityUserPointsLogService;
}
}

View File

@@ -0,0 +1,79 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityConfigLog" table="ACTIVITY_CONFIG_LOG">
<id name="id" type="java.lang.String">
<column name="UUID"/>
<generator class="uuid.hex"/>
</id>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID"/>
</property>
<property name="type" type="java.lang.String">
<column name="TYPE"/>
</property>
<property name="titleCn" type="java.lang.String">
<column name="TITLE_CN"/>
</property>
<property name="titleEn" type="java.lang.String">
<column name="TITLE_EN"/>
</property>
<property name="tags" type="java.lang.String">
<column name="TAGS"/>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS"/>
</property>
<property name="startTime" type="timestamp">
<column name="START_TIME"/>
</property>
<property name="endTime" type="timestamp">
<column name="END_TIME"/>
</property>
<property name="allowJoinTimes" type="java.lang.Integer">
<column name="ALLOW_JOIN_TIMES"/>
</property>
<property name="activityConfig" type="java.lang.String">
<column name="ACTIVITY_CONFIG"/>
</property>
<property name="joinRule" type="java.lang.String">
<column name="JOIN_RULE"/>
</property>
<property name="awardRule" type="java.lang.String">
<column name="AWARD_RULE"/>
</property>
<property name="detailUrl" type="java.lang.String">
<column name="DETAIL_URL"/>
</property>
<property name="imageUrl" type="java.lang.String">
<column name="IMAGE_URL"/>
</property>
<property name="location" type="java.lang.Integer">
<column name="LOCATION"/>
</property>
<property name="descriptionCn" type="java.lang.String">
<column name="DESCRIPTION_CN"/>
</property>
<property name="descriptionEn" type="java.lang.String">
<column name="DESCRIPTION_EN"/>
</property>
<property name="lastOperator" type="java.lang.String">
<column name="LAST_OPERATOR"/>
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME"/>
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME"/>
</property>
<property name="logTime" type="timestamp">
<column name="LOG_TIME"/>
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,107 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.util.Date;
/**
* 活动记录修改历史记录
*
* @author caster
*/
@Data
public class ActivityConfigLog extends EntityObject {
// 对应 activityLibrary 表的 id
private String activityId;
/**
* 活动类型编码参考枚举类型ActivityTypeEnum
*/
private String type;
// // 全局唯一,和 id 一一对应
// private String activityCode;
/**
* 活动标题
*/
private String titleCn;
private String titleEn;
/**
* 活动标签,多个标签之间用","隔开
*/
private String tags;
/**
* 活动有效开始时间,不给空值
*/
private Date startTime;
/**
* 活动有效截止时间,不给空值
*/
private Date endTime;
/**
* 活动允许重复参加的次数0-无限制n 代表只能参加 N 次)
*/
private Integer allowJoinTimes;
/**
* 活动状态0-未启动1-启动2-结束活动
*/
private Integer status;
/**
* 活动配置信息,难以区分分类时可以直接取代 jonRule 和 awardRule
* 参考枚举类型ActivityTypeEnum
* 存储结构为: ActivityParam 对象集合
*/
private String activityConfig;
/**
* 活动加入规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String joinRule;
/**
* 活动奖励规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String awardRule;
/**
* 活动详情网页地址
*/
private String detailUrl;
/**
* 活动封面图片地址
*/
private String imageUrl;
/**
* 活动排序位置值越小排序越靠前默认值99999
*/
private Integer location;
/**
* 活动详情内容
*/
private String descriptionCn;
private String descriptionEn;
private String lastOperator;
// 原始记录 createTime
private Date createTime;
// 原始记录 updateTime
private Date updateTime;
// 活动记录入 log 日志的操作时间
private Date logTime;
}

View File

@@ -0,0 +1,84 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityLibrary" table="ACTIVITY_LIBRARY">
<id name="id" type="java.lang.String">
<column name="UUID"/>
<generator class="uuid.hex"/>
</id>
<property name="templateId" type="java.lang.String">
<column name="TEMPLATE_ID"/>
</property>
<property name="type" type="java.lang.String">
<column name="TYPE"/>
</property>
<property name="titleCn" type="java.lang.String">
<column name="TITLE_CN"/>
</property>
<property name="titleEn" type="java.lang.String">
<column name="TITLE_EN"/>
</property>
<property name="tags" type="java.lang.String">
<column name="TAGS"/>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS"/>
</property>
<property name="isShow" type="java.lang.Integer">
<column name="IS_SHOW"/>
</property>
<property name="startTime" type="timestamp">
<column name="START_TIME"/>
</property>
<property name="endTime" type="timestamp">
<column name="END_TIME"/>
</property>
<property name="allowJoinTimes" type="java.lang.Integer">
<column name="ALLOW_JOIN_TIMES"/>
</property>
<property name="activityConfig" type="java.lang.String">
<column name="ACTIVITY_CONFIG"/>
</property>
<property name="joinRule" type="java.lang.String">
<column name="JOIN_RULE"/>
</property>
<property name="awardRule" type="java.lang.String">
<column name="AWARD_RULE"/>
</property>
<property name="detailUrl" type="java.lang.String">
<column name="DETAIL_URL"/>
</property>
<property name="imageUrl" type="java.lang.String">
<column name="IMAGE_URL"/>
</property>
<property name="location" type="java.lang.Integer">
<column name="LOCATION"/>
</property>
<property name="descriptionCn" type="java.lang.String">
<column name="DESCRIPTION_CN"/>
</property>
<property name="descriptionEn" type="java.lang.String">
<column name="DESCRIPTION_EN"/>
</property>
<property name="lastOperator" type="java.lang.String">
<column name="LAST_OPERATOR"/>
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME"/>
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME"/>
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,136 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 系统活动库表
*
* @author caster
*/
@Data
public class ActivityLibrary extends EntityObject {
private String templateId;
/**
* 活动类型编码参考枚举类型ActivityTypeEnum
*/
private String type;
// 全局唯一,和 id 一一对应
//private String activityCode;
/**
* 活动标题
*/
//private String title;
private String titleCn;
private String titleEn;
/**
* 活动标签,多个标签之间用","隔开
*/
private String tags;
/**
* 活动有效开始时间,不给空值
*/
private Date startTime;
/**
* 活动有效截止时间,不给空值
*/
private Date endTime;
// /**
// * 用户触发活动模式0-全局被动生效1-租户内用户被动生效2-用户主动参与生效3-用户行为触发
// */
// private Integer triggerMode;
/**
* 活动允许重复参加的次数0-无限制n 代表只能参加 N 次)
*/
private Integer allowJoinTimes;
/**
* 活动状态0-未启动1-启动2-结束活动
*/
private Integer status;
/**
* 活动是否显示状态0-不显示1-显示
*/
private Integer isShow;
/**
* 活动配置信息,难以区分分类时可以直接取代 jonRule 和 awardRule
* 参考枚举类型ActivityTypeEnum
* 存储结构为: ActivityParam 对象集合
*/
private String activityConfig;
/**
* 活动加入规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String joinRule;
/**
* 活动奖励规则json结构
* 存储结构为: ActivityParam 对象集合
*/
private String awardRule;
/**
* 活动详情网页地址
*/
private String detailUrl;
/**
* 活动封面图片地址
*/
private String imageUrl;
/**
* 活动排序位置值越小排序越靠前默认值99999
*/
private Integer location;
/**
* 活动详情内容
*/
//private String description;
private String descriptionCn;
private String descriptionEn;
// /**
// * 活动在子租户下的继承模式0-不继承1-直接子租户继承2-多级子租户继承
// */
// private Integer inheritMode;
//
// /**
// * 活动介绍,支持富文本
// */
// private String description;
//
// /**
// * 如果当前活动继承自上级租户则此处填写相关的最原始的活动ID(注意并不一定是上级租户的活动ID)
// * 默认值为 0
// */
// private Long fromActivity;
private String lastOperator;
private Date createTime;
private Date updateTime;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,75 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityPrize" table="ACTIVITY_PRIZE">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="poolId" type="java.lang.String">
<column name="POOL_ID" />
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID" />
</property>
<property name="prizeNameCn" type="java.lang.String">
<column name="PRIZE_NAME_CN" />
</property>
<property name="prizeNameEn" type="java.lang.String">
<column name="PRIZE_NAME_EN" />
</property>
<property name="prizeType" type="java.lang.Integer">
<column name="PRIZE_TYPE" />
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS" />
</property>
<property name="prizeAmount" type="java.math.BigDecimal">
<column name="PRIZE_AMOUNT" />
</property>
<property name="image" type="java.lang.String">
<column name="IMAGE" />
</property>
<property name="remark" type="java.lang.String">
<column name="REMARK" />
</property>
<property name="maxQuantity" type="java.lang.Integer">
<column name="MAX_QUANTITY" />
</property>
<property name="leftQuantity" type="java.lang.Integer">
<column name="LEFT_QUANTITY" />
</property>
<property name="odds" type="java.math.BigDecimal">
<column name="ODDS" />
</property>
<property name="defaultPrize" type="java.lang.Integer">
<column name="DEFAULT_PRIZE" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="createBy" type="java.lang.String">
<column name="CREATE_BY" />
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,81 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrize extends EntityObject {
private String poolId;
private String activityId;
/**
*
*/
private String prizeNameCn;
private String prizeNameEn;
/**
* 奖品类型, 1-实物、2-彩金, 3-谢谢惠顾
*/
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品最大数量0-无限
*/
private Integer maxQuantity;
/**
* 奖品剩余数量,当 max_quantity=0 时该值无意义
*/
private Integer leftQuantity;
/**
* 中奖几率
*/
private BigDecimal odds;
/**
* 该奖项状态0-不可用1-可用
*/
private Integer status;
/**
* 当所有随机奖励都没有获取时,当前奖品是否是默认奖品
*/
private Integer defaultPrize;
private String image;
private String remark;
/**
*/
private Date createTime;
private Date updateTime;
/**
*/
private String createBy;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,78 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityPrizeLog" table="ACTIVITY_PRIZE_LOG">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="activityLogId" type="java.lang.String">
<column name="ACTIVITY_LOG_ID" />
</property>
<property name="poolId" type="java.lang.String">
<column name="POOL_ID" />
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID" />
</property>
<property name="prizeNameCn" type="java.lang.String">
<column name="PRIZE_NAME_CN" />
</property>
<property name="prizeNameEn" type="java.lang.String">
<column name="PRIZE_NAME_EN" />
</property>
<property name="prizeType" type="java.lang.Integer">
<column name="PRIZE_TYPE" />
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS" />
</property>
<property name="prizeAmount" type="java.math.BigDecimal">
<column name="PRIZE_AMOUNT" />
</property>
<property name="image" type="java.lang.String">
<column name="IMAGE" />
</property>
<property name="remark" type="java.lang.String">
<column name="REMARK" />
</property>
<property name="maxQuantity" type="java.lang.Integer">
<column name="MAX_QUANTITY" />
</property>
<property name="leftQuantity" type="java.lang.Integer">
<column name="LEFT_QUANTITY" />
</property>
<property name="odds" type="java.math.BigDecimal">
<column name="ODDS" />
</property>
<property name="defaultPrize" type="java.lang.Integer">
<column name="DEFAULT_PRIZE" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="createBy" type="java.lang.String">
<column name="CREATE_BY" />
</property>
<property name="logTime" type="timestamp">
<column name="LOG_TIME" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,83 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrizeLog extends EntityObject {
// 对应 activityConfigLog 表记录主键id
private String activityLogId;
private String poolId;
private String activityId;
/**
*
*/
private String prizeNameCn;
private String prizeNameEn;
/**
* 奖品类型, 1-实物、2-彩金, 3-谢谢惠顾
*/
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品最大数量0-无限
*/
private Integer maxQuantity;
/**
* 奖品剩余数量,当 max_quantity=0 时该值无意义
*/
private Integer leftQuantity;
/**
* 中奖几率
*/
private BigDecimal odds;
/**
* 该奖项状态0-不可用1-可用
*/
private Integer status;
/**
* 当所有随机奖励都没有获取时,当前奖品是否是默认奖品
*/
private Integer defaultPrize;
private String image;
private String remark;
/**
*/
private Date createTime;
private Date updateTime;
/**
* 最后修改用户
*/
private String createBy;
private Date logTime;
}

View File

@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityPrizePool" table="ACTIVITY_PRIZE_POOL">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="prizeNameCn" type="java.lang.String">
<column name="PRIZE_NAME_CN" />
</property>
<property name="prizeNameEn" type="java.lang.String">
<column name="PRIZE_NAME_EN" />
</property>
<property name="prizeType" type="java.lang.Integer">
<column name="PRIZE_TYPE" />
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS" />
</property>
<property name="prizeAmount" type="java.math.BigDecimal">
<column name="PRIZE_AMOUNT" />
</property>
<property name="image" type="java.lang.String">
<column name="IMAGE" />
</property>
<property name="remark" type="java.lang.String">
<column name="REMARK" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="createBy" type="java.lang.String">
<column name="CREATE_BY" />
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,57 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 活动奖品池
*
* @author caster
*/
@Data
public class ActivityPrizePool extends EntityObject {
/**
*
*/
private String prizeNameCn;
private String prizeNameEn;
/**
* 奖品类型, 1-实物、2-彩金, 3-谢谢惠顾
*/
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 奖品启用状态0-未启用1-启用
*/
private Integer status;
private String image;
private String remark;
/**
*/
private Date createTime;
private Date updateTime;
/**
*/
private String createBy;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityTemplate" table="ACTIVITY_TEMPLATE">
<id name="id" type="java.lang.String">
<column name="UUID"/>
<generator class="uuid.hex"/>
</id>
<property name="type" type="java.lang.String">
<column name="TYPE"/>
</property>
<property name="title" type="java.lang.String">
<column name="TITLE"/>
</property>
<property name="tags" type="java.lang.String">
<column name="TAGS"/>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS"/>
</property>
<property name="activityConfig" type="java.lang.String">
<column name="ACTIVITY_CONFIG"/>
</property>
<property name="joinRule" type="java.lang.String">
<column name="JOIN_RULE"/>
</property>
<property name="awardRule" type="java.lang.String">
<column name="AWARD_RULE"/>
</property>
<property name="description" type="java.lang.String">
<column name="DESCRIPTION"/>
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME"/>
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME"/>
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,73 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.util.Date;
/**
* 活动模板表,相当于提前定义的活动配置属性,边界等信息
*
* @author caster
*/
@Data
public class ActivityTemplate extends EntityObject {
/**
* 活动类型编码参考枚举类型ActivityTypeEnum
*/
private String type;
/**
* 活动标题
*/
private String title;
/**
* 活动标签,多个标签之间用","隔开
*/
private String tags;
// /**
// * 用户触发活动模式0-全局被动生效1-租户内用户被动生效2-用户主动参与生效3-用户行为触发
// */
// private Integer triggerMode;
/**
* 模板状态0-不可用1-可用
*/
private Integer status;
/**
* 活动配置信息,难以区分分类时可以直接取代 jonRule 和 awardRule
* 参考枚举类型ActivityTypeEnum
* 存储结构为: ActivityParam 对象集合
*/
private String activityConfig;
/**
* 活动规则参数json结构
* 存储结构为: ActivityParam 对象集合
*/
private String joinRule;
/**
* 活动规则参数json结构
* 存储结构为: ActivityParam 对象集合
*/
private String awardRule;
/**
* 活动介绍,支持富文本
*/
private String description;
private Date createTime;
private Date updateTime;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityUser" table="ACTIVITY_USER">
<id name="id" type="java.lang.String">
<column name="UUID"/>
<generator class="uuid.hex"/>
</id>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID"/>
</property>
<property name="activityType" type="java.lang.String">
<column name="ACTIVITY_TYPE"/>
</property>
<property name="userId" type="java.lang.String">
<column name="USER_ID"/>
</property>
<property name="triggerType" type="java.lang.String">
<column name="TRIGGER_TYPE"/>
</property>
<property name="validBeginTime" type="timestamp">
<column name="VALID_BEGIN_TIME"/>
</property>
<property name="validEndTime" type="timestamp">
<column name="VALID_END_TIME"/>
</property>
<property name="firstTriggerTime" type="java.lang.Long">
<column name="FIRST_TRIGGER_TIME"/>
</property>
<property name="lastTriggerTime" type="java.lang.Long">
<column name="LAST_TRIGGER_TIME"/>
</property>
<property name="allowJoinTimes" type="java.lang.Integer">
<column name="ALLOW_JOIN_TIMES"/>
</property>
<property name="joinTimes" type="java.lang.Integer">
<column name="JOIN_TIMES"/>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS"/>
</property>
<property name="userType" type="java.lang.Integer">
<column name="USER_TYPE"/>
</property>
<property name="userRegistTime" type="java.lang.Long">
<column name="USER_REGIST_TIME"/>
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME"/>
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME"/>
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,83 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.util.Date;
/**
* 用户基于符合规则的方式参与指定活动,针对同一活动,一个用户针对同一事件类型只产生一条记录,
* 同类事件的多次参与的统计信息需要在本表合并到一起。
*
* @author caster
*/
@Data
public class ActivityUser extends EntityObject {
/**
* activity_library 记录主键
*/
private String activityId;
/**
* 参考枚举类型: ActivityTypeEnum
*/
private String activityType;
private String userId;
/**
* 活动触发方式:
*/
private String triggerType;
/**
* 活动有效开始时间,为空代表直接立即生效
*/
private Date validBeginTime;
/**
* 活动有效截止时间,为空代表永久生效
*/
private Date validEndTime;
/**
* 第一次触发活动时间戳
*/
private Long firstTriggerTime;
/**
* 最近一次触发活动时间戳
*/
private Long lastTriggerTime;
/**
* 累计允许参与次数
*/
private Integer allowJoinTimes;
/**
* 同一活动累计参与次数
*/
private Integer joinTimes;
/**
* 用户在该类事件下,处于的活动状态: 0-当前joinTimes次数下未结束1-当前joinTimes次数下已结束2-当前eventType下完结特殊业务会用到
*/
private Integer status;
// 用户类型1-商家2-买家
private Integer userType;
// 用户入驻平台时间,毫秒时间戳
private Long userRegistTime;
private Date createTime;
private Date updateTime;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityUserJoinLog" table="ACTIVITY_USER_JOIN_LOG">
<id name="id" type="java.lang.String">
<column name="UUID"/>
<generator class="uuid.hex"/>
</id>
<property name="userId" type="java.lang.String">
<column name="USER_ID"/>
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID"/>
</property>
<property name="triggerTime" type="java.lang.Long">
<column name="TRIGGER_TIME"/>
</property>
<property name="finishTime" type="java.lang.Long">
<column name="FINISH_TIME"/>
</property>
<property name="eventKey" type="java.lang.String">
<column name="EVENT_KEY"/>
</property>
<property name="eventType" type="java.lang.String">
<column name="EVENT_TYPE"/>
</property>
<property name="times" type="java.lang.Integer">
<column name="TIMES"/>
</property>
<property name="refId" type="java.lang.String">
<column name="REF_ID"/>
</property>
<property name="refType" type="java.lang.Integer">
<column name="REF_TYPE"/>
</property>
<property name="status" type="java.lang.Integer">
<column name="STATUS"/>
</property>
<property name="extraInfo" type="java.lang.String">
<column name="EXTRA_INFO"/>
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME"/>
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME"/>
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,80 @@
package project.mall.activity.model;
import kernel.bo.EntityObject;
import lombok.Data;
import java.util.Date;
/**
* 用户每次参与活动的历史记录
*
* @author caster
*/
@Data
public class ActivityUserJoinLog extends EntityObject {
/**
* activity_library 记录主键
*/
private String activityId;
private String userId;
// /**
// * 活动触发方式:
// */
// private Integer triggerType;
//
// private Integer type;
/**
* 本次触发加入活动的时间
*/
private Long triggerTime;
/**
* 本次完成活动的时间
*/
private Long finishTime;
/**
*
*/
private String eventType;
/**
* 同一个用户,基于同一个 eventId 只能有一条记录,防止同一活动在同一次事件中重复参与
*/
private String eventKey;
/**
* 对应活动场次
*/
private Integer times;
/**
* 用户加入本次活动相关关键业务记录ID不建议此处存奖励相关的记录
*/
private String refId;
private Integer refType;
/**
* 本次状态1-进行中2-结束
*/
private Integer status;
/**
* 记录额外信息json结构例如记录奖励相关信息具体业务具体解释
*/
private String extraInfo;
private Date createTime;
private Date updateTime;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.ActivityUserPointsLog" table="ACTIVITY_USER_POINTS_LOG">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="partyId" type="java.lang.String">
<column name="PARTY_ID" />
</property>
<property name="refType" type="java.lang.String">
<column name="REF_TYPE" />
</property>
<property name="refId" type="java.lang.String">
<column name="REF_ID" />
</property>
<property name="points" type="java.lang.Integer">
<column name="POINTS" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="createBy" type="java.lang.String">
<column name="CREATE_BY" />
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,46 @@
package project.mall.activity.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import kernel.bo.EntityObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
public class ActivityUserPointsLog extends EntityObject {
/**
* 用户ID
*/
private String partyId;
/**
* 相关业务类型:
*
*/
private String refType;
/**
* 相关业务ID
*/
private String refId;
/**
* 增减积分量,增积分时为正数,扣积分时为负数
*/
private Integer points;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 操作用户
*/
private String createBy;
private Integer deleted;
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.lottery.ActivityUserPoints" table="ACTIVITY_USER_POINTS">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="partyId" type="java.lang.String">
<column name="PARTY_ID" />
</property>
<property name="activityType" type="java.lang.String">
<column name="ACTIVITY_TYPE" />
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID" />
</property>
<property name="points" type="java.lang.Integer">
<column name="POINTS" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="deleted" type="java.lang.Integer">
<column name="DELETED" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,46 @@
package project.mall.activity.model.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import kernel.bo.EntityObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
public class ActivityUserPoints extends EntityObject {
/**
* 用户ID
*/
private String partyId;
private String activityType;
/**
* 活动ID
*/
private String activityId;
/**
* 积分
*/
private Integer points;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 逻辑删除标记: 0-正常1-删除
*/
private Integer deleted;
}

View File

@@ -0,0 +1,74 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.lottery.LotteryReceive" table="ACTIVITY_LOTTERY_RECEIVE">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="partyId" type="java.lang.String">
<column name="PARTY_ID" />
</property>
<property name="partyName" type="java.lang.String">
<column name="PARTY_NAME" />
</property>
<property name="sellerName" type="java.lang.String">
<column name="SELLER_NAME" />
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID" />
</property>
<property name="lotteryName" type="java.lang.String">
<column name="LOTTERY_NAME" />
</property>
<property name="prizeIds" type="java.lang.String">
<column name="PRIZE_IDS" />
</property>
<property name="prizeAmount" type="java.math.BigDecimal">
<column name="PRIZE_AMOUNT" />
</property>
<property name="recommendName" type="java.lang.String">
<column name="RECOMMEND_NAME" />
</property>
<property name="prizeType" type="java.lang.Integer">
<column name="PRIZE_TYPE" />
</property>
<property name="state" type="java.lang.Integer">
<column name="STATE" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="applyTime" type="timestamp">
<column name="APPLY_TIME" />
</property>
<property name="issueTime" type="timestamp">
<column name="ISSUE_TIME" />
</property>
<property name="createUser" type="java.lang.String">
<column name="CREATE_USER" />
</property>
<property name="activityType" type="java.lang.Integer">
<column name="ACTIVITY_TYPE" />
</property>
<property name="remark" type="java.lang.String">
<column name="REMARK" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,106 @@
package project.mall.activity.model.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import kernel.bo.EntityObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
public class LotteryReceive extends EntityObject {
/**
* 活动ID
*/
private String activityId;
/**
* 活动名称
*/
private String lotteryName;
/**
* 领取用户ID
*/
private String partyId;
/**
* 领取用户名称
*/
private String partyName;
/**
* 奖品ID集合以逗号分割例如112322333
*/
private String prizeIds;
/**
* 奖品类型, 1-实物、2-彩金, 3-谢谢惠顾
*/
private Integer prizeType;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 推荐人名称
*/
private String recommendName;
/**
* 店铺名
*/
private String sellerName;
/**
* 备注
*/
private String remark;
/**
* 派发状态 (1-已派发,0-未派发)
*/
private Integer state;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
/**
* 派发时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date issueTime;
/**
* 派发时间
*/
private String createUser;
/**
* 业务区分活动类型0-拉人活动1-营销活动
*/
private Integer activityType;
}

View File

@@ -0,0 +1,75 @@
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="project.mall.activity.model.lottery.LotteryRecord" table="ACTIVITY_LOTTERY_RECORD">
<id name="id" type="java.lang.String">
<column name="UUID" />
<generator class="uuid.hex" />
</id>
<property name="recommendName" type="java.lang.String">
<column name="RECOMMEND_NAME" />
</property>
<property name="partyId" type="java.lang.String">
<column name="PARTY_ID" />
</property>
<property name="partyName" type="java.lang.String">
<column name="PARTY_NAME" />
</property>
<property name="sellerName" type="java.lang.String">
<column name="SELLER_NAME" />
</property>
<property name="activityId" type="java.lang.String">
<column name="ACTIVITY_ID" />
</property>
<property name="lotteryName" type="java.lang.String">
<column name="LOTTERY_NAME" />
</property>
<property name="prizeId" type="java.lang.String">
<column name="PRIZE_ID" />
</property>
<property name="prizeName" type="java.lang.String">
<column name="PRIZE_NAME" />
</property>
<property name="prizeType" type="java.lang.Integer">
<column name="PRIZE_TYPE" />
</property>
<property name="prizeAmount" type="java.math.BigDecimal">
<column name="PRIZE_AMOUNT" />
</property>
<property name="receiveState" type="java.lang.Integer">
<column name="RECEIVE_STATE" />
</property>
<property name="createTime" type="timestamp">
<column name="CREATE_TIME" />
</property>
<property name="updateTime" type="timestamp">
<column name="UPDATE_TIME" />
</property>
<property name="lotteryTime" type="timestamp">
<column name="LOTTERY_TIME" />
</property>
<property name="receiveTime" type="timestamp">
<column name="RECEIVE_TIME" />
</property>
<property name="prizeImage" type="java.lang.String">
<column name="PRIZE_IMAGE" />
</property>
</class>
</hibernate-mapping>

View File

@@ -0,0 +1,97 @@
package project.mall.activity.model.lottery;
import com.fasterxml.jackson.annotation.JsonFormat;
import kernel.bo.EntityObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
public class LotteryRecord extends EntityObject {
/**
* 中奖用户ID
*/
private String partyId;
/**
* 中奖用户名称
*/
private String partyName;
/**
* 店铺名
*/
private String sellerName;
/**
* 推荐人名称
*/
private String recommendName;
/**
* 奖品名称
*/
private String prizeName;
/**
* 活动ID
*/
private String activityId;
/**
* 活动名称
*/
private String lotteryName;
/**
* 奖品类型, 1-实物、2-彩金, 3-谢谢惠顾
*/
private Integer prizeType;
/**
* 奖品ID
*/
private String prizeId;
/**
* 奖品图片地址
*/
private String prizeImage;
/**
* 奖品价值
*/
private BigDecimal prizeAmount;
/**
* 是否领取 (1-已领取,0-未领取)
*/
private Integer receiveState;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 中奖时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lotteryTime;
/**
* 领取时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date receiveTime;
}

View File

@@ -0,0 +1,14 @@
package project.mall.activity.rule;
import lombok.Data;
/**
* 活动配置信息,难以区分分类时可以直接取代 jonRule 和 awardRule
*/
@Data
public class BaseActivityConfig {
public String showTitle() {
return "-";
}
}

View File

@@ -0,0 +1,35 @@
package project.mall.activity.rule;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class FruitDialActivityConfig extends BaseActivityConfig {
/**
* 默认抽奖积分
*/
private Integer initLotteryScore;
/**
* 默认多少积分兑换一次抽奖
*/
private Integer scoreExchangeLotteryTimeRatio;
/**
* 邀请获得积分奖励
*/
private Integer inviteAwardScore;
/**
* 活动最小领取彩金
*/
private BigDecimal minReceiveMoneyThreshold;
/**
* 抽奖条件(首充金额)
*/
private Double firstRechargeAmountLimit;
}

View File

@@ -0,0 +1,20 @@
package project.mall.activity.rule;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class SimpleLotteryActivityConfig extends BaseActivityConfig {
/**
* 默认多少积分兑换一次抽奖
*/
private Integer scoreExchangeLotteryTimeRatio;
/**
* 活动最小领取彩金
*/
private BigDecimal minReceiveMoneyThreshold;
}

View File

@@ -0,0 +1,14 @@
package project.mall.activity.rule.award;
import lombok.Data;
/**
* 活动奖励规则
*/
@Data
public class BaseActivityAwardRule {
public String showTitle() {
return "-";
}
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.rule.award;
import lombok.Data;
/**
* demo活动奖励规则
*/
@Data
public class DemoActivityAwardRule extends BaseActivityAwardRule {
@Override
public String showTitle() {
return "奖励彩金 100 USDT";
}
}

View File

@@ -0,0 +1,13 @@
package project.mall.activity.rule.join;
import lombok.Data;
/**
* demo活动参加规则
*/
@Data
public class BaseActivityJoinRule {
public String showTitle() {
return "-";
}
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.rule.join;
import lombok.Data;
/**
* demo活动参加规则
*/
@Data
public class DemoActivityJoinRule extends BaseActivityJoinRule {
@Override
public String showTitle() {
return "用户活动积分满 10 分";
}
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.service;
import kernel.web.Page;
import project.mall.activity.model.ActivityConfigLog;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityPrize;
import java.util.List;
public interface ActivityConfigLogService {
ActivityConfigLog saveLog(ActivityLibrary activityEntity, List<ActivityPrize> prizeList);
ActivityConfigLog getLastLog(String activityId);
}

View File

@@ -0,0 +1,10 @@
//package project.mall.activity.service;
//
//import project.mall.activity.model.ActivityTemplate;
//
//import java.util.List;
//
//public interface ActivityHandleStatService {
//
//
//}

View File

@@ -0,0 +1,67 @@
package project.mall.activity.service;
import kernel.web.Page;
import project.mall.activity.model.ActivityLibrary;
import java.util.List;
public interface ActivityLibraryService {
/**
* 新增或者修改活动
*
* @param activityEntity
* @return
*/
String saveActivity(ActivityLibrary activityEntity);
/**
* 物理删除
*
* @param id
*/
void delete(String id);
/**
* 逻辑删除
*
* @param id
*/
void deleteLogic(String id);
ActivityLibrary findById(String id);
/**
* 单例类型的活动
*
* @param templateId
* @return
*/
ActivityLibrary findByTemplate(String templateId);
/**
* 单例类型的活动
*
* @param activityType
* @return
*/
ActivityLibrary findByType(String activityType);
Page listActivity(String name, Integer state, String fromTime, String toTime, int pageNum, int pageSize);
List<ActivityLibrary> listRunningActivity();
// ActivityLibrary getByCode(String activityCode);
List<ActivityLibrary> getShowActivity(String type);
void updateShow(String id, int show);
/**
* 更新首冲抽奖活动详情地址
*
* @param id
* @return
*/
String updateLotteryActivityUrl(String id);
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityPrizeLog;
import java.util.List;
public interface ActivityPrizeLogService {
ActivityPrizeLog saveLogBy(ActivityPrize prize, String activityLogId);
List<ActivityPrizeLog> listByActivityId(String activityId, int status);
}

View File

@@ -0,0 +1,30 @@
package project.mall.activity.service;
import kernel.web.Page;
import project.mall.activity.model.ActivityPrizePool;
import java.util.List;
public interface ActivityPrizePoolService {
void save(ActivityPrizePool prizePool);
void delete(String id);
void deleteLogic(String id);
void update(ActivityPrizePool prizePool, String lang);
ActivityPrizePool detail(String id);
List<ActivityPrizePool> listAll();
Page listPrize(String prizeName, int prizeType, int status, String startTime, String endTime, int pageNum, int pageSize);
List<ActivityPrizePool> listLotteryPrize(int size);
List<ActivityPrizePool> listByIds(List<String> ids);
List<ActivityPrizePool> listDefaltPrize(int status, int size);
}

View File

@@ -0,0 +1,46 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityPrizePool;
import java.util.List;
public interface ActivityPrizeService {
void save(ActivityPrize prize);
ActivityPrize getById(String ids);
List<ActivityPrize> listByIds(List<String> ids);
List<ActivityPrize> listByActivityId(String activityId, int status);
List<ActivityPrize> listAll();
List<ActivityPrize> listDefaltPrize(String activityId);
/**
* 物理删除
*
* @param activityId
* @return
*/
int deleteActivityPrize(String activityId);
/**
* 逻辑删除
*
* @param activityId
* @return
*/
int deleteActivityPrizeLogic(String activityId);
/**
* 同步奖品信息变更
*
* @param newPrizeInfo
* @return
*/
int updateSyncAttrs(ActivityPrizePool newPrizeInfo);
}

View File

@@ -0,0 +1,15 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityTemplate;
import java.util.List;
public interface ActivityTemplateService {
List<ActivityTemplate> listAllValidActivityType();
ActivityTemplate getById(String id);
}

View File

@@ -0,0 +1,13 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.model.ActivityUserJoinLog;
public interface ActivityUserJoinLogService {
ActivityUserJoinLog save(ActivityUserJoinLog entity);
ActivityUserJoinLog lastJoinLog(String activityId, String userId, String eventType);
}

View File

@@ -0,0 +1,10 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityUserPointsLog;
public interface ActivityUserPointsLogService {
ActivityUserPointsLog saveLog(String partyId, int accPoints, String createBy, String refType, String refId);
}

View File

@@ -0,0 +1,22 @@
package project.mall.activity.service;
import project.mall.activity.model.lottery.ActivityUserPoints;
import java.util.List;
public interface ActivityUserPointsService {
ActivityUserPoints getById(String id);
ActivityUserPoints saveOrGetUserPoints(String activityType, String activityId, String userId);
void add(ActivityUserPoints points);
void update(ActivityUserPoints points);
ActivityUserPoints getByActivityId(String activityId, String partyId);
List<ActivityUserPoints> getByActivityId(String activityId, String activityType, List<String> partyIdList);
void updatePoints(String id, int incr);
}

View File

@@ -0,0 +1,29 @@
package project.mall.activity.service;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityUser;
import java.util.List;
public interface ActivityUserService {
ActivityUser saveOrGetActivityUser(String activityType, String activityId, String userId, String actionType);
ActivityUser save(ActivityUser entity);
/**
* 统计活动期间充值人数,推荐人ID信息
*
* @param activityLibrary
* @param recommendUserIds
* @return
*/
Integer count(ActivityLibrary activityLibrary, List<String> recommendUserIds);
ActivityUser getActivityUser(String activityId, String userId, String actionType);
void updateAllowJoinTimes(String id, int incr);
// void updateJoinTimes(String id, int incr);
}

View File

@@ -0,0 +1,76 @@
package project.mall.activity.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Property;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityConfigLog;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.service.ActivityConfigLogService;
import project.mall.activity.service.ActivityPrizeLogService;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
public class ActivityConfigLogServiceImpl extends HibernateDaoSupport implements ActivityConfigLogService {
@Resource
private ActivityPrizeLogService activityPrizeLogService;
@Override
public ActivityConfigLog saveLog(ActivityLibrary activityEntity, List<ActivityPrize> prizeList) {
if (activityEntity == null
|| activityEntity.getId() == null
|| StrUtil.isBlank(activityEntity.getId().toString())) {
return null;
}
Date now = new Date();
ActivityConfigLog log = new ActivityConfigLog();
BeanUtil.copyProperties(activityEntity, log);
log.setId(null);
log.setActivityId(activityEntity.getId().toString());
log.setLogTime(now);
this.getHibernateTemplate().save(log);
if (CollectionUtil.isNotEmpty(prizeList)) {
for (ActivityPrize prize : prizeList) {
activityPrizeLogService.saveLogBy(prize, log.getId().toString());
}
}
return log;
}
@Override
public ActivityConfigLog getLastLog(String activityId) {
if (StrUtil.isBlank(activityId)) {
return null;
}
DetachedCriteria query = DetachedCriteria.forClass(ActivityConfigLog.class);
query.add(Property.forName("activityId").eq(activityId));
query.addOrder(Order.desc("logTime"));
List<ActivityConfigLog> list = (List<ActivityConfigLog>)getHibernateTemplate().findByCriteria(query, 1, 1);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
public void setActivityPrizeLogService(ActivityPrizeLogService prizeLogService) {
this.activityPrizeLogService = prizeLogService;
}
}

View File

@@ -0,0 +1,19 @@
//package project.mall.activity.service.impl;
//
//import kernel.util.StringUtils;
//import kernel.web.Page;
//import org.hibernate.criterion.DetachedCriteria;
//import org.hibernate.criterion.Order;
//import org.hibernate.criterion.Projections;
//import org.hibernate.criterion.Property;
//import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
//import project.mall.activity.model.ActivityTemplate;
//import project.mall.activity.service.ActivityHandleStatService;
//import project.mall.lottery.model.Lottery;
//
//import java.util.List;
//import java.util.Objects;
//
//public class ActivityHandleStatServiceImpl extends HibernateDaoSupport implements ActivityHandleStatService {
//
//}

View File

@@ -0,0 +1,232 @@
package project.mall.activity.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.StringUtils;
import kernel.web.Page;
import org.apache.commons.collections.CollectionUtils;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.service.ActivityLibraryService;
import project.mall.auto.AutoConfig;
import java.util.Date;
import java.util.List;
import java.util.Objects;
//import project.mall.activity.model.lottery.Lottery;
//import project.mall.activity.model.lottery.LotteryInfoPrize;
//import project.mall.activity.model.lottery.LotteryPrize;
public class ActivityLibraryServiceImpl extends HibernateDaoSupport implements ActivityLibraryService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public String saveActivity(ActivityLibrary activityEntity) {
Date now = new Date();
if (activityEntity.getCreateTime() == null) {
activityEntity.setCreateTime(now);
}
activityEntity.setUpdateTime(now);
activityEntity.setDeleted(0);
if (activityEntity.getId() == null
|| StrUtil.isBlank(activityEntity.getId().toString())
|| Objects.equals(activityEntity.getId().toString(), "0")) {
activityEntity.setId(null);
this.getHibernateTemplate().save(activityEntity);
} else {
this.getHibernateTemplate().update(activityEntity);
}
return activityEntity.getId().toString();
}
@Override
public void delete(String id) {
ActivityLibrary lottery = this.getHibernateTemplate().get(ActivityLibrary.class, id);
if (Objects.isNull(lottery)) {
throw new BusinessException("活动记录不存在");
}
getHibernateTemplate().delete(lottery);
}
@Override
public void deleteLogic(String id) {
ActivityLibrary lottery = this.getHibernateTemplate().get(ActivityLibrary.class, id);
if (Objects.isNull(lottery)) {
throw new BusinessException("活动记录不存在");
}
lottery.setDeleted(1);
getHibernateTemplate().update(lottery);
}
@Override
public ActivityLibrary findById(String id) {
return getHibernateTemplate().get(ActivityLibrary.class, id);
}
@Override
public ActivityLibrary findByTemplate(String templateId) {
if (StrUtil.isBlank(templateId)) {
throw new BusinessException("缺失 templateId 参数");
}
DetachedCriteria query = DetachedCriteria.forClass(ActivityLibrary.class);
query.add(Property.forName("templateId").eq(templateId));
query.add(Property.forName("deleted").eq(0));
List<ActivityLibrary> list = (List<ActivityLibrary>) getHibernateTemplate().findByCriteria(query);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
@Override
public ActivityLibrary findByType(String activityType) {
if (StrUtil.isBlank(activityType)) {
throw new BusinessException("缺失 activityType 参数");
}
DetachedCriteria query = DetachedCriteria.forClass(ActivityLibrary.class);
query.add(Property.forName("type").eq(activityType));
query.add(Property.forName("deleted").eq(0));
List<ActivityLibrary> list = (List<ActivityLibrary>) getHibernateTemplate().findByCriteria(query);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
@Override
public List<ActivityLibrary> getShowActivity(String type) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityLibrary.class);
query.add(Property.forName("isShow").eq(1));
query.add(Property.forName("type").eq(type));
query.add(Property.forName("deleted").eq(0));
List<ActivityLibrary> list = (List<ActivityLibrary>) getHibernateTemplate().findByCriteria(query);
return list;
}
@Override
public void updateShow(String id, int show) {
ActivityLibrary library = (ActivityLibrary) getHibernateTemplate().get(ActivityLibrary.class, id);
if (library == null) {
throw new BusinessException("活动不存在" + id);
}
if (show != 0 && show != 1) {
throw new BusinessException("请设置正确的状态值");
}
library.setIsShow(show);
getHibernateTemplate().update(library);
}
@Override
public Page listActivity(String title, Integer status, String startTime, String endTime, int pageNum, int pageSize) {
Page page = new Page();
DetachedCriteria query = DetachedCriteria.forClass(ActivityLibrary.class);
query.add(Property.forName("deleted").eq(0));
if (Objects.nonNull(status)) {
query.add(Property.forName("status").eq(status));
}
if (StringUtils.isNotEmpty(title)) {
Disjunction titleOr = Restrictions.disjunction();
titleOr.add(Restrictions.like("titleCn", title.trim(), MatchMode.ANYWHERE));
titleOr.add(Restrictions.like("titleEn", title.trim(), MatchMode.ANYWHERE));
query.add(titleOr);
}
if (StringUtils.isNotEmpty(startTime)) {
// 此处不能填字符串类的日期值,必须是 Date 类型的
Date time = DateUtil.parseDateTime(startTime);
query.add(Property.forName("startTime").gt(time));
}
if (StringUtils.isNotEmpty(endTime)) {
// 此处不能填字符串类的日期值,必须是 Date 类型的
Date time = DateUtil.parseDateTime(endTime);
query.add(Property.forName("startTime").lt(time));
}
query.addOrder(Order.desc("createTime"));
// 查询总条数
Long totalCount = (Long) query.setProjection(Projections.rowCount()).getExecutableCriteria(this.getHibernateTemplate().getSessionFactory().getCurrentSession()).uniqueResult();
query.setProjection(null);
List<?> resultList = getHibernateTemplate().findByCriteria(query, (pageNum - 1) * pageSize, pageSize);
page.setElements(resultList);
page.setThisPageNumber(pageNum);
page.setTotalElements(totalCount.intValue());
page.setPageSize(pageSize);
return page;
}
@Override
public List<ActivityLibrary> listRunningActivity() {
Date now = new Date();
DetachedCriteria query = DetachedCriteria.forClass(ActivityLibrary.class);
query.add(Property.forName("status").eq(1));
query.add(Property.forName("startTime").le(now));
query.add(Property.forName("endTime").gt(now));
query.add(Property.forName("deleted").eq(0));
query.addOrder(Order.asc("startTime"));
List<ActivityLibrary> list = (List<ActivityLibrary>) getHibernateTemplate().findByCriteria(query);
return list;
}
@Override
public String updateLotteryActivityUrl(String id) {
ActivityLibrary entity = findById(id);
if (entity == null) {
return null;
}
if (StrUtil.isNotBlank(entity.getDetailUrl())) {
return entity.getDetailUrl();
}
String baseDomain = AutoConfig.attribute("dm_url");
if (StrUtil.isBlank(baseDomain)) {
logger.error("[ActivityLibraryServiceImpl updateActivityUrl] 未读取到当前应用的基础域名,无法组装活动详情地址");
return null;
}
baseDomain = baseDomain.trim();
if (!baseDomain.endsWith("/")) {
baseDomain = baseDomain + "/";
}
String detailUrl = baseDomain + "www/#/activity/turntable?id=" + id;
entity.setDetailUrl(detailUrl);
this.getHibernateTemplate().update(entity);
return detailUrl;
}
}

View File

@@ -0,0 +1,52 @@
package project.mall.activity.service.impl;
import cn.hutool.core.bean.BeanUtil;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityPrizeLog;
import project.mall.activity.service.ActivityPrizeLogService;
import java.util.Date;
import java.util.List;
public class ActivityPrizeLogServiceImpl extends HibernateDaoSupport implements ActivityPrizeLogService {
@Override
public ActivityPrizeLog saveLogBy(ActivityPrize prize, String activityLogId) {
if (prize == null) {
return null;
}
Date now = new Date();
ActivityPrizeLog log = new ActivityPrizeLog();
BeanUtil.copyProperties(prize, log);
log.setId(null);
log.setActivityLogId(activityLogId);
log.setLogTime(now);
getHibernateTemplate().save(log);
return log;
}
/**
*
* @param activityLogId
* @param status : 该奖项状态0-不可用1-可用
* @return
*/
@Override
public List<ActivityPrizeLog> listByActivityId(String activityLogId, int status) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizeLog.class);
query.add(Property.forName("activityLogId").eq(activityLogId));
if (status >= 0) {
query.add(Property.forName("status").eq(status));
}
List<ActivityPrizeLog> results = (List<ActivityPrizeLog>) getHibernateTemplate().findByCriteria(query);
return results;
}
}

View File

@@ -0,0 +1,191 @@
package project.mall.activity.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import kernel.util.StringUtils;
import kernel.web.Page;
import org.hibernate.criterion.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityPrizePool;
import project.mall.activity.service.ActivityPrizePoolService;
import java.util.*;
public class ActivityPrizePoolServiceImpl extends HibernateDaoSupport implements ActivityPrizePoolService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void save(ActivityPrizePool prizePool) {
prizePool.setUpdateTime(new Date());
if (prizePool.getId() == null
|| StrUtil.isBlank(prizePool.getId().toString())
|| Objects.equals(prizePool.getId().toString(), "0")) {
if (prizePool.getCreateTime() == null) {
prizePool.setCreateTime(new Date());
}
prizePool.setId(null);
prizePool.setDeleted(0);
getHibernateTemplate().save(prizePool);
} else {
getHibernateTemplate().update(prizePool);
}
}
@Override
public void delete(String id) {
ActivityPrizePool prizePool = this.getHibernateTemplate().get(ActivityPrizePool.class, id);
if (Objects.isNull(prizePool)) {
//throw new BusinessException("记录不存在");
return;
}
getHibernateTemplate().delete(prizePool);
}
@Override
public void deleteLogic(String id) {
ActivityPrizePool prizePool = this.getHibernateTemplate().get(ActivityPrizePool.class, id);
if (Objects.isNull(prizePool)) {
//throw new BusinessException("记录不存在");
return;
}
prizePool.setDeleted(1);
prizePool.setStatus(0);
getHibernateTemplate().update(prizePool);
}
@Override
public void update(ActivityPrizePool prizePool, String lang) {
prizePool.setUpdateTime(new Date());
// if (lang.equals(LanguageEnum.CN.getLang())) {
// prizePool.setPrizeNameCn(lotteryPrize.getPrizeNameCn());
// } else {
// prizePool.setPrizeNameEn(lotteryPrize.getPrizeNameEn());
// }
this.getHibernateTemplate().save(prizePool);
}
@Override
public ActivityPrizePool detail(String id) {
return this.getHibernateTemplate().get(ActivityPrizePool.class, id);
}
@Override
public List<ActivityPrizePool> listAll() {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizePool.class);
query.add(Property.forName("deleted").eq(0));
return (List<ActivityPrizePool>) getHibernateTemplate().findByCriteria(query);
}
@Override
public Page listPrize(String prizeName, int prizeType, int status, String startTime, String endTime, int pageNum, int pageSize) {
Page page = new Page();
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizePool.class);
query.add(Property.forName("deleted").eq(0));
if (prizeType > 0) {
query.add(Property.forName("prizeType").eq(prizeType));
}
if (status >= 0) {
query.add(Property.forName("status").eq(status));
}
if (StringUtils.isNotEmpty(prizeName)) {
Disjunction titleOr = Restrictions.disjunction();
titleOr.add(Restrictions.like("prizeNameCn", prizeName.trim(), MatchMode.ANYWHERE));
titleOr.add(Restrictions.like("prizeNameEn", prizeName.trim(), MatchMode.ANYWHERE));
query.add(titleOr);
}
if (StringUtils.isNotEmpty(startTime)) {
// 此处不能填字符串类的日期值,必须是 Date 类型的
Date time = DateUtil.parseDateTime(startTime);
query.add(Property.forName("createTime").gt(time));
}
if (StringUtils.isNotEmpty(endTime)) {
// 此处不能填字符串类的日期值,必须是 Date 类型的
Date time = DateUtil.parseDateTime(endTime);
query.add(Property.forName("createTime").lt(time));
}
query.addOrder(Order.desc("createTime"));
// 查询总条数
Long totalCount = (Long) query.setProjection(Projections.rowCount()).getExecutableCriteria(this.getHibernateTemplate().getSessionFactory().getCurrentSession()).uniqueResult();
query.setProjection(null);
List<?> resultList = getHibernateTemplate().findByCriteria(query, (pageNum - 1) * pageSize, pageSize);
page.setThisPageNumber(pageNum);
page.setTotalElements(totalCount.intValue());
page.setElements(resultList);
page.setPageSize(pageSize);
return page;
}
/**
* 过滤掉:谢谢惠顾类型的奖品
*
* @param size
* @return
*/
@Override
public List<ActivityPrizePool> listLotteryPrize(int size) {
if (size <= 0) {
size = 1;
}
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizePool.class);
query.add(Property.forName("prizeType").in(1, 2));
query.add(Property.forName("status").eq(1));
query.add(Property.forName("deleted").eq(0));
query.addOrder(Order.desc("createTime"));
List<ActivityPrizePool> resultList = (List<ActivityPrizePool>) getHibernateTemplate().findByCriteria(query, 0, size);
return resultList;
}
@Override
public List<ActivityPrizePool> listByIds(List<String> ids) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizePool.class);
query.add(Property.forName("id").in(ids));
List<ActivityPrizePool> results = (List<ActivityPrizePool>) getHibernateTemplate().findByCriteria(query);
return results;
}
@Override
public List<ActivityPrizePool> listDefaltPrize(int status, int size) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrizePool.class);
query.add(Property.forName("deleted").eq(0));
if (status >= 0) {
query.add(Property.forName("status").eq(1));
}
// 谢谢惠顾类型的奖品
query.add(Property.forName("prizeType").eq(3));
List<ActivityPrizePool> results = null;
if (size <= 0) {
results = (List<ActivityPrizePool>) getHibernateTemplate().findByCriteria(query);
} else {
results = (List<ActivityPrizePool>) getHibernateTemplate().findByCriteria(query, 0, size);
}
return results;
}
}

View File

@@ -0,0 +1,199 @@
package project.mall.activity.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.hibernate.query.NativeQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.ActivityPrizePool;
import project.mall.activity.service.ActivityLibraryService;
import project.mall.activity.service.ActivityPrizePoolService;
import project.mall.activity.service.ActivityPrizeService;
import project.mall.activity.service.lottery.LotteryRecordService;
import project.redis.RedisHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class ActivityPrizeServiceImpl extends HibernateDaoSupport implements ActivityPrizeService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private JdbcTemplate jdbcTemplate;
private ActivityPrizePoolService activityPrizePoolService;
private LotteryRecordService lotteryRecordService;
private ActivityLibraryService activityLibraryService;
private RedisHandler redisHandler;
@Override
public void save(ActivityPrize prize) {
Date now = new Date();
if (prize.getCreateTime() == null) {
prize.setCreateTime(now);
}
prize.setUpdateTime(now);
if (prize.getId() == null
|| StrUtil.isBlank(prize.getId().toString())
|| Objects.equals(prize.getId().toString(), "0")) {
prize.setId(null);
prize.setDeleted(0);
getHibernateTemplate().save(prize);
} else {
getHibernateTemplate().update(prize);
}
}
@Override
public ActivityPrize getById(String id) {
return this.getHibernateTemplate().get(ActivityPrize.class, id);
}
@Override
public List<ActivityPrize> listByIds(List<String> ids) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrize.class);
query.add(Property.forName("id").in(ids));
List<ActivityPrize> results = (List<ActivityPrize>) getHibernateTemplate().findByCriteria(query);
return results;
}
/**
* @param activityId
* @param status : 该奖项状态0-不可用1-可用
* @return
*/
@Override
public List<ActivityPrize> listByActivityId(String activityId, int status) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrize.class);
query.add(Property.forName("activityId").eq(activityId));
if (status >= 0) {
query.add(Property.forName("status").eq(status));
}
List<ActivityPrize> results = (List<ActivityPrize>) getHibernateTemplate().findByCriteria(query);
return results;
}
@Override
public List<ActivityPrize> listDefaltPrize(String activityId) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityPrize.class);
query.add(Property.forName("activityId").eq(activityId));
query.add(Property.forName("status").eq(1));
//query.add(Property.forName("defaultPrize").eq(1));
// 谢谢惠顾类型的奖品
query.add(Property.forName("prizeType").eq(3));
query.add(Property.forName("deleted").eq(0));
List<ActivityPrize> results = (List<ActivityPrize>) getHibernateTemplate().findByCriteria(query);
return results;
}
@Override
public int deleteActivityPrize(String activityId) {
if (StrUtil.isBlank(activityId)) {
return 0;
}
List<ActivityPrize> list = listByActivityId(activityId, -1);
if (CollectionUtil.isEmpty(list)) {
return 0;
}
for (ActivityPrize one : list) {
getHibernateTemplate().delete(one);
}
return list.size();
}
@Override
public int deleteActivityPrizeLogic(String activityId) {
if (StrUtil.isBlank(activityId)) {
return 0;
}
List<ActivityPrize> list = listByActivityId(activityId, -1);
if (CollectionUtil.isEmpty(list)) {
return 0;
}
for (ActivityPrize one : list) {
one.setDeleted(1);
getHibernateTemplate().update(one);
}
return list.size();
}
public int updateSyncAttrs(ActivityPrizePool newPrizeInfo) {
if (newPrizeInfo == null
|| newPrizeInfo.getId() == null
|| StrUtil.isBlank(newPrizeInfo.getId().toString())) {
return 0;
}
Session currentSession = getHibernateTemplate().getSessionFactory().getCurrentSession();
String sql = "update ACTIVITY_PRIZE set PRIZE_NAME_CN= :prizeNameCn, PRIZE_NAME_EN= :prizeNameEn," +
"PRIZE_TYPE= :prizeType, PRIZE_AMOUNT= :prizeAmount, IMAGE= :image, REMARK= :remark " +
"where POOL_ID= :poolId ";
NativeQuery query = currentSession.createSQLQuery(sql);
query.setParameter("prizeNameCn", newPrizeInfo.getPrizeNameCn() == null ? "" : newPrizeInfo.getPrizeNameCn().trim());
query.setParameter("prizeNameEn", newPrizeInfo.getPrizeNameEn() == null ? "" : newPrizeInfo.getPrizeNameEn().trim());
query.setParameter("prizeType", newPrizeInfo.getPrizeType());
query.setParameter("prizeAmount", newPrizeInfo.getPrizeAmount());
query.setParameter("image", newPrizeInfo.getImage() == null ? "" : newPrizeInfo.getImage().trim());
query.setParameter("remark", newPrizeInfo.getRemark() == null ? "" : newPrizeInfo.getRemark().trim());
return query.executeUpdate();
}
// 获取2个值之间的随机数
private static long randomNum(int smin, int smax) {
int range = smax - smin;
double rand = Math.random();
return (smin + Math.round(rand * range));
}
@Override
public List<ActivityPrize> listAll() {
return getHibernateTemplate().loadAll(ActivityPrize.class);
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setRedisHandler(RedisHandler redisHandler) {
this.redisHandler = redisHandler;
}
public void setLotteryRecordService(LotteryRecordService lotteryRecordService) {
this.lotteryRecordService = lotteryRecordService;
}
public void setActivityLibraryService(ActivityLibraryService activityService) {
this.activityLibraryService = activityService;
}
public void setActivityPrizePoolService(ActivityPrizePoolService activityPrizePoolService) {
this.activityPrizePoolService = activityPrizePoolService;
}
}

View File

@@ -0,0 +1,36 @@
package project.mall.activity.service.impl;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityPrizePool;
import project.mall.activity.model.ActivityTemplate;
import project.mall.activity.service.ActivityTemplateService;
import java.util.List;
public class ActivityTemplateServiceImpl extends HibernateDaoSupport implements ActivityTemplateService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public List<ActivityTemplate> listAllValidActivityType() {
DetachedCriteria query = DetachedCriteria.forClass(ActivityTemplate.class);
query.add(Property.forName("status").eq(1));
query.add(Property.forName("deleted").eq(0));
query.addOrder(Order.desc("createTime"));
List<ActivityTemplate> resultList = (List<ActivityTemplate>)getHibernateTemplate().findByCriteria(query);
return resultList;
}
@Override
public ActivityTemplate getById(String id) {
return this.getHibernateTemplate().get(ActivityTemplate.class, id);
}
}

View File

@@ -0,0 +1,59 @@
package project.mall.activity.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Property;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.model.ActivityUserJoinLog;
import project.mall.activity.service.ActivityUserJoinLogService;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Slf4j
public class ActivityUserJoinLogServiceImpl extends HibernateDaoSupport implements ActivityUserJoinLogService {
@Override
public ActivityUserJoinLog save(ActivityUserJoinLog entity) {
Date now = new Date();
if (entity.getCreateTime() == null) {
entity.setCreateTime(now);
}
entity.setUpdateTime(now);
if (entity.getId() == null
|| StrUtil.isBlank(entity.getId().toString())
|| Objects.equals(entity.getId().toString(), "0")) {
entity.setId(null);
entity.setDeleted(0);
getHibernateTemplate().save(entity);
} else {
getHibernateTemplate().update(entity);
}
return entity;
}
@Override
public ActivityUserJoinLog lastJoinLog(String activityId, String userId, String eventType) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityUserJoinLog.class);
query.add(Property.forName("activityId").eq(activityId));
query.add(Property.forName("userId").eq(userId));
if (StrUtil.isNotBlank(eventType)) {
query.add(Property.forName("eventType").eq(eventType));
}
query.addOrder(Order.desc("triggerTime"));
List<ActivityUserJoinLog> list = (List<ActivityUserJoinLog>) getHibernateTemplate().findByCriteria(query);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
}

View File

@@ -0,0 +1,36 @@
package project.mall.activity.service.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityUserPointsLog;
import project.mall.activity.service.ActivityUserPointsLogService;
import java.util.Date;
public class ActivityUserPointsLogServiceImpl extends HibernateDaoSupport implements ActivityUserPointsLogService {
private JdbcTemplate jdbcTemplate;
@Override
public ActivityUserPointsLog saveLog(String partyId, int accPoints, String createBy, String refType, String refId) {
Date now = new Date();
ActivityUserPointsLog points = new ActivityUserPointsLog();
points.setId(null);
points.setPartyId(partyId);
points.setPoints(accPoints);
points.setEntityVersion(1);
points.setCreateTime(now);
points.setRefType(refType);
points.setRefId(refId);
points.setDeleted(0);
points.setCreateBy(createBy);
getHibernateTemplate().save(points);
return points;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}

View File

@@ -0,0 +1,126 @@
package project.mall.activity.service.impl;
import kernel.exception.BusinessException;
import org.apache.commons.collections.CollectionUtils;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.model.ActivityPrize;
import project.mall.activity.model.lottery.ActivityUserPoints;
import project.mall.activity.service.ActivityUserPointsService;
import project.wallet.consumer.WalletExtendMessage;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class ActivityUserPointsServiceImpl extends HibernateDaoSupport implements ActivityUserPointsService {
private JdbcTemplate jdbcTemplate;
@Override
public ActivityUserPoints getById(String id) {
return this.getHibernateTemplate().get(ActivityUserPoints.class, id);
}
@Override
public ActivityUserPoints saveOrGetUserPoints(String activityType, String activityId, String userId) {
ActivityUserPoints existRecord = getByActivityId(activityId, userId);
if (existRecord != null) {
return existRecord;
}
try {
Date now = new Date();
ActivityUserPoints points = new ActivityUserPoints();
points.setActivityType(activityType);
points.setActivityId(activityId);
points.setPartyId(userId);
points.setPoints(0);
points.setEntityVersion(1);
points.setCreateTime(now);
points.setUpdateTime(now);
points.setDeleted(0);
getHibernateTemplate().save(points);
getHibernateTemplate().flush();
return points;
} catch (Exception e) {
// 并发场景下的重复创建防止 TODO
throw new BusinessException(e);
}
}
@Override
public void add(ActivityUserPoints points) {
ActivityUserPoints userPoints = getByActivityId(points.getActivityId(), points.getPartyId());
if (Objects.isNull(userPoints)) {
getHibernateTemplate().save(points);
}
}
@Override
public void update(ActivityUserPoints points) {
getHibernateTemplate().update(points);
}
@Override
public ActivityUserPoints getByActivityId(String activityId, String partyId) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityUserPoints.class);
query.add(Property.forName("activityId").eq(activityId));
query.add(Property.forName("partyId").eq(partyId));
query.add(Property.forName("deleted").eq(0));
List<ActivityUserPoints> points = (List<ActivityUserPoints>) getHibernateTemplate().findByCriteria(query);
ActivityUserPoints dto = null;
if (CollectionUtils.isNotEmpty(points)) {
dto = points.get(0);
}
return dto;
}
@Override
public List<ActivityUserPoints> getByActivityId(String activityId, String activityType, List<String> partyIdList) {
List<ActivityUserPoints> pointsList = new ArrayList<>();
if (CollectionUtils.isEmpty(partyIdList)) {
return pointsList;
}
DetachedCriteria query = DetachedCriteria.forClass(ActivityUserPoints.class);
query.add(Property.forName("activityId").eq(activityId));
query.add(Property.forName("partyId").in(partyIdList));
query.add(Property.forName("deleted").eq(0));
pointsList = (List<ActivityUserPoints>) getHibernateTemplate().findByCriteria(query);
return pointsList;
}
@Override
public void updatePoints(String id, int incr) {
String sql = "UPDATE ACTIVITY_USER_POINTS SET POINTS=POINTS+? WHERE UUID=? ";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setDouble(1, incr);
ps.setString(2, id);
}
@Override
public int getBatchSize() {
return 1;
}
});
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}

View File

@@ -0,0 +1,205 @@
package project.mall.activity.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import kernel.exception.BusinessException;
import org.hibernate.HibernateException;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.HibernateCallback;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import project.mall.activity.handler.FirstRechargeFruitDialActivityHandler;
import project.mall.activity.model.ActivityLibrary;
import project.mall.activity.model.ActivityUser;
import project.mall.activity.service.ActivityUserService;
import project.party.PartyService;
import project.party.model.Party;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class ActivityUserServiceImpl extends HibernateDaoSupport implements ActivityUserService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private JdbcTemplate jdbcTemplate;
private PartyService partyService;
@Override
public ActivityUser saveOrGetActivityUser(String activityType, String activityId, String userId, String actionType) {
ActivityUser activityUser = getActivityUser(activityId, userId, actionType);
if (activityUser != null) {
return activityUser;
}
try {
Date now = new Date();
activityUser = new ActivityUser();
activityUser.setActivityId(activityId);
activityUser.setActivityType(activityType);
activityUser.setUserId(userId);
activityUser.setLastTriggerTime(0L);
activityUser.setStatus(0);
activityUser.setAllowJoinTimes(0);
activityUser.setJoinTimes(0);
activityUser.setValidEndTime(null);
activityUser.setValidBeginTime(null);
activityUser.setTriggerType(actionType);
activityUser.setFirstTriggerTime(0L);
activityUser.setCreateTime(now);
activityUser.setUpdateTime(now);
activityUser.setUserType(0);
// 此处不填充,则在查询时返回的是 null
activityUser.setUserRegistTime(0L);
activityUser.setDeleted(0);
getHibernateTemplate().save(activityUser);
getHibernateTemplate().flush();
return activityUser;
} catch (Exception e) {
// 并发场景下的重复创建防止 TODO
throw new BusinessException(e);
}
}
@Override
public ActivityUser save(ActivityUser entity) {
Date now = new Date();
if (entity.getCreateTime() == null) {
entity.setCreateTime(now);
}
entity.setUpdateTime(now);
if (entity.getId() == null
|| StrUtil.isBlank(entity.getId().toString())
|| Objects.equals(entity.getId().toString(), "0")) {
entity.setId(null);
getHibernateTemplate().save(entity);
} else {
getHibernateTemplate().update(entity);
}
return entity;
}
@Override
public Integer count(ActivityLibrary activityLibrary, List<String> recommendUserIds) {
Integer result = 0;
DetachedCriteria query = DetachedCriteria.forClass(ActivityUser.class);
query.add(Property.forName("triggerType").eq(FirstRechargeFruitDialActivityHandler.ActivityTouchEventTypeEnum.USER_RECHARGE.getEventType()));
query.add(Property.forName("activityId").eq(activityLibrary.getId()));
query.add(Property.forName("userId").in(recommendUserIds));
query.add(Property.forName("deleted").eq(0));
List<ActivityUser> results = (List<ActivityUser>) getHibernateTemplate().findByCriteria(query);
if (CollectionUtil.isNotEmpty(results)) {
Integer count = 0;
for (ActivityUser activityUser : results) {
Party party = partyService.cachePartyBy(activityUser.getUserId(), true);
if (Objects.nonNull(party)) {
if (party.getCreateTime().after(activityLibrary.getStartTime()) && party.getCreateTime().before(activityLibrary.getEndTime())) {
count++;
}
}
}
result = count;
}
return result;
}
public ActivityUser getActivityUser(String activityId, String userId, String actionType) {
DetachedCriteria query = DetachedCriteria.forClass(ActivityUser.class);
query.add(Property.forName("activityId").eq(activityId));
query.add(Property.forName("userId").eq(userId));
query.add(Property.forName("deleted").eq(0));
if (StrUtil.isNotBlank(actionType)) {
query.add(Property.forName("triggerType").eq(actionType));
}
List<ActivityUser> list = (List<ActivityUser>) getHibernateTemplate().findByCriteria(query);
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.get(0);
}
@Override
public synchronized void updateAllowJoinTimes(String id, final int incr) {
// String sql = "UPDATE ACTIVITY_USER SET ALLOW_JOIN_TIMES=ALLOW_JOIN_TIMES+? WHERE UUID=? ";
// getHibernateTemplate().execute(new HibernateCallback() {
// public Object doInHibernate(Session session) throws HibernateException {
// SQLQuery query = session.createSQLQuery(sql);
// query.setParameter(1, incr);
// query.setParameter(2, id);
// return query.executeUpdate();
// }
// });
// getHibernateTemplate().flush();
// int[] effectRows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
// @Override
// public void setValues(PreparedStatement ps, int i) throws SQLException {
// ps.setInt(1, incr);
// ps.setString(2, id);
// }
//
// @Override
// public int getBatchSize() {
// return 1;
// }
// });
// System.out.println("-----> effectRows:" + effectRows);
// 经各种测试发现以上方式不能更新数据,将使用统一锁的模式来处理,还是存在并发问题(其他业务更新全量 ActivityUser 对象时还是可能存在值覆盖的情况)
ActivityUser activityUser = getHibernateTemplate().get(ActivityUser.class, id);
if (activityUser == null) {
return;
}
Date now = new Date();
activityUser.setAllowJoinTimes(activityUser.getAllowJoinTimes() + incr);
activityUser.setUpdateTime(now);
getHibernateTemplate().update(activityUser);
getHibernateTemplate().flush();
activityUser = getHibernateTemplate().get(ActivityUser.class, id);
System.out.println("----> newUser.allowJoinTimes:" + activityUser.getAllowJoinTimes());
}
// @Override
// public void updateJoinTimes(String id, int incr) {
// String sql = "UPDATE ACTIVITY_USER SET JOIN_TIMES=JOIN_TIMES+? WHERE UUID=? ";
// jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
// @Override
// public void setValues(PreparedStatement ps, int i) throws SQLException {
// ps.setDouble(1, incr);
// ps.setString(2, id);
// }
//
// @Override
// public int getBatchSize() {
// return 1;
// }
// });
// }
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setPartyService(PartyService partyService) {
this.partyService = partyService;
}
}

Some files were not shown because too many files have changed in this diff Show More