feat: update console and other register reward flow

This commit is contained in:
hy001 2026-04-16 20:07:14 +08:00
parent 258e56ea45
commit ed4a95da29
22 changed files with 565 additions and 172 deletions

View File

@ -62,6 +62,9 @@ public interface TeamBillCycleClientApi {
@PostMapping("/resetUnpaidBillMemberTarget")
ResultResponse<Void> resetUnpaidBillMemberTarget(@RequestBody TeamMemberTargetResetCmd param);
@PostMapping("/refreshUnpaidBills")
ResultResponse<Void> refreshUnpaidBills(@RequestBody TeamBillCycleBackQryCmd query);
@GetMapping("/getBillByTeamId")
ResultResponse<TeamBillCycleDTO> getBillByTeamId(@RequestParam("teamId") Long teamId);
}

View File

@ -0,0 +1,43 @@
package com.red.circle.console.adapter.app.activity;
import com.red.circle.console.app.dto.clienobject.app.activity.register.ResidentRegisterRewardConfigCO;
import com.red.circle.console.app.dto.cmd.app.activity.register.ResidentRegisterRewardConfigSaveCmd;
import com.red.circle.console.app.service.app.activity.ResidentRegisterRewardService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 常驻注册奖励后台接口.
*/
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/resident-activity/register-reward")
public class ResidentRegisterRewardRestController {
private final ResidentRegisterRewardService residentRegisterRewardService;
@GetMapping("/config")
public ResidentRegisterRewardConfigCO getConfig(String sysOrigin) {
return residentRegisterRewardService.getConfig(sysOrigin);
}
@OpsOperationReqLog(value = "保存注册奖励配置")
@PostMapping("/config/save")
public void saveConfig(@RequestBody @Validated ResidentRegisterRewardConfigSaveCmd cmd) {
residentRegisterRewardService.saveConfig(cmd);
}
@OpsOperationReqLog(value = "重置注册奖励配置")
@PostMapping("/config/reset")
public void resetConfig(@RequestParam("sysOrigin") String sysOrigin) {
residentRegisterRewardService.resetConfig(sysOrigin);
}
}

View File

@ -61,8 +61,10 @@ public class TeamPolicyManagerRestController extends BaseController {
// 发布完政策如果有团队是其他政策需要替换最新的政策可能出现的情况钻石->美元;美元->钻石;美元->美元;钻石->钻石
if (Objects.equals(param.getRelease(), Boolean.TRUE)) {
// 如果政策是发布的状态查询该区域所有的团队并改变团队政策
teamBillCycleClient.listBillTable(new TeamBillCycleBackQryCmd().setRegion(param.getRegion()));
// 发布后主动重算该系统该区域下团队的未出账账单
teamBillCycleClient.refreshUnpaidBills(new TeamBillCycleBackQryCmd()
.setSysOrigin(param.getSysOrigin())
.setRegion(param.getRegion()));
}
}

View File

@ -0,0 +1,116 @@
package com.red.circle.console.app.service.app.activity;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.red.circle.console.app.dto.clienobject.app.activity.register.ResidentRegisterRewardConfigCO;
import com.red.circle.console.app.dto.cmd.app.activity.register.ResidentRegisterRewardConfigSaveCmd;
import com.red.circle.console.app.service.app.props.PropsActivityRewardGroupService;
import com.red.circle.console.infra.database.rds.entity.app.register.ResidentRegisterRewardConfig;
import com.red.circle.console.infra.database.rds.service.app.register.ResidentRegisterRewardConfigService;
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.other.inner.model.dto.material.PropsActivityRewardGroupDTO;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 常驻注册奖励服务实现.
*/
@Service
@RequiredArgsConstructor
public class ResidentRegisterRewardServiceImpl implements ResidentRegisterRewardService {
private final ResidentRegisterRewardConfigService residentRegisterRewardConfigService;
private final PropsActivityRewardGroupService propsActivityRewardGroupService;
@Override
public ResidentRegisterRewardConfigCO getConfig(String sysOrigin) {
String normalizedSysOrigin = normalizeSysOrigin(sysOrigin);
ResidentRegisterRewardConfig config = residentRegisterRewardConfigService.query()
.eq(ResidentRegisterRewardConfig::getSysOrigin, normalizedSysOrigin)
.getOne();
ResidentRegisterRewardConfigCO result = new ResidentRegisterRewardConfigCO()
.setSysOrigin(normalizedSysOrigin);
if (config == null) {
return result;
}
return result.setConfigured(Boolean.TRUE)
.setEnabled(Boolean.TRUE.equals(config.getEnabled()))
.setGoldAmount(defaultGoldAmount(config.getGoldAmount()))
.setRewardGroupId(config.getRewardGroupId())
.setRewardGroupName(getRewardGroupName(config.getRewardGroupId()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveConfig(ResidentRegisterRewardConfigSaveCmd cmd) {
validateConfig(cmd);
String normalizedSysOrigin = normalizeSysOrigin(cmd.getSysOrigin());
ResidentRegisterRewardConfig config = residentRegisterRewardConfigService.query()
.eq(ResidentRegisterRewardConfig::getSysOrigin, normalizedSysOrigin)
.getOne();
boolean exists = config != null;
if (!exists) {
config = new ResidentRegisterRewardConfig().setId(IdWorkerUtils.getId());
}
config.setSysOrigin(normalizedSysOrigin)
.setEnabled(Boolean.TRUE.equals(cmd.getEnabled()))
.setGoldAmount(defaultGoldAmount(cmd.getGoldAmount()))
.setRewardGroupId(cmd.getRewardGroupId());
if (exists) {
ResponseAssert.isTrue(CommonErrorCode.UPDATE_FAILURE,
residentRegisterRewardConfigService.updateSelectiveById(config));
return;
}
ResponseAssert.isTrue(CommonErrorCode.SAVE_FAILURE,
residentRegisterRewardConfigService.save(config));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resetConfig(String sysOrigin) {
String normalizedSysOrigin = normalizeSysOrigin(sysOrigin);
residentRegisterRewardConfigService.remove(
Wrappers.<ResidentRegisterRewardConfig>lambdaQuery()
.eq(ResidentRegisterRewardConfig::getSysOrigin, normalizedSysOrigin));
}
private void validateConfig(ResidentRegisterRewardConfigSaveCmd cmd) {
long goldAmount = defaultGoldAmount(cmd.getGoldAmount());
if (Boolean.TRUE.equals(cmd.getEnabled())) {
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
"At least one reward is required when enabled.",
goldAmount > 0 || cmd.getRewardGroupId() != null);
}
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
"goldAmount must be greater than or equal to zero.",
goldAmount >= 0);
}
private String normalizeSysOrigin(String sysOrigin) {
return Objects.toString(sysOrigin, "").trim().toUpperCase();
}
private Long defaultGoldAmount(Long goldAmount) {
return goldAmount == null ? 0L : goldAmount;
}
private String getRewardGroupName(Long rewardGroupId) {
if (rewardGroupId == null) {
return null;
}
try {
PropsActivityRewardGroupDTO group = propsActivityRewardGroupService.getByGroupId(rewardGroupId);
return group == null ? null : group.getName();
} catch (Exception ignore) {
return null;
}
}
}

View File

@ -0,0 +1,31 @@
package com.red.circle.console.app.dto.clienobject.app.activity.register;
import com.red.circle.framework.dto.ClientObject;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 常驻注册奖励配置.
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class ResidentRegisterRewardConfigCO extends ClientObject {
@Serial
private static final long serialVersionUID = 1L;
private Boolean configured = Boolean.FALSE;
private String sysOrigin;
private Boolean enabled = Boolean.FALSE;
private Long goldAmount = 0L;
private Long rewardGroupId;
private String rewardGroupName;
}

View File

@ -0,0 +1,29 @@
package com.red.circle.console.app.dto.cmd.app.activity.register;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 常驻注册奖励配置保存命令.
*/
@Data
@Accessors(chain = true)
public class ResidentRegisterRewardConfigSaveCmd implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank(message = "sysOrigin required.")
private String sysOrigin;
@NotNull(message = "enabled required.")
private Boolean enabled;
private Long goldAmount;
private Long rewardGroupId;
}

View File

@ -0,0 +1,16 @@
package com.red.circle.console.app.service.app.activity;
import com.red.circle.console.app.dto.clienobject.app.activity.register.ResidentRegisterRewardConfigCO;
import com.red.circle.console.app.dto.cmd.app.activity.register.ResidentRegisterRewardConfigSaveCmd;
/**
* 常驻注册奖励管理.
*/
public interface ResidentRegisterRewardService {
ResidentRegisterRewardConfigCO getConfig(String sysOrigin);
void saveConfig(ResidentRegisterRewardConfigSaveCmd cmd);
void resetConfig(String sysOrigin);
}

View File

@ -0,0 +1,10 @@
package com.red.circle.console.infra.database.rds.dao.app.register;
import com.red.circle.console.infra.database.rds.entity.app.register.ResidentRegisterRewardConfig;
import com.red.circle.framework.mybatis.dao.BaseDAO;
/**
* 常驻注册奖励配置 DAO.
*/
public interface ResidentRegisterRewardConfigDAO extends BaseDAO<ResidentRegisterRewardConfig> {
}

View File

@ -0,0 +1,39 @@
package com.red.circle.console.infra.database.rds.entity.app.register;
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 com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 常驻注册奖励配置.
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@TableName("resident_register_reward_config")
public class ResidentRegisterRewardConfig extends TimestampBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("sys_origin")
private String sysOrigin;
@TableField("enabled")
private Boolean enabled;
@TableField("gold_amount")
private Long goldAmount;
@TableField("reward_group_id")
private Long rewardGroupId;
}

View File

@ -0,0 +1,11 @@
package com.red.circle.console.infra.database.rds.service.app.register;
import com.red.circle.console.infra.database.rds.entity.app.register.ResidentRegisterRewardConfig;
import com.red.circle.framework.mybatis.service.BaseService;
/**
* 常驻注册奖励配置服务.
*/
public interface ResidentRegisterRewardConfigService
extends BaseService<ResidentRegisterRewardConfig> {
}

View File

@ -0,0 +1,16 @@
package com.red.circle.console.infra.database.rds.service.app.register.impl;
import com.red.circle.console.infra.database.rds.dao.app.register.ResidentRegisterRewardConfigDAO;
import com.red.circle.console.infra.database.rds.entity.app.register.ResidentRegisterRewardConfig;
import com.red.circle.console.infra.database.rds.service.app.register.ResidentRegisterRewardConfigService;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import org.springframework.stereotype.Service;
/**
* 常驻注册奖励配置服务实现.
*/
@Service
public class ResidentRegisterRewardConfigServiceImpl extends
BaseServiceImpl<ResidentRegisterRewardConfigDAO, ResidentRegisterRewardConfig>
implements ResidentRegisterRewardConfigService {
}

View File

@ -73,11 +73,6 @@ public class SecurityFilter implements Filter {
responseFailureUnauthorized(res);
return;
}
if ("154".equals(reqUserId)) {
responseFailureUnauthorized(res);
return;
}
request.setAttribute(RequestAttrConstant.REQ_UID, userId);
chain.doFilter(request, res);
}

View File

@ -32,8 +32,8 @@ public class TeamSalaryPaymentTask {
/**
* 每天凌晨0点过30秒执行一次处理团队工资结算.
*/
// @Scheduled(cron = "30 1 0 * * ?", zone = "Asia/Riyadh")
// @TaskCacheLock(key = "TEAM_SALARY_PAYMENT_TASK", expireSecond = 10800)
@Scheduled(cron = "30 1 0 * * ?", zone = "Asia/Riyadh")
@TaskCacheLock(key = "TEAM_SALARY_PAYMENT_TASK", expireSecond = 10800)
public void teamSalaryPaymentTask() {
long startTime = System.currentTimeMillis();
log.warn("exec team_salary_payment_task start");

View File

@ -50,7 +50,7 @@ public class CreateAccountAspect {
private final AuthTypeService authTypeService;
private final ArchiveDeviceService archiveDeviceService;
private final RegisterDeviceGateway registerDeviceGateway;
private final UserPropsRewardProcess userPropsRewardProcess;
private final RegisterRewardStreamProducer registerRewardStreamProducer;
private final CreateAccountExpandProcess createAccountExpandProcess;
private final CheckRegisterUserProfileProcess checkRegisterUserProfileProcess;
@ -76,11 +76,10 @@ public class CreateAccountAspect {
cmd.setProfile(userProfile);
}
if (result.checkSuccess() && Objects.nonNull(result.getBody())) {
result.getBody().putRewardProps(
userPropsRewardProcess.newUserReward(
SysOriginPlatformEnum.valueOf(cmd.requireReqSysOrigin()),
cmd.requiredReqUserId(), cmd.getProfile().getCountryId()
)
registerRewardStreamProducer.publish(
SysOriginPlatformEnum.valueOf(cmd.requireReqSysOrigin()),
cmd.requiredReqUserId(),
cmd.getProfile().getCountryId()
);
}

View File

@ -0,0 +1,51 @@
package com.red.circle.other.infra.annotation.aspect;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
* 注册成功后发送异步奖励事件.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RegisterRewardStreamProducer {
private final RedisTemplate<String, Object> redisTemplate;
@Value("${red-circle.activity.register-reward.stream-key:activity:register:reward}")
private String streamKey;
public void publish(SysOriginPlatformEnum sysOrigin, Long userId, Long countryId) {
Map<String, String> payload = new HashMap<>(8);
payload.put("eventId", buildEventId(sysOrigin, userId));
payload.put("sysOrigin", sysOrigin.name());
payload.put("userId", String.valueOf(userId));
if (countryId != null) {
payload.put("countryId", String.valueOf(countryId));
}
payload.put("publishedAt", String.valueOf(System.currentTimeMillis()));
try {
MapRecord<String, String, String> record = StreamRecords.newRecord()
.in(streamKey)
.ofMap(payload);
redisTemplate.opsForStream().add(record);
} catch (Exception e) {
log.error("publish register reward event failed. sysOrigin={} userId={} countryId={}",
sysOrigin, userId, countryId, e);
}
}
private String buildEventId(SysOriginPlatformEnum sysOrigin, Long userId) {
return "REGISTER_REWARD:" + sysOrigin.name() + ":" + userId;
}
}

View File

@ -12,13 +12,10 @@ import com.red.circle.other.inner.enums.material.GiftCurrencyType;
import com.red.circle.other.inner.model.dto.material.GiftConfigDTO;
import com.red.circle.other.inner.model.dto.material.NewUserRewardDTO;
import com.red.circle.other.inner.model.dto.material.PropsDecorationDTO;
import com.red.circle.other.inner.model.dto.material.props.PropsResources;
import com.red.circle.other.inner.model.dto.material.props.UserBackpackProps;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

View File

@ -89,6 +89,12 @@ public class TeamBillCycleClientEndpoint implements TeamBillCycleClientApi {
return ResultResponse.success();
}
@Override
public ResultResponse<Void> refreshUnpaidBills(TeamBillCycleBackQryCmd query) {
teamBillCycleClientService.refreshUnpaidBills(query);
return ResultResponse.success();
}
@Override
public ResultResponse<TeamBillCycleDTO> getBillByTeamId(Long teamId) {
return ResultResponse.success(teamBillCycleClientService.getBillByTeamId(teamId));

View File

@ -39,5 +39,7 @@ public interface TeamBillCycleClientService {
void resetUnpaidBillMemberTarget(TeamMemberTargetResetCmd param);
void refreshUnpaidBills(TeamBillCycleBackQryCmd query);
TeamBillCycleDTO getBillByTeamId(Long teamId);
}

View File

@ -43,6 +43,7 @@ import com.red.circle.other.inner.enums.team.TeamBillCycleStatus;
import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex;
import com.red.circle.other.inner.enums.team.TeamMemberTargetSettlementResult;
import com.red.circle.other.inner.enums.team.TeamMemberTargetStatus;
import com.red.circle.other.inner.model.dto.agency.agency.TeamStatus;
import com.red.circle.other.inner.model.cmd.team.TeamBillByConditionQryCmd;
import com.red.circle.other.inner.model.cmd.team.TeamBillCycleBackQryCmd;
import com.red.circle.other.inner.model.cmd.team.TeamCalculatorWorkCmd;
@ -176,6 +177,32 @@ public class TeamBillCycleClientServiceImpl implements TeamBillCycleClientServic
teamBillCycleService.updateStatus(billId, status, updateUser);
}
@Override
public void refreshUnpaidBills(TeamBillCycleBackQryCmd query) {
if (Objects.isNull(query)
|| StringUtils.isBlank(query.getSysOrigin())
|| StringUtils.isBlank(query.getRegion())) {
return;
}
List<TeamProfile> teamProfiles = Optional.ofNullable(teamProfileService.listRegionAll(query.getRegion()))
.orElse(Lists.newArrayList())
.stream()
.filter(teamProfile -> Objects.equals(TeamStatus.AVAILABLE, teamProfile.getStatus()))
.filter(teamProfile -> Objects.equals(query.getSysOrigin(), teamProfile.getSysOrigin()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(teamProfiles)) {
return;
}
List<List<TeamProfile>> teams = Lists.partition(teamProfiles, 200);
teams.forEach(teamList -> sendTeamSalaryCountMq(teamList.stream()
.map(TeamProfile::getId)
.collect(Collectors.toCollection(HashSet::new))));
}
@Override
public List<TeamBillAnchorStatisticsModelDTO> queryTeamBillByCondition(
TeamBillByConditionQryCmd cmd) {