增加VIP 赠送,主播工资统计

This commit is contained in:
hy001 2026-05-19 21:00:04 +08:00
parent e3942ff651
commit a9320d58ec
27 changed files with 1507 additions and 242 deletions

View File

@ -4,8 +4,9 @@ import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceDecrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import java.util.List;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@ -56,9 +57,16 @@ public interface UserBankBalanceClientApi {
@GetMapping("/getTotalBalance")
ResultResponse<Long> getTotalBalance(@RequestParam("userId") Long userId);
/**
* 查看用户银行卡账户.
*/
@PostMapping("/listBalance")
ResultResponse<List<UserBankBalanceDTO>> listBalance(@RequestBody UserBankBalanceQryCmd query);
}
/**
* 查看用户银行卡账户.
*/
@PostMapping("/listBalance")
ResultResponse<List<UserBankBalanceDTO>> listBalance(@RequestBody UserBankBalanceQryCmd query);
/**
* 用户银行卡账户统计.
*/
@PostMapping("/calculateStatistics")
ResultResponse<UserBankBalanceStatisticsDTO> calculateStatistics(
@RequestBody UserBankBalanceQryCmd query);
}

View File

@ -37,8 +37,23 @@ public class UserBankBalanceQryCmd extends CommonCommand {
*/
private Long lastId;
/**
* 每页大小.
*/
private Integer limit;
}
/**
* 每页大小.
*/
private Integer limit;
/**
* 页码.
*/
private Integer pageNo;
/**
* 排序字段.
*/
private String sortField;
/**
* 排序方向.
*/
private String sortOrder;
}

View File

@ -0,0 +1,29 @@
package com.red.circle.wallet.inner.model.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 用户银行账户统计.
*/
@Data
@Accessors(chain = true)
public class UserBankBalanceStatisticsDTO implements Serializable {
/**
* 获得总额.
*/
private BigDecimal totalEarnPoints;
/**
* 消费总额.
*/
private BigDecimal totalConsumptionPoints;
/**
* 余额总额.
*/
private BigDecimal totalBalance;
}

View File

@ -15,10 +15,11 @@ import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankMoneyCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankRunningWaterQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankTransferCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawGoldApplyQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyExportQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyQryCmd;
import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawGoldApplyQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyExportQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyQryCmd;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
@ -57,12 +58,20 @@ public class UserBankBalanceRestController extends BaseController {
* 用户余额列表.
*/
@GetMapping("/page")
public List<UserBankBalanceCO> page(UserBankBalanceQryCmd query) {
return userBankBalanceBackService.listUserBankBalance(query);
}
/**
* 用户余额列表-导出.
public List<UserBankBalanceCO> page(UserBankBalanceQryCmd query) {
return userBankBalanceBackService.listUserBankBalance(query);
}
/**
* 用户余额统计.
*/
@GetMapping("/statistics")
public UserBankBalanceStatisticsDTO statistics(UserBankBalanceQryCmd query) {
return userBankBalanceBackService.calculateStatistics(query);
}
/**
* 用户余额列表-导出.
*/
@GetMapping("/export")
public void export(HttpServletResponse response, String sysOrigin) throws IOException {

View File

@ -96,9 +96,9 @@ public class UserBankBalanceBackServiceImpl implements UserBankBalanceBackServic
@Override
public List<UserBankBalanceCO> listUserBankBalance(UserBankBalanceQryCmd query) {
List<UserBankBalanceDTO> userBankBalances = ResponseAssert.requiredSuccess(
userBankBalanceClient.listBalance(query));
public List<UserBankBalanceCO> listUserBankBalance(UserBankBalanceQryCmd query) {
List<UserBankBalanceDTO> userBankBalances = ResponseAssert.requiredSuccess(
userBankBalanceClient.listBalance(query));
if (CollectionUtils.isEmpty(userBankBalances)) {
return Lists.newArrayList();
}
@ -122,11 +122,16 @@ public class UserBankBalanceBackServiceImpl implements UserBankBalanceBackServic
.setUserProfile(userProfile)
.setCreateTime(userBankBalance.getCreateTime())
.setUpdateTime(userBankBalance.getUpdateTime());
}).filter(Objects::nonNull).collect(Collectors.toList());
}
private Set<Long> getBankUserIds(List<UserBankBalanceDTO> userBankBalances) {
}).filter(Objects::nonNull).collect(Collectors.toList());
}
@Override
public UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query) {
return ResponseAssert.requiredSuccess(userBankBalanceClient.calculateStatistics(query));
}
private Set<Long> getBankUserIds(List<UserBankBalanceDTO> userBankBalances) {
return userBankBalances.stream().map(UserBankBalanceDTO::getId).collect(Collectors.toSet());
}

View File

@ -9,10 +9,11 @@ import com.red.circle.wallet.inner.model.cmd.ApprovalMoneyApplyCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankMoneyCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankRunningWaterQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankTransferCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawGoldApplyQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyExportQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankTransferCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawGoldApplyQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyExportQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankWithdrawMoneyApplyQryCmd;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import jakarta.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.List;
@ -25,13 +26,18 @@ import org.springframework.web.multipart.MultipartFile;
*/
public interface UserBankBalanceBackService {
/**
* 查询用户银行账户余额.
*/
List<UserBankBalanceCO> listUserBankBalance(UserBankBalanceQryCmd query);
/**
* 导出余额列表.
/**
* 查询用户银行账户余额.
*/
List<UserBankBalanceCO> listUserBankBalance(UserBankBalanceQryCmd query);
/**
* 统计用户银行账户余额.
*/
UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query);
/**
* 导出余额列表.
*/
void exportUserBankBalance(HttpServletResponse response, String sysOrigin);

View File

@ -84,9 +84,10 @@ public class PropsActivitySendCommon {
private final RoomProfileManagerService roomProfileManagerService;
private final ActivitySourceGroupGateway activitySourceGroupGateway;
private final PropsMaterialInfraConvertor propsMaterialInfraConvertor;
private final PropsVipActualEquityService propsVipActualEquityService;
private final ActivityReceiveRewardLogService activityReceiveRewardLogService;
private final PropsActivityRewardConfigService propsActivityRewardConfigService;
private final PropsVipActualEquityService propsVipActualEquityService;
private final VipActivityRewardCommon vipActivityRewardCommon;
private final ActivityReceiveRewardLogService activityReceiveRewardLogService;
private final PropsActivityRewardConfigService propsActivityRewardConfigService;
/**
* 领取活动奖励.
@ -145,12 +146,20 @@ public class PropsActivitySendCommon {
* 发送单个-活动道具.
*/
public void sendActivitySingle(SendRewardSingle param) {
if (Objects.isNull(param.getRewardConfig())) {
throw new IllegalArgumentException("Send rewardConfig is null.");
}
PropsActivityRewardConfig rewardConfig = param.getRewardConfig();
sendGroup(param, Collections.singletonList(toPrizeDescribe(rewardConfig)));
}
if (Objects.isNull(param.getRewardConfig())) {
throw new IllegalArgumentException("Send rewardConfig is null.");
}
PropsActivityRewardConfig rewardConfig = param.getRewardConfig();
if (isVipLevelReward(param, rewardConfig)) {
vipActivityRewardCommon.gift(param.getSysOrigin(),
param.getAcceptUserId(),
param.getTrackId(),
rewardConfig.getContent(),
param.getOrigin());
return;
}
sendGroup(param, Collections.singletonList(toPrizeDescribe(rewardConfig)));
}
/**
* 发送奖励.
@ -159,17 +168,33 @@ public class PropsActivitySendCommon {
List<PropsActivityRewardConfig> configs = propsActivityRewardConfigService.listByGroupId(
param.getResourceGroupId());
if (CollectionUtils.isEmpty(configs)) {
return;
}
propsSendCommon.send(new PrizeDescribeRewardCmd()
.setTrackId(param.getTrackId())
.setAcceptUserId(param.getAcceptUserId())
.setOrigin(param.getOrigin())
.setSysOrigin(param.getSysOrigin())
.setPrizes(toListPrizeDescribe(configs))
);
}
if (CollectionUtils.isEmpty(configs)) {
return;
}
List<PropsActivityRewardConfig> vipConfigs = configs.stream()
.filter(config -> isVipLevelReward(param, config))
.toList();
vipConfigs.forEach(config -> vipActivityRewardCommon.gift(param.getSysOrigin(),
param.getAcceptUserId(),
param.getTrackId(),
config.getContent(),
param.getOrigin()));
List<PropsActivityRewardConfig> normalConfigs = configs.stream()
.filter(config -> !isVipLevelReward(param, config))
.toList();
if (CollectionUtils.isEmpty(normalConfigs)) {
return;
}
propsSendCommon.send(new PrizeDescribeRewardCmd()
.setTrackId(param.getTrackId())
.setAcceptUserId(param.getAcceptUserId())
.setOrigin(param.getOrigin())
.setSysOrigin(param.getSysOrigin())
.setPrizes(toListPrizeDescribe(normalConfigs))
);
}
/**
@ -196,10 +221,16 @@ public class PropsActivitySendCommon {
return;
}
if (PropsActivityRewardEnum.PROPS.eq(key)) {
sendProps(param, prizeArrays);
return;
}
if (PropsActivityRewardEnum.PROPS.eq(key)) {
sendVipLevelRewards(param, prizeArrays);
List<PrizeDescribe> normalProps = prizeArrays.stream()
.filter(prize -> !isVipLevelReward(param, prize))
.toList();
if (CollectionUtils.isNotEmpty(normalProps)) {
sendProps(param, normalProps);
}
return;
}
if (PropsActivityRewardEnum.FRAGMENTS.eq(key)) {
@ -272,14 +303,36 @@ public class PropsActivitySendCommon {
return configs.stream().map(this::toPrizeDescribe).collect(Collectors.toList());
}
private PrizeDescribe toPrizeDescribe(PropsActivityRewardConfig config) {
return new PrizeDescribe()
private PrizeDescribe toPrizeDescribe(PropsActivityRewardConfig config) {
return new PrizeDescribe()
.setTrackId(config.getGroupId())
.setType(config.getType())
.setDetailType(config.getDetailType())
.setContent(config.getContent())
.setQuantity(config.getQuantity());
}
.setQuantity(config.getQuantity());
}
private void sendVipLevelRewards(SendRewardAbstract param, List<PrizeDescribe> prizeArrays) {
prizeArrays.stream()
.filter(prize -> isVipLevelReward(param, prize))
.forEach(prize -> vipActivityRewardCommon.gift(param.getSysOrigin(),
param.getAcceptUserId(),
param.getTrackId(),
prize.getContent(),
param.getOrigin()));
}
private boolean isVipLevelReward(SendRewardAbstract param, PropsActivityRewardConfig config) {
return Objects.nonNull(param.getSysOrigin())
&& vipActivityRewardCommon.isVipDetail(config.getType(), config.getDetailType())
&& vipActivityRewardCommon.existsEnabledConfig(param.getSysOrigin(), config.getContent());
}
private boolean isVipLevelReward(SendRewardAbstract param, PrizeDescribe prize) {
return Objects.nonNull(param.getSysOrigin())
&& vipActivityRewardCommon.isVipDetail(prize.getType(), prize.getDetailType())
&& vipActivityRewardCommon.existsEnabledConfig(param.getSysOrigin(), prize.getContent());
}
private void sendEmoji(SendRewardAbstract param, List<PrizeDescribe> val) {
val.forEach(props ->

View File

@ -0,0 +1,346 @@
package com.red.circle.other.infra.common.activity;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.enums.SendPropsOrigin;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.infra.database.rds.entity.badge.BadgeBackpack;
import com.red.circle.other.infra.database.rds.entity.props.PropsBackpack;
import com.red.circle.other.infra.database.rds.entity.props.RunningWaterUserProps;
import com.red.circle.other.infra.database.rds.entity.vip.UserVipState;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import com.red.circle.other.infra.database.rds.entity.vip.VipOrderRecord;
import com.red.circle.other.infra.database.rds.service.badge.BadgeBackpackService;
import com.red.circle.other.infra.database.rds.service.props.PropsBackpackService;
import com.red.circle.other.infra.database.rds.service.props.RunningWaterUserPropsService;
import com.red.circle.other.infra.database.rds.service.vip.UserVipStateService;
import com.red.circle.other.infra.database.rds.service.vip.VipLevelConfigService;
import com.red.circle.other.infra.database.rds.service.vip.VipOrderRecordService;
import com.red.circle.other.inner.enums.material.BadgeBackpackExpireType;
import com.red.circle.other.inner.enums.material.PropsCommodityType;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* Sends operator-managed VIP rewards from resource groups.
*/
@Component
@RequiredArgsConstructor
public class VipActivityRewardCommon {
public static final String VIP_DETAIL_TYPE = "NOBLE_VIP";
private static final int DEFAULT_DURATION_DAYS = 30;
private static final String STATUS_ACTIVE = "ACTIVE";
private static final String STATUS_SUCCESS = "SUCCESS";
private static final String ORDER_TYPE_RESOURCE_GROUP = "RESOURCE_GROUP";
private static final String EVENT_PREFIX = "RESOURCE-GROUP-VIP-";
private static final String WATER_ORIGIN = "VIP_PURCHASE";
private static final String WATER_ORIGIN_DESC = "VIP purchase";
private static final Timestamp MYSQL_DATETIME_MAX = Timestamp.valueOf("2038-01-19 00:00:00");
private final UserProfileGateway userProfileGateway;
private final BadgeBackpackService badgeBackpackService;
private final PropsBackpackService propsBackpackService;
private final UserVipStateService userVipStateService;
private final VipLevelConfigService vipLevelConfigService;
private final VipOrderRecordService vipOrderRecordService;
private final RunningWaterUserPropsService runningWaterUserPropsService;
public boolean isVipDetail(String type, String detailType) {
return Objects.equals("PROPS", type) && Objects.equals(VIP_DETAIL_TYPE, detailType);
}
public boolean existsEnabledConfig(SysOriginPlatformEnum sysOrigin, String content) {
Long configId = DataTypeUtils.toLong(content);
return Objects.nonNull(vipLevelConfigService.getEnabledById(sysOrigin.name(), configId));
}
@Transactional(rollbackFor = Exception.class)
public void gift(SysOriginPlatformEnum sysOrigin, Long userId, Long trackId, String content,
SendPropsOrigin origin) {
Long configId = DataTypeUtils.toLong(content);
VipLevelConfig config = vipLevelConfigService.getEnabledById(sysOrigin.name(), configId);
ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, config);
int durationDays = durationDays(config);
Timestamp now = TimestampUtils.now();
UserVipState currentState = getUserVipState(sysOrigin.name(), userId);
boolean currentActive = isActive(currentState, now);
int fromLevel = currentActive ? Objects.requireNonNullElse(currentState.getLevel(), 0) : 0;
ResponseAssert.isFalse(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
currentActive && fromLevel > Objects.requireNonNullElse(config.getLevel(), 0));
Timestamp startAt = currentActive && Objects.nonNull(currentState.getStartAt())
? currentState.getStartAt()
: now;
Timestamp expireAt = capExpireTime(resolveExpireAt(currentState, config, currentActive, now));
Long orderId = IdWorkerUtils.getId();
String eventId = EVENT_PREFIX + orderId;
vipOrderRecordService.save(buildOrder(orderId, eventId, sysOrigin.name(), userId, fromLevel,
config, startAt, expireAt, now));
upsertUserVipState(currentState, sysOrigin.name(), userId, config, startAt, expireAt, now);
giftVipResources(userId, trackId, config, expireAt, durationDays, now, origin);
userProfileGateway.removeCacheAll(userId);
}
private VipOrderRecord buildOrder(Long orderId, String eventId, String sysOrigin, Long userId,
Integer fromLevel, VipLevelConfig config, Timestamp startAt, Timestamp expireAt,
Timestamp now) {
VipOrderRecord order = new VipOrderRecord()
.setId(orderId)
.setEventId(eventId)
.setSysOrigin(sysOrigin)
.setUserId(userId)
.setOrderType(ORDER_TYPE_RESOURCE_GROUP)
.setFromLevel(fromLevel)
.setToLevel(config.getLevel())
.setDurationDays(durationDays(config))
.setOriginalPriceGold(Objects.requireNonNullElse(config.getPriceGold(), 0L))
.setPaidGold(0L)
.setStatus(STATUS_SUCCESS)
.setStartAt(startAt)
.setExpireAt(expireAt)
.setErrorMessage("");
order.setCreateTime(now);
order.setUpdateTime(now);
return order;
}
private void upsertUserVipState(UserVipState currentState, String sysOrigin, Long userId,
VipLevelConfig config, Timestamp startAt, Timestamp expireAt, Timestamp now) {
UserVipState state = Objects.nonNull(currentState)
? currentState
: new UserVipState().setId(IdWorkerUtils.getId());
if (Objects.isNull(currentState)) {
state.setCreateTime(now);
}
state.setSysOrigin(sysOrigin);
state.setUserId(userId);
state.setLevel(config.getLevel());
state.setLevelCode(levelCode(config));
state.setDisplayName(displayName(config));
state.setStatus(STATUS_ACTIVE);
state.setStartAt(startAt);
state.setExpireAt(expireAt);
copyResources(state, config);
state.setUpdateTime(now);
if (Objects.nonNull(currentState)) {
userVipStateService.updateSelectiveById(state);
return;
}
userVipStateService.save(state);
}
private void copyResources(UserVipState state, VipLevelConfig config) {
state.setBadgeResourceId(config.getBadgeResourceId());
state.setBadgeName(config.getBadgeName());
state.setBadgeUrl(config.getBadgeUrl());
state.setShortBadgeResourceId(config.getShortBadgeResourceId());
state.setShortBadgeName(config.getShortBadgeName());
state.setShortBadgeUrl(config.getShortBadgeUrl());
state.setAvatarFrameResourceId(config.getAvatarFrameResourceId());
state.setAvatarFrameName(config.getAvatarFrameName());
state.setAvatarFrameUrl(config.getAvatarFrameUrl());
state.setEntryEffectResourceId(config.getEntryEffectResourceId());
state.setEntryEffectName(config.getEntryEffectName());
state.setEntryEffectUrl(config.getEntryEffectUrl());
state.setChatBubbleResourceId(config.getChatBubbleResourceId());
state.setChatBubbleName(config.getChatBubbleName());
state.setChatBubbleUrl(config.getChatBubbleUrl());
state.setFloatPictureResourceId(config.getFloatPictureResourceId());
state.setFloatPictureName(config.getFloatPictureName());
state.setFloatPictureUrl(config.getFloatPictureUrl());
state.setBackgroundCardResourceId(config.getBackgroundCardResourceId());
state.setBackgroundCardName(config.getBackgroundCardName());
state.setBackgroundCardUrl(config.getBackgroundCardUrl());
state.setEffectImageResourceId(config.getEffectImageResourceId());
state.setEffectImageName(config.getEffectImageName());
state.setEffectImageUrl(config.getEffectImageUrl());
}
private void giftVipResources(Long userId, Long trackId, VipLevelConfig config,
Timestamp expireAt, Integer days, Timestamp now, SendPropsOrigin origin) {
for (VipResourceGrant resource : vipResourcesFromConfig(config)) {
if (resource.badge) {
giveBadge(userId, resource.resourceId, expireAt, days);
continue;
}
giveProp(userId, trackId, resource.resourceId, resource.type, expireAt, days, now, origin);
}
}
private List<VipResourceGrant> vipResourcesFromConfig(VipLevelConfig config) {
return List.of(
VipResourceGrant.badge(config.getBadgeResourceId()),
VipResourceGrant.badge(config.getShortBadgeResourceId()),
VipResourceGrant.prop(config.getAvatarFrameResourceId(), PropsCommodityType.AVATAR_FRAME),
VipResourceGrant.prop(config.getEntryEffectResourceId(), PropsCommodityType.RIDE),
VipResourceGrant.prop(config.getChatBubbleResourceId(), PropsCommodityType.CHAT_BUBBLE),
VipResourceGrant.prop(config.getFloatPictureResourceId(), PropsCommodityType.FLOAT_PICTURE),
VipResourceGrant.prop(config.getBackgroundCardResourceId(), PropsCommodityType.DATA_CARD)
).stream().filter(VipResourceGrant::valid).toList();
}
private void giveBadge(Long userId, Long badgeId, Timestamp expireAt, Integer days) {
badgeBackpackService.activationTemporaryAndUse(userId, badgeId, days);
BadgeBackpack backpack = badgeBackpackService.getByUserIdByBadgeId(userId, badgeId);
if (Objects.isNull(backpack)) {
return;
}
backpack.setUseProps(Boolean.TRUE);
backpack.setExpireType(BadgeBackpackExpireType.TEMPORARY.name());
backpack.setExpireTime(expireAt);
badgeBackpackService.updateSelectiveById(backpack);
}
private void giveProp(Long userId, Long trackId, Long propsId, PropsCommodityType type,
Timestamp expireAt, Integer days, Timestamp now, SendPropsOrigin origin) {
PropsBackpack oldBackpack = propsBackpackService.getUserProps(userId, propsId, type);
boolean useProps = resolveUseProps(userId, propsId, type, oldBackpack, now);
propsBackpackService.addPropsBackpack(new PropsBackpack()
.setUserId(userId)
.setPropsId(propsId)
.setType(type.name())
.setExpireTime(expireAt)
.setUseProps(useProps)
.setAllowGive(Boolean.FALSE));
RunningWaterUserProps water = new RunningWaterUserProps()
.setUserId(userId)
.setPropsId(propsId)
.setPropsCandy(BigDecimal.ZERO)
.setType(type.name())
.setOrigin(Objects.nonNull(origin) ? origin.name() : WATER_ORIGIN)
.setOriginDesc(Objects.nonNull(origin) ? origin.getDesc() : WATER_ORIGIN_DESC)
.setDays(days);
water.setCreateTime(now);
water.setUpdateTime(now);
water.setCreateUser(Objects.requireNonNullElse(trackId, 0L));
water.setUpdateUser(0L);
runningWaterUserPropsService.save(water);
}
private boolean resolveUseProps(Long userId, Long propsId, PropsCommodityType type,
PropsBackpack oldBackpack, Timestamp now) {
if (Objects.nonNull(oldBackpack)) {
return Boolean.TRUE.equals(oldBackpack.getUseProps());
}
List<PropsBackpack> notExpiredSameType = propsBackpackService.query()
.eq(PropsBackpack::getUserId, userId)
.eq(PropsBackpack::getType, type.name())
.gt(PropsBackpack::getExpireTime, now)
.list();
return notExpiredSameType.stream()
.noneMatch(item -> Boolean.TRUE.equals(item.getUseProps())
&& !Objects.equals(item.getPropsId(), propsId));
}
private UserVipState getUserVipState(String sysOrigin, Long userId) {
return userVipStateService.query()
.eq(UserVipState::getSysOrigin, sysOrigin)
.eq(UserVipState::getUserId, userId)
.last(PageConstant.LIMIT_ONE)
.getOne();
}
private boolean isActive(UserVipState state, Timestamp now) {
return Objects.nonNull(state)
&& STATUS_ACTIVE.equals(state.getStatus())
&& Objects.nonNull(state.getExpireAt())
&& state.getExpireAt().after(now)
&& Objects.nonNull(state.getLevel())
&& state.getLevel() > 0;
}
private Timestamp resolveExpireAt(UserVipState currentState, VipLevelConfig config,
boolean currentActive, Timestamp now) {
Timestamp plusDuration = plusDays(now, durationDays(config));
if (!currentActive) {
return plusDuration;
}
if (Objects.equals(currentState.getLevel(), config.getLevel())) {
return plusDays(max(currentState.getExpireAt(), now), durationDays(config));
}
return max(currentState.getExpireAt(), plusDuration);
}
private int durationDays(VipLevelConfig config) {
Integer days = config.getDurationDays();
return Objects.nonNull(days) && days > 0 ? days : DEFAULT_DURATION_DAYS;
}
private String levelCode(VipLevelConfig config) {
if (Objects.nonNull(config.getLevelCode()) && !config.getLevelCode().isBlank()) {
return config.getLevelCode();
}
return "VIP" + Objects.requireNonNullElse(config.getLevel(), 0);
}
private String displayName(VipLevelConfig config) {
if (Objects.nonNull(config.getDisplayName()) && !config.getDisplayName().isBlank()) {
return config.getDisplayName();
}
return "VIP " + Objects.requireNonNullElse(config.getLevel(), 0);
}
private Timestamp max(Timestamp first, Timestamp second) {
if (Objects.isNull(first)) {
return second;
}
if (Objects.isNull(second)) {
return first;
}
return first.after(second) ? first : second;
}
private Timestamp plusDays(Timestamp value, int days) {
return Timestamp.from(value.toInstant().plus(Duration.ofDays(days)));
}
private Timestamp capExpireTime(Timestamp expireTime) {
if (Objects.isNull(expireTime)) {
return null;
}
return expireTime.after(MYSQL_DATETIME_MAX) ? MYSQL_DATETIME_MAX : expireTime;
}
private static final class VipResourceGrant {
private final Long resourceId;
private final PropsCommodityType type;
private final boolean badge;
private VipResourceGrant(Long resourceId, PropsCommodityType type, boolean badge) {
this.resourceId = resourceId;
this.type = type;
this.badge = badge;
}
private static VipResourceGrant badge(Long resourceId) {
return new VipResourceGrant(resourceId, null, true);
}
private static VipResourceGrant prop(Long resourceId, PropsCommodityType type) {
return new VipResourceGrant(resourceId, type, false);
}
private boolean valid() {
return Objects.nonNull(resourceId) && resourceId > 0;
}
}
}

View File

@ -0,0 +1,7 @@
package com.red.circle.other.infra.database.rds.dao.vip;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.vip.UserVipState;
public interface UserVipStateDAO extends BaseDAO<UserVipState> {
}

View File

@ -0,0 +1,7 @@
package com.red.circle.other.infra.database.rds.dao.vip;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
public interface VipLevelConfigDAO extends BaseDAO<VipLevelConfig> {
}

View File

@ -0,0 +1,7 @@
package com.red.circle.other.infra.database.rds.dao.vip;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.vip.VipOrderRecord;
public interface VipOrderRecordDAO extends BaseDAO<VipOrderRecord> {
}

View File

@ -0,0 +1,130 @@
package com.red.circle.other.infra.database.rds.entity.vip;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* User VIP state.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("user_vip_state")
public class UserVipState implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("sys_origin")
private String sysOrigin;
@TableField("user_id")
private Long userId;
@TableField("level")
private Integer level;
@TableField("level_code")
private String levelCode;
@TableField("display_name")
private String displayName;
@TableField("status")
private String status;
@TableField("start_at")
private Timestamp startAt;
@TableField("expire_at")
private Timestamp expireAt;
@TableField("badge_resource_id")
private Long badgeResourceId;
@TableField("badge_name")
private String badgeName;
@TableField("badge_url")
private String badgeUrl;
@TableField("short_badge_resource_id")
private Long shortBadgeResourceId;
@TableField("short_badge_name")
private String shortBadgeName;
@TableField("short_badge_url")
private String shortBadgeUrl;
@TableField("avatar_frame_resource_id")
private Long avatarFrameResourceId;
@TableField("avatar_frame_name")
private String avatarFrameName;
@TableField("avatar_frame_url")
private String avatarFrameUrl;
@TableField("entry_effect_resource_id")
private Long entryEffectResourceId;
@TableField("entry_effect_name")
private String entryEffectName;
@TableField("entry_effect_url")
private String entryEffectUrl;
@TableField("chat_bubble_resource_id")
private Long chatBubbleResourceId;
@TableField("chat_bubble_name")
private String chatBubbleName;
@TableField("chat_bubble_url")
private String chatBubbleUrl;
@TableField("float_picture_resource_id")
private Long floatPictureResourceId;
@TableField("float_picture_name")
private String floatPictureName;
@TableField("float_picture_url")
private String floatPictureUrl;
@TableField("background_card_resource_id")
private Long backgroundCardResourceId;
@TableField("background_card_name")
private String backgroundCardName;
@TableField("background_card_url")
private String backgroundCardUrl;
@TableField("effect_image_resource_id")
private Long effectImageResourceId;
@TableField("effect_image_name")
private String effectImageName;
@TableField("effect_image_url")
private String effectImageUrl;
@TableField("create_time")
private Timestamp createTime;
@TableField("update_time")
private Timestamp updateTime;
}

View File

@ -0,0 +1,127 @@
package com.red.circle.other.infra.database.rds.entity.vip;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* VIP level config.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("vip_level_config")
public class VipLevelConfig implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("sys_origin")
private String sysOrigin;
@TableField("level")
private Integer level;
@TableField("level_code")
private String levelCode;
@TableField("display_name")
private String displayName;
@TableField("enabled")
private Boolean enabled;
@TableField("duration_days")
private Integer durationDays;
@TableField("price_gold")
private Long priceGold;
@TableField("badge_resource_id")
private Long badgeResourceId;
@TableField("badge_name")
private String badgeName;
@TableField("badge_url")
private String badgeUrl;
@TableField("short_badge_resource_id")
private Long shortBadgeResourceId;
@TableField("short_badge_name")
private String shortBadgeName;
@TableField("short_badge_url")
private String shortBadgeUrl;
@TableField("avatar_frame_resource_id")
private Long avatarFrameResourceId;
@TableField("avatar_frame_name")
private String avatarFrameName;
@TableField("avatar_frame_url")
private String avatarFrameUrl;
@TableField("entry_effect_resource_id")
private Long entryEffectResourceId;
@TableField("entry_effect_name")
private String entryEffectName;
@TableField("entry_effect_url")
private String entryEffectUrl;
@TableField("chat_bubble_resource_id")
private Long chatBubbleResourceId;
@TableField("chat_bubble_name")
private String chatBubbleName;
@TableField("chat_bubble_url")
private String chatBubbleUrl;
@TableField("float_picture_resource_id")
private Long floatPictureResourceId;
@TableField("float_picture_name")
private String floatPictureName;
@TableField("float_picture_url")
private String floatPictureUrl;
@TableField("background_card_resource_id")
private Long backgroundCardResourceId;
@TableField("background_card_name")
private String backgroundCardName;
@TableField("background_card_url")
private String backgroundCardUrl;
@TableField("effect_image_resource_id")
private Long effectImageResourceId;
@TableField("effect_image_name")
private String effectImageName;
@TableField("effect_image_url")
private String effectImageUrl;
@TableField("create_time")
private Timestamp createTime;
@TableField("update_time")
private Timestamp updateTime;
}

View File

@ -0,0 +1,73 @@
package com.red.circle.other.infra.database.rds.entity.vip;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serial;
import java.io.Serializable;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* VIP order record.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("vip_order_record")
public class VipOrderRecord implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("event_id")
private String eventId;
@TableField("sys_origin")
private String sysOrigin;
@TableField("user_id")
private Long userId;
@TableField("order_type")
private String orderType;
@TableField("from_level")
private Integer fromLevel;
@TableField("to_level")
private Integer toLevel;
@TableField("duration_days")
private Integer durationDays;
@TableField("original_price_gold")
private Long originalPriceGold;
@TableField("paid_gold")
private Long paidGold;
@TableField("status")
private String status;
@TableField("start_at")
private Timestamp startAt;
@TableField("expire_at")
private Timestamp expireAt;
@TableField("error_message")
private String errorMessage;
@TableField("create_time")
private Timestamp createTime;
@TableField("update_time")
private Timestamp updateTime;
}

View File

@ -0,0 +1,7 @@
package com.red.circle.other.infra.database.rds.service.vip;
import com.red.circle.framework.mybatis.service.BaseService;
import com.red.circle.other.infra.database.rds.entity.vip.UserVipState;
public interface UserVipStateService extends BaseService<UserVipState> {
}

View File

@ -0,0 +1,15 @@
package com.red.circle.other.infra.database.rds.service.vip;
import com.red.circle.framework.mybatis.service.BaseService;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import java.util.Map;
import java.util.Set;
public interface VipLevelConfigService extends BaseService<VipLevelConfig> {
Map<Long, VipLevelConfig> mapByIds(Set<Long> ids);
Map<Long, VipLevelConfig> mapBySysOriginAndIds(String sysOrigin, Set<Long> ids);
VipLevelConfig getEnabledById(String sysOrigin, Long id);
}

View File

@ -0,0 +1,7 @@
package com.red.circle.other.infra.database.rds.service.vip;
import com.red.circle.framework.mybatis.service.BaseService;
import com.red.circle.other.infra.database.rds.entity.vip.VipOrderRecord;
public interface VipOrderRecordService extends BaseService<VipOrderRecord> {
}

View File

@ -0,0 +1,12 @@
package com.red.circle.other.infra.database.rds.service.vip.impl;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.other.infra.database.rds.dao.vip.UserVipStateDAO;
import com.red.circle.other.infra.database.rds.entity.vip.UserVipState;
import com.red.circle.other.infra.database.rds.service.vip.UserVipStateService;
import org.springframework.stereotype.Service;
@Service
public class UserVipStateServiceImpl extends BaseServiceImpl<UserVipStateDAO, UserVipState>
implements UserVipStateService {
}

View File

@ -0,0 +1,59 @@
package com.red.circle.other.infra.database.rds.service.vip.impl;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.other.infra.database.rds.dao.vip.VipLevelConfigDAO;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import com.red.circle.other.infra.database.rds.service.vip.VipLevelConfigService;
import com.red.circle.tool.core.collection.CollectionUtils;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
@Service
public class VipLevelConfigServiceImpl extends BaseServiceImpl<VipLevelConfigDAO, VipLevelConfig>
implements VipLevelConfigService {
@Override
public Map<Long, VipLevelConfig> mapByIds(Set<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return CollectionUtils.newHashMap();
}
return Optional.ofNullable(query().in(VipLevelConfig::getId, ids).list())
.map(list -> list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(VipLevelConfig::getId, item -> item, (first, second) -> first)))
.orElseGet(CollectionUtils::newHashMap);
}
@Override
public Map<Long, VipLevelConfig> mapBySysOriginAndIds(String sysOrigin, Set<Long> ids) {
if (CollectionUtils.isEmpty(ids) || Objects.isNull(sysOrigin)) {
return CollectionUtils.newHashMap();
}
return Optional.ofNullable(query()
.eq(VipLevelConfig::getSysOrigin, sysOrigin)
.in(VipLevelConfig::getId, ids)
.list())
.map(list -> list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(VipLevelConfig::getId, item -> item, (first, second) -> first)))
.orElseGet(CollectionUtils::newHashMap);
}
@Override
public VipLevelConfig getEnabledById(String sysOrigin, Long id) {
if (Objects.isNull(id) || Objects.isNull(sysOrigin)) {
return null;
}
return query()
.eq(VipLevelConfig::getSysOrigin, sysOrigin)
.eq(VipLevelConfig::getId, id)
.eq(VipLevelConfig::getEnabled, Boolean.TRUE)
.last(PageConstant.LIMIT_ONE)
.getOne();
}
}

View File

@ -0,0 +1,12 @@
package com.red.circle.other.infra.database.rds.service.vip.impl;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.other.infra.database.rds.dao.vip.VipOrderRecordDAO;
import com.red.circle.other.infra.database.rds.entity.vip.VipOrderRecord;
import com.red.circle.other.infra.database.rds.service.vip.VipOrderRecordService;
import org.springframework.stereotype.Service;
@Service
public class VipOrderRecordServiceImpl extends BaseServiceImpl<VipOrderRecordDAO, VipOrderRecord>
implements VipOrderRecordService {
}

View File

@ -3,16 +3,21 @@ package com.red.circle.other.infra.gateway.props;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.enums.PropsActivityRewardEnum;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.domain.gateway.props.ActivitySourceGroupGateway;
import com.red.circle.other.infra.common.props.PropsResourcesCommon;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.domain.gateway.props.ActivitySourceGroupGateway;
import com.red.circle.other.infra.common.activity.VipActivityRewardCommon;
import com.red.circle.other.infra.common.props.PropsResourcesCommon;
import com.red.circle.other.infra.convertor.material.PropsActivityInfraConvertor;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardGroup;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardGroupService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardGroup;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
import com.red.circle.other.infra.database.rds.entity.props.PropsSourceRecord;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardGroupService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
import com.red.circle.other.infra.database.rds.service.props.PropsSourceRecordService;
import com.red.circle.other.infra.database.rds.service.vip.VipLevelConfigService;
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
import com.red.circle.other.inner.model.cmd.material.PropsResourcesMergeCmd;
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
@ -50,11 +55,13 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGateway {
private final PropsResourcesCommon propsResourcesCommon;
private final PropsActivityInfraConvertor propsActivityInfraConvertor;
private final PropsActivityRuleConfigService propsActivityRuleConfigService;
private final PropsActivityRewardGroupService propsActivityRewardGroupService;
private final PropsActivityRewardConfigService propsActivityRewardConfigService;
private final PropsResourcesCommon propsResourcesCommon;
private final PropsActivityInfraConvertor propsActivityInfraConvertor;
private final PropsSourceRecordService propsSourceRecordService;
private final VipLevelConfigService vipLevelConfigService;
private final PropsActivityRuleConfigService propsActivityRuleConfigService;
private final PropsActivityRewardGroupService propsActivityRewardGroupService;
private final PropsActivityRewardConfigService propsActivityRewardConfigService;
@Override
public ActivityPropsRule getByRuleId(Long id) {
@ -122,16 +129,21 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
List<PropsActivityRewardGroup> rewardGroups = propsActivityRewardGroupService
.listByIds(groupIds);
Map<Long, List<PropsActivityRewardConfig>> rewardGroupConfigMap = propsActivityRewardConfigService
.mapGroupByIds(groupIds);
PropsResourcesMergeDTO propsResourcesMerge = propsResourcesCommon.mapMergeByIds(
new PropsResourcesMergeCmd()
.setSysOrigin(sysOrigin)
.setPropsIds(getPropsAllIds(rewardGroupConfigMap))
.setGiftIds(
getActivityRewardContentToId(rewardGroupConfigMap, PropsActivityRewardEnum.GIFT))
.setBadgeIds(
Map<Long, List<PropsActivityRewardConfig>> rewardGroupConfigMap = propsActivityRewardConfigService
.mapGroupByIds(groupIds);
Map<Long, VipLevelConfig> vipConfigMap = vipLevelConfigService.mapBySysOriginAndIds(sysOrigin,
getVipLevelConfigIds(rewardGroupConfigMap));
Map<Long, PropsSourceRecord> vipCoverMap = propsSourceRecordService.mapByIds(
getVipCoverResourceIds(vipConfigMap.values()));
PropsResourcesMergeDTO propsResourcesMerge = propsResourcesCommon.mapMergeByIds(
new PropsResourcesMergeCmd()
.setSysOrigin(sysOrigin)
.setPropsIds(getPropsAllIds(rewardGroupConfigMap, vipConfigMap.keySet()))
.setGiftIds(
getActivityRewardContentToId(rewardGroupConfigMap, PropsActivityRewardEnum.GIFT))
.setBadgeIds(
getActivityRewardContentToId(rewardGroupConfigMap, PropsActivityRewardEnum.BADGE))
.setEmojiIds(
getActivityRewardContentToId(rewardGroupConfigMap, PropsActivityRewardEnum.EMOJI))
@ -144,11 +156,12 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
List<PropsActivityRewardConfig> rewardConfigs = rewardGroupConfigMap
.get(activityPropsGroup.getId());
activityPropsGroup.setActivityRewardProps(rewardConfigs.stream()
.map(rewardConfig -> getActivityRewardProps(propsResourcesMerge, rewardConfig))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
activityPropsGroup.setActivityRewardProps(rewardConfigs.stream()
.map(rewardConfig -> getActivityRewardProps(propsResourcesMerge, vipConfigMap,
vipCoverMap, rewardConfig))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
return activityPropsGroup;
})
@ -161,12 +174,18 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
.get(groupId);
}
private ActivityRewardProps getActivityRewardProps(PropsResourcesMergeDTO propsSource,
PropsActivityRewardConfig rewardConfig) {
if (PropsActivityRewardEnum.PROPS.eq(rewardConfig.getType())
|| PropsActivityRewardEnum.PROP_COUPON.eq(rewardConfig.getType())
|| PropsActivityRewardEnum.FRAGMENTS.eq(rewardConfig.getType())
private ActivityRewardProps getActivityRewardProps(PropsResourcesMergeDTO propsSource,
Map<Long, VipLevelConfig> vipConfigMap,
Map<Long, PropsSourceRecord> vipCoverMap,
PropsActivityRewardConfig rewardConfig) {
if (isVipLevelReward(rewardConfig, vipConfigMap)) {
return buildActivityVip(vipConfigMap, vipCoverMap, rewardConfig);
}
if (PropsActivityRewardEnum.PROPS.eq(rewardConfig.getType())
|| PropsActivityRewardEnum.PROP_COUPON.eq(rewardConfig.getType())
|| PropsActivityRewardEnum.FRAGMENTS.eq(rewardConfig.getType())
|| PropsActivityRewardEnum.CUSTOMIZE.eq(rewardConfig.getType())) {
return buildActivityProps(propsSource.getProps(), rewardConfig);
}
@ -208,8 +227,8 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
return activityRewardProps;
}
private ActivityRewardProps buildActivityGift(
Map<Long, GiftConfigDTO> giftConfigMap, PropsActivityRewardConfig rewardConfig) {
private ActivityRewardProps buildActivityGift(
Map<Long, GiftConfigDTO> giftConfigMap, PropsActivityRewardConfig rewardConfig) {
GiftConfigDTO giftConfig = giftConfigMap.get(DataTypeUtils.toLong(rewardConfig.getContent()));
@ -221,8 +240,25 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
giftConfig.getGiftCandy().multiply(BigDecimal.valueOf(rewardConfig.getQuantity()))
.setScale(0, RoundingMode.DOWN));
activityRewardProps.setDetailType(rewardConfig.getDetailType());
return activityRewardProps;
}
return activityRewardProps;
}
private ActivityRewardProps buildActivityVip(
Map<Long, VipLevelConfig> vipConfigMap,
Map<Long, PropsSourceRecord> vipCoverMap,
PropsActivityRewardConfig rewardConfig) {
VipLevelConfig vipConfig = vipConfigMap.get(DataTypeUtils.toLong(rewardConfig.getContent()));
ResponseAssert.notNull(CommonErrorCode.CONFIGURATION_ERROR, vipConfig);
ActivityRewardProps activityRewardProps = propsActivityInfraConvertor
.toActivityRewardProps(rewardConfig);
activityRewardProps.setCover(resolveVipCover(vipConfig, vipCoverMap));
activityRewardProps.setQuantity(durationDays(vipConfig));
activityRewardProps.setAmount(BigDecimal.valueOf(Objects.requireNonNullElse(
vipConfig.getPriceGold(), 0L)));
activityRewardProps.setRemark(displayName(vipConfig));
activityRewardProps.setDetailType(rewardConfig.getDetailType());
return activityRewardProps;
}
private ActivityRewardProps buildEmoji(Map<Long, EmojiGroupDTO> emojiGroupMap,
PropsActivityRewardConfig rewardConfig) {
@ -282,19 +318,28 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
return activityRewardProps;
}
private Set<Long> getPropsAllIds(
Map<Long, List<PropsActivityRewardConfig>> rewardGroupConfigMap) {
return getPropsAllIds(
rewardGroupConfigMap.values().stream().flatMap(Collection::stream).collect(
Collectors.toList())
);
}
private Set<Long> getPropsAllIds(List<PropsActivityRewardConfig> rewardConfigs) {
Set<Long> propsIds = getActivityRewardContentToId(rewardConfigs, PropsActivityRewardEnum.PROPS);
Set<Long> couponIds = getActivityRewardContentToId(rewardConfigs, PropsActivityRewardEnum.PROP_COUPON);
propsIds.addAll(couponIds);
private Set<Long> getPropsAllIds(
Map<Long, List<PropsActivityRewardConfig>> rewardGroupConfigMap,
Set<Long> vipConfigIds) {
return getPropsAllIds(
rewardGroupConfigMap.values().stream().flatMap(Collection::stream).collect(
Collectors.toList()),
vipConfigIds
);
}
private Set<Long> getPropsAllIds(List<PropsActivityRewardConfig> rewardConfigs,
Set<Long> vipConfigIds) {
Set<Long> propsIds = rewardConfigs.stream()
.filter(conf -> PropsActivityRewardEnum.PROPS.eq(conf.getType()))
.filter(conf -> !isVipLevelReward(conf, vipConfigIds))
.map(propsActivityRewardConfig -> DataTypeUtils
.toLong(propsActivityRewardConfig.getContent()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Set<Long> couponIds = getActivityRewardContentToId(rewardConfigs, PropsActivityRewardEnum.PROP_COUPON);
propsIds.addAll(couponIds);
Set<Long> fragmentIds = getActivityRewardContentToId(rewardConfigs,
PropsActivityRewardEnum.FRAGMENTS);
@ -319,8 +364,8 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
.collect(Collectors.toSet());
}
private Set<Long> getActivityRewardContentToId(
List<PropsActivityRewardConfig> rewardGroupConfigList,
private Set<Long> getActivityRewardContentToId(
List<PropsActivityRewardConfig> rewardGroupConfigList,
PropsActivityRewardEnum type) {
if (CollectionUtils.isEmpty(rewardGroupConfigList)) {
return CollectionUtils.newHashSet();
@ -331,7 +376,90 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
.toLong(propsActivityRewardConfig.getContent()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
}
private Set<Long> getVipLevelConfigIds(
Map<Long, List<PropsActivityRewardConfig>> rewardGroupConfigMap) {
return rewardGroupConfigMap.values().stream()
.flatMap(Collection::stream)
.filter(config -> PropsActivityRewardEnum.PROPS.eq(config.getType()))
.filter(config -> Objects.equals(config.getDetailType(),
VipActivityRewardCommon.VIP_DETAIL_TYPE))
.map(config -> DataTypeUtils.toLong(config.getContent()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
private Set<Long> getVipCoverResourceIds(Collection<VipLevelConfig> vipConfigs) {
if (CollectionUtils.isEmpty(vipConfigs)) {
return CollectionUtils.newHashSet();
}
return vipConfigs.stream()
.map(this::firstVipResourceId)
.filter(Objects::nonNull)
.filter(id -> id > 0)
.collect(Collectors.toSet());
}
private boolean isVipLevelReward(PropsActivityRewardConfig rewardConfig,
Map<Long, VipLevelConfig> vipConfigMap) {
return isVipLevelReward(rewardConfig, vipConfigMap.keySet());
}
private boolean isVipLevelReward(PropsActivityRewardConfig rewardConfig, Set<Long> vipConfigIds) {
return PropsActivityRewardEnum.PROPS.eq(rewardConfig.getType())
&& Objects.equals(rewardConfig.getDetailType(), VipActivityRewardCommon.VIP_DETAIL_TYPE)
&& vipConfigIds.contains(DataTypeUtils.toLong(rewardConfig.getContent()));
}
private Long firstVipResourceId(VipLevelConfig vipConfig) {
return Stream.of(
vipConfig.getBadgeResourceId(),
vipConfig.getShortBadgeResourceId(),
vipConfig.getAvatarFrameResourceId(),
vipConfig.getEntryEffectResourceId(),
vipConfig.getChatBubbleResourceId(),
vipConfig.getFloatPictureResourceId(),
vipConfig.getBackgroundCardResourceId(),
vipConfig.getEffectImageResourceId())
.filter(Objects::nonNull)
.filter(id -> id > 0)
.findFirst()
.orElse(null);
}
private String resolveVipCover(VipLevelConfig vipConfig,
Map<Long, PropsSourceRecord> vipCoverMap) {
PropsSourceRecord sourceRecord = vipCoverMap.get(firstVipResourceId(vipConfig));
if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getCover())) {
return sourceRecord.getCover();
}
return Stream.of(
vipConfig.getBadgeUrl(),
vipConfig.getShortBadgeUrl(),
vipConfig.getAvatarFrameUrl(),
vipConfig.getEntryEffectUrl(),
vipConfig.getChatBubbleUrl(),
vipConfig.getFloatPictureUrl(),
vipConfig.getBackgroundCardUrl(),
vipConfig.getEffectImageUrl())
.filter(Objects::nonNull)
.filter(value -> !value.isBlank())
.findFirst()
.orElse("");
}
private int durationDays(VipLevelConfig vipConfig) {
Integer days = vipConfig.getDurationDays();
return Objects.nonNull(days) && days > 0 ? days : 30;
}
private String displayName(VipLevelConfig vipConfig) {
if (Objects.nonNull(vipConfig.getDisplayName()) && !vipConfig.getDisplayName().isBlank()) {
return vipConfig.getDisplayName();
}
return "VIP " + Objects.requireNonNullElse(vipConfig.getLevel(), 0);
}
private Set<Long> getGroupIds(List<PropsActivityRuleConfig> ruleConfigs) {

View File

@ -2,24 +2,27 @@ package com.red.circle.other.app.inner.service.material.props.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.red.circle.common.business.enums.PropsActivityRewardEnum;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.app.inner.convertor.material.PropsInnerConvertor;
import com.red.circle.other.app.inner.service.material.props.PropsActivityRewardGroupClientService;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardGroup;
import com.red.circle.common.business.enums.PropsActivityRewardEnum;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.infra.common.activity.VipActivityRewardCommon;
import com.red.circle.other.app.inner.convertor.material.PropsInnerConvertor;
import com.red.circle.other.app.inner.service.material.props.PropsActivityRewardGroupClientService;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardGroup;
import com.red.circle.other.infra.database.rds.entity.badge.BadgeConfig;
import com.red.circle.other.infra.database.rds.entity.badge.BadgePictureConfig;
import com.red.circle.other.infra.database.rds.entity.emoji.EmojiGroup;
import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig;
import com.red.circle.other.infra.database.rds.entity.props.PropsSourceRecord;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardGroupService;
import com.red.circle.other.infra.database.rds.service.badge.BadgeConfigService;
import com.red.circle.other.infra.database.rds.service.badge.BadgePictureConfigService;
import com.red.circle.other.infra.database.rds.entity.emoji.EmojiGroup;
import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig;
import com.red.circle.other.infra.database.rds.entity.props.PropsSourceRecord;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardGroupService;
import com.red.circle.other.infra.database.rds.service.badge.BadgeConfigService;
import com.red.circle.other.infra.database.rds.service.badge.BadgePictureConfigService;
import com.red.circle.other.infra.database.rds.service.emoji.EmojiGroupService;
import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService;
import com.red.circle.other.infra.database.rds.service.props.PropsSourceRecordService;
import com.red.circle.other.infra.database.rds.service.gift.GiftConfigService;
import com.red.circle.other.infra.database.rds.service.props.PropsSourceRecordService;
import com.red.circle.other.infra.database.rds.service.vip.VipLevelConfigService;
import com.red.circle.other.inner.model.cmd.material.PropsActivityRewardGroupParamCmd;
import com.red.circle.other.inner.model.cmd.material.PropsActivityRewardGroupQryCmd;
import com.red.circle.other.inner.model.dto.material.ActivityGroupKeyValueDTO;
@ -28,16 +31,18 @@ import com.red.circle.other.inner.model.dto.material.PropsActivityRewardGroupDTO
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import com.red.circle.tool.core.text.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -56,9 +61,10 @@ public class PropsActivityRewardGroupClientServiceImpl implements
private final EmojiGroupService emojiGroupService;
private final GiftConfigService giftConfigService;
private final BadgeConfigService badgeConfigService;
private final PropsInnerConvertor propsInnerConvertor;
private final PropsSourceRecordService propsSourceRecordService;
private final BadgeConfigService badgeConfigService;
private final PropsInnerConvertor propsInnerConvertor;
private final VipLevelConfigService vipLevelConfigService;
private final PropsSourceRecordService propsSourceRecordService;
private final BadgePictureConfigService badgePictureConfigService;
private final PropsActivityRewardGroupService propsActivityRewardGroupService;
private final PropsActivityRewardConfigService propsActivityRewardConfigService;
@ -215,14 +221,21 @@ public class PropsActivityRewardGroupClientServiceImpl implements
List<PropsActivityRewardConfig> propsActivityRewardConfigs = propsActivityRewardConfigService.listByGroupIds(
groupIds);
if (CollectionUtils.isEmpty(propsActivityRewardConfigs)) {
return Lists.newArrayList();
}
Set<Long> sourceIds = propsActivityRewardConfigs.stream().filter(config ->
Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name()) || Objects.equals(config.getType(), PropsActivityRewardEnum.PROP_COUPON.name()))
.map(config -> Long.valueOf(config.getContent()))
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(propsActivityRewardConfigs)) {
return Lists.newArrayList();
}
Map<Long, VipLevelConfig> vipConfigMap = vipLevelConfigService.mapByIds(
getVipLevelConfigIds(propsActivityRewardConfigs));
Map<Long, PropsSourceRecord> vipCoverMap = propsSourceRecordService.mapByIds(
getVipCoverResourceIds(vipConfigMap.values()));
Set<Long> sourceIds = propsActivityRewardConfigs.stream().filter(config ->
(Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name())
&& !isVipLevelReward(config, vipConfigMap))
|| Objects.equals(config.getType(), PropsActivityRewardEnum.PROP_COUPON.name()))
.map(config -> Long.valueOf(config.getContent()))
.collect(Collectors.toSet());
Set<Long> fragmentsIds = propsActivityRewardConfigs.stream().filter(config ->
Objects.equals(config.getType(), PropsActivityRewardEnum.FRAGMENTS.name()))
@ -262,12 +275,17 @@ public class PropsActivityRewardGroupClientServiceImpl implements
.collect(Collectors.toSet()));
return propsActivityRewardConfigs.stream().map(config -> {
PropsActivityRewardConfigInfoDTO propsActivity = propsInnerConvertor.toPropsActivityRewardConfigInfoDTO(
config);
propsActivity.setSort(Objects.isNull(propsActivity.getSort()) ? 0 : propsActivity.getSort());
if (Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name()) || Objects.equals(config.getType(), PropsActivityRewardEnum.PROP_COUPON.name())) {
PropsSourceRecord sourceRecord = sourceRecordMap
.get(Long.valueOf(config.getContent()));
PropsActivityRewardConfigInfoDTO propsActivity = propsInnerConvertor.toPropsActivityRewardConfigInfoDTO(
config);
propsActivity.setSort(Objects.isNull(propsActivity.getSort()) ? 0 : propsActivity.getSort());
if (isVipLevelReward(config, vipConfigMap)) {
setVipConfig(propsActivity, vipConfigMap.get(Long.valueOf(config.getContent())),
vipCoverMap);
return propsActivity;
}
if (Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name()) || Objects.equals(config.getType(), PropsActivityRewardEnum.PROP_COUPON.name())) {
PropsSourceRecord sourceRecord = sourceRecordMap
.get(Long.valueOf(config.getContent()));
if (Objects.nonNull(sourceRecord)) {
propsActivity.setCover(sourceRecord.getCover());
propsActivity.setSourceUrl(sourceRecord.getSourceUrl());
@ -330,6 +348,102 @@ public class PropsActivityRewardGroupClientServiceImpl implements
propsActivity.setAmount(giftConfig.getGiftCandy());
return;
}
log.error("礼物数据错误:{}", config);
}
}
log.error("礼物数据错误:{}", config);
}
private Set<Long> getVipLevelConfigIds(List<PropsActivityRewardConfig> configs) {
return configs.stream()
.filter(config -> Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name()))
.filter(config -> Objects.equals(config.getDetailType(),
VipActivityRewardCommon.VIP_DETAIL_TYPE))
.map(config -> DataTypeUtils.toLong(config.getContent()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
private Set<Long> getVipCoverResourceIds(Collection<VipLevelConfig> vipConfigs) {
if (CollectionUtils.isEmpty(vipConfigs)) {
return Set.of();
}
return vipConfigs.stream()
.map(this::firstVipResourceId)
.filter(Objects::nonNull)
.filter(id -> id > 0)
.collect(Collectors.toSet());
}
private boolean isVipLevelReward(PropsActivityRewardConfig config,
Map<Long, VipLevelConfig> vipConfigMap) {
return Objects.equals(config.getType(), PropsActivityRewardEnum.PROPS.name())
&& Objects.equals(config.getDetailType(), VipActivityRewardCommon.VIP_DETAIL_TYPE)
&& vipConfigMap.containsKey(DataTypeUtils.toLong(config.getContent()));
}
private void setVipConfig(PropsActivityRewardConfigInfoDTO propsActivity,
VipLevelConfig vipConfig, Map<Long, PropsSourceRecord> vipCoverMap) {
propsActivity.setCover(resolveVipCover(vipConfig, vipCoverMap));
propsActivity.setSourceUrl(resolveVipSourceUrl(vipConfig, vipCoverMap));
propsActivity.setAmount(BigDecimal.valueOf(Objects.requireNonNullElse(
vipConfig.getPriceGold(), 0L)));
propsActivity.setQuantity(durationDays(vipConfig));
propsActivity.setName(displayName(vipConfig));
propsActivity.setRemark(displayName(vipConfig));
}
private Long firstVipResourceId(VipLevelConfig vipConfig) {
return Stream.of(
vipConfig.getBadgeResourceId(),
vipConfig.getShortBadgeResourceId(),
vipConfig.getAvatarFrameResourceId(),
vipConfig.getEntryEffectResourceId(),
vipConfig.getChatBubbleResourceId(),
vipConfig.getFloatPictureResourceId(),
vipConfig.getBackgroundCardResourceId(),
vipConfig.getEffectImageResourceId())
.filter(Objects::nonNull)
.filter(id -> id > 0)
.findFirst()
.orElse(null);
}
private String resolveVipCover(VipLevelConfig vipConfig,
Map<Long, PropsSourceRecord> vipCoverMap) {
PropsSourceRecord sourceRecord = vipCoverMap.get(firstVipResourceId(vipConfig));
if (Objects.nonNull(sourceRecord) && StringUtils.isNotBlank(sourceRecord.getCover())) {
return sourceRecord.getCover();
}
return resolveVipSourceUrl(vipConfig, vipCoverMap);
}
private String resolveVipSourceUrl(VipLevelConfig vipConfig,
Map<Long, PropsSourceRecord> vipCoverMap) {
PropsSourceRecord sourceRecord = vipCoverMap.get(firstVipResourceId(vipConfig));
if (Objects.nonNull(sourceRecord) && StringUtils.isNotBlank(sourceRecord.getSourceUrl())) {
return sourceRecord.getSourceUrl();
}
return Stream.of(
vipConfig.getBadgeUrl(),
vipConfig.getShortBadgeUrl(),
vipConfig.getAvatarFrameUrl(),
vipConfig.getEntryEffectUrl(),
vipConfig.getChatBubbleUrl(),
vipConfig.getFloatPictureUrl(),
vipConfig.getBackgroundCardUrl(),
vipConfig.getEffectImageUrl())
.filter(StringUtils::isNotBlank)
.findFirst()
.orElse("");
}
private int durationDays(VipLevelConfig vipConfig) {
Integer days = vipConfig.getDurationDays();
return Objects.nonNull(days) && days > 0 ? days : 30;
}
private String displayName(VipLevelConfig vipConfig) {
if (StringUtils.isNotBlank(vipConfig.getDisplayName())) {
return vipConfig.getDisplayName();
}
return "VIP " + Objects.requireNonNullElse(vipConfig.getLevel(), 0);
}
}

View File

@ -3,8 +3,9 @@ package com.red.circle.wallet.infra.database.mongo.service.bank;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankBalance;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -52,11 +53,16 @@ public interface UserBankBalanceService {
*/
Map<Long, BankBalanceDTO> mapBalance(Collection<Long> userIds);
/**
* 查看用户银行卡账户.
*/
List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query);
List<UserBankBalance> listAll();
/**
* 查看用户银行卡账户.
*/
List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query);
/**
* 统计用户银行卡账户.
*/
UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query);
List<UserBankBalance> listAll();
}

View File

@ -8,20 +8,24 @@ import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankBalance;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankBalanceService;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import java.util.Collection;
import java.util.List;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
@ -33,10 +37,14 @@ import org.springframework.stereotype.Service;
*/
@Service
@RequiredArgsConstructor
public class UserBankBalanceServiceImpl implements UserBankBalanceService {
private final MongoTemplate mongoTemplate;
private final MongoPrimaryService mongoPrimaryService;
public class UserBankBalanceServiceImpl implements UserBankBalanceService {
private static final int DEFAULT_LIMIT = 20;
private static final Set<String> SORTABLE_FIELDS = Set.of(
"balance", "earnPoints", "consumptionPoints");
private final MongoTemplate mongoTemplate;
private final MongoPrimaryService mongoPrimaryService;
@Override
public boolean incr(Long userId, String sysOrigin, PennyAmount amount) {
@ -112,32 +120,26 @@ public class UserBankBalanceServiceImpl implements UserBankBalanceService {
balance -> BankBalanceDTO.of(balance.getBalance())));
}
@Override
public List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query) {
Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin());
if (Objects.nonNull(query.getUserId())) {
if (Objects.nonNull(query.getLastId())) {
query.setLastId(null);
criteria.and("id").lt(query.getLastId());
} else {
query.setLastId(null);
criteria.and("id").is(query.getUserId());
}
}
if (Objects.nonNull(query.getLastId())) {
criteria.and("id").lt(query.getLastId());
}
return mongoTemplate.find(Query.query(criteria)
.with(Sort.by(Sort.Order.desc("id")))
.limit(query.getLimit()),
UserBankBalance.class).stream()
.map(balance -> new UserBankBalanceDTO()
.setBalance(BankBalanceDTO.of(
balance.getBalance()).getAccurateBalance())
@Override
public List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query) {
boolean sorted = isSorted(query);
Criteria criteria = buildCriteria(query);
if (!sorted && Objects.nonNull(query.getLastId()) && Objects.isNull(query.getUserId())) {
criteria.and("id").lt(query.getLastId());
}
Query mongoQuery = Query.query(criteria)
.with(buildSort(query, sorted))
.limit(getLimit(query));
if (sorted) {
mongoQuery.skip((long) (getPageNo(query) - 1) * getLimit(query));
}
return mongoTemplate.find(mongoQuery, UserBankBalance.class).stream()
.map(balance -> new UserBankBalanceDTO()
.setBalance(BankBalanceDTO.of(
balance.getBalance()).getAccurateBalance())
.setSysOrigin(balance.getSysOrigin())
.setId(balance.getId())
.setEarnPoints(BankBalanceDTO.of(balance.getEarnPoints()).getAccurateBalance())
@ -146,12 +148,74 @@ public class UserBankBalanceServiceImpl implements UserBankBalanceService {
.setCreateTime(balance.getCreateTime())
.setUpdateTime(balance.getUpdateTime())
)
.toList();
}
@Override
public List<UserBankBalance> listAll() {
.toList();
}
@Override
public UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query) {
Document document = mongoTemplate.aggregate(
Aggregation.newAggregation(
Aggregation.match(buildCriteria(query)),
Aggregation.group()
.sum("earnPoints").as("totalEarnPoints")
.sum("consumptionPoints").as("totalConsumptionPoints")
.sum("balance").as("totalBalance")
),
UserBankBalance.class,
Document.class)
.getUniqueMappedResult();
return new UserBankBalanceStatisticsDTO()
.setTotalEarnPoints(getAccurateAmount(document, "totalEarnPoints"))
.setTotalConsumptionPoints(getAccurateAmount(document, "totalConsumptionPoints"))
.setTotalBalance(getAccurateAmount(document, "totalBalance"));
}
private Criteria buildCriteria(UserBankBalanceQryCmd query) {
Criteria criteria = new Criteria();
if (Objects.nonNull(query.getSysOrigin())) {
criteria.and("sysOrigin").is(query.getSysOrigin());
}
if (Objects.nonNull(query.getUserId())) {
criteria.and("id").is(query.getUserId());
}
return criteria;
}
private Sort buildSort(UserBankBalanceQryCmd query, boolean sorted) {
if (!sorted) {
return Sort.by(Sort.Order.desc("id"));
}
Sort.Direction direction = "ascend".equalsIgnoreCase(query.getSortOrder())
? Sort.Direction.ASC
: Sort.Direction.DESC;
return Sort.by(new Sort.Order(direction, query.getSortField()), Sort.Order.desc("id"));
}
private boolean isSorted(UserBankBalanceQryCmd query) {
return SORTABLE_FIELDS.contains(query.getSortField())
&& ("ascend".equalsIgnoreCase(query.getSortOrder())
|| "descend".equalsIgnoreCase(query.getSortOrder()));
}
private int getLimit(UserBankBalanceQryCmd query) {
return Objects.nonNull(query.getLimit()) && query.getLimit() > 0
? query.getLimit()
: DEFAULT_LIMIT;
}
private int getPageNo(UserBankBalanceQryCmd query) {
return Objects.nonNull(query.getPageNo()) && query.getPageNo() > 0 ? query.getPageNo() : 1;
}
private BigDecimal getAccurateAmount(Document document, String key) {
return BankBalanceDTO.of(Objects.isNull(document) ? 0L : getLongValue(document, key))
.getAccurateBalance();
}
@Override
public List<UserBankBalance> listAll() {
return mongoTemplate.find(Query.query(new Criteria()),
UserBankBalance.class);
}

View File

@ -6,8 +6,9 @@ import com.red.circle.wallet.inner.endpoint.bank.api.UserBankBalanceClientApi;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceDecrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import com.red.circle.wallet.inner.service.wallet.UserBankBalanceClientService;
import java.util.List;
import lombok.RequiredArgsConstructor;
@ -56,8 +57,14 @@ public class UserBankBalanceClientEndpoint implements UserBankBalanceClientApi {
@Override
public ResultResponse<List<UserBankBalanceDTO>> listBalance(UserBankBalanceQryCmd query) {
return ResultResponse.success(userBankBalanceClientService.listBalance(query));
}
}
public ResultResponse<List<UserBankBalanceDTO>> listBalance(UserBankBalanceQryCmd query) {
return ResultResponse.success(userBankBalanceClientService.listBalance(query));
}
@Override
public ResultResponse<UserBankBalanceStatisticsDTO> calculateStatistics(
UserBankBalanceQryCmd query) {
return ResultResponse.success(userBankBalanceClientService.calculateStatistics(query));
}
}

View File

@ -3,8 +3,9 @@ package com.red.circle.wallet.inner.service.wallet;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceDecrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import java.util.List;
/**
@ -40,8 +41,13 @@ public interface UserBankBalanceClientService {
*/
BankBalanceDTO decr(UserBankBalanceDecrCmd cmd);
/**
* 查看用户银行卡账户.
*/
List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query);
}
/**
* 查看用户银行卡账户.
*/
List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query);
/**
* 统计用户银行卡账户.
*/
UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query);
}

View File

@ -4,8 +4,9 @@ import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankBalanceSe
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceDecrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd;
import com.red.circle.wallet.inner.model.cmd.UserBankBalanceQryCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserBankBalanceStatisticsDTO;
import com.red.circle.wallet.inner.service.wallet.UserBankBalanceClientService;
import java.util.List;
import lombok.RequiredArgsConstructor;
@ -44,8 +45,13 @@ public class UserBankBalanceClientServiceImpl implements UserBankBalanceClientSe
@Override
public List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query) {
return userBankBalanceService.listBalance(query);
}
}
public List<UserBankBalanceDTO> listBalance(UserBankBalanceQryCmd query) {
return userBankBalanceService.listBalance(query);
}
@Override
public UserBankBalanceStatisticsDTO calculateStatistics(UserBankBalanceQryCmd query) {
return userBankBalanceService.calculateStatistics(query);
}
}