z
增加灵仙游戏,修复房间排序,数据大屏增加
This commit is contained in:
parent
19675dfe19
commit
ff9b790de5
@ -107,6 +107,14 @@ lucky:
|
||||
app_id: ${LIKEI_GAME_BAISHUN_APP_ID}
|
||||
app_channel: ${LIKEI_GAME_BAISHUN_APP_CHANNEL}
|
||||
|
||||
invite:
|
||||
campaign:
|
||||
go:
|
||||
enabled: ${INVITE_CAMPAIGN_GO_ENABLED:true}
|
||||
base-url: ${INVITE_CAMPAIGN_GO_URL:}
|
||||
internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:}
|
||||
timeout-millis: ${INVITE_CAMPAIGN_GO_TIMEOUT_MILLIS:10000}
|
||||
|
||||
activity:
|
||||
ranking:
|
||||
reward:
|
||||
|
||||
@ -29,6 +29,11 @@ public class LoginLoggerEvent implements Serializable {
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private String invitePeople;
|
||||
|
||||
/**
|
||||
* 邀请活动邀请码(注册时才有).
|
||||
*/
|
||||
private String invitationCode;
|
||||
|
||||
/**
|
||||
* 0.注册登录,1.登陆.
|
||||
*/
|
||||
|
||||
@ -25,6 +25,11 @@ public class CreateAccountCmd extends AppExtCommand {
|
||||
*/
|
||||
private String invitePeople;
|
||||
|
||||
/**
|
||||
* 邀请活动邀请码.
|
||||
*/
|
||||
private String invitationCode;
|
||||
|
||||
/**
|
||||
* 注册类型:MOBILE、FACEBOOK、GOOGLE、APPLE、Snapchat.
|
||||
*/
|
||||
|
||||
@ -5,6 +5,7 @@ import com.red.circle.console.app.dto.clientobject.datav.CountryDashboardMetricC
|
||||
import com.red.circle.console.app.dto.cmd.datav.CountryDashboardQryCmd;
|
||||
import com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO;
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO;
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardRetentionUserDTO;
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardUserCountryDTO;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@ -19,11 +20,13 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bson.Document;
|
||||
@ -32,6 +35,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@ -42,6 +46,7 @@ import org.springframework.stereotype.Service;
|
||||
public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
|
||||
private static final String COLLECTION_GIFT_GIVE_RUNNING_WATER = "gift_give_running_water";
|
||||
private static final String COLLECTION_USER_DAILY_ACTIVE_LOG = "user_daily_active_log";
|
||||
private static final String PERIOD_ALL = "ALL";
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
@ -79,11 +84,16 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
mergeSql(rows, countryDashboardDAO.listLuckyBoxGame(
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.sysOrigin));
|
||||
mergeSql(rows, countryDashboardDAO.listGameUser(
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.sysOrigin));
|
||||
|
||||
mergeMongo(rows, condition, listGiftConsume(condition),
|
||||
(row, amount) -> row.setGiftConsume(add(row.getGiftConsume(), amount)));
|
||||
mergeMongo(rows, condition, listLuckyGiftAnchorShare(condition),
|
||||
(row, amount) -> row.setLuckyGiftAnchorShare(add(row.getLuckyGiftAnchorShare(), amount)));
|
||||
mergeDailyActive(rows, condition, listDailyActive(condition));
|
||||
mergeRetention(rows, condition);
|
||||
|
||||
List<CountryDashboardMetricCO> records = rows.values().stream()
|
||||
.peek(this::calculateFormulaFields)
|
||||
@ -118,8 +128,10 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
row.setDealerRecharge(add(row.getDealerRecharge(), metric.getDealerRecharge()));
|
||||
row.setSalaryExchange(add(row.getSalaryExchange(), metric.getSalaryExchange()));
|
||||
row.setLuckyGiftTotalFlow(add(row.getLuckyGiftTotalFlow(), metric.getLuckyGiftTotalFlow()));
|
||||
row.setLuckyGiftUser(row.getLuckyGiftUser() + safeLong(metric.getLuckyGiftUser()));
|
||||
row.setLuckyGiftPayout(add(row.getLuckyGiftPayout(), metric.getLuckyGiftPayout()));
|
||||
row.setGameTotalFlow(add(row.getGameTotalFlow(), metric.getGameTotalFlow()));
|
||||
row.setGameUser(row.getGameUser() + safeLong(metric.getGameUser()));
|
||||
row.setGamePayout(add(row.getGamePayout(), metric.getGamePayout()));
|
||||
}
|
||||
}
|
||||
@ -145,6 +157,66 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeDailyActive(Map<String, CountryDashboardMetricCO> rows, QueryCondition condition,
|
||||
List<MongoUserDayMetric> metrics) {
|
||||
if (metrics == null || metrics.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, CountryDashboardUserCountryDTO> countryMap = mapUserCountries(
|
||||
metrics.stream().map(MongoUserDayMetric::userId).filter(Objects::nonNull).distinct().toList());
|
||||
Set<String> counted = new HashSet<>();
|
||||
|
||||
for (MongoUserDayMetric metric : metrics) {
|
||||
CountryDashboardUserCountryDTO country = countryMap.get(metric.userId());
|
||||
if (country == null || !matchesCountry(country, condition.countryKeyword)) {
|
||||
continue;
|
||||
}
|
||||
PeriodBucket bucket = condition.toPeriodBucket(metric.day());
|
||||
String dedupeKey = country.getCountryCode() + "|" + bucket.key() + "|" + metric.userId();
|
||||
if (!counted.add(dedupeKey)) {
|
||||
continue;
|
||||
}
|
||||
CountryDashboardMetricCO row = getRow(rows, country.getCountryCode(), country.getCountryName(),
|
||||
bucket.key(), bucket.name());
|
||||
row.setDailyActiveUser(row.getDailyActiveUser() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeRetention(Map<String, CountryDashboardMetricCO> rows, QueryCondition condition) {
|
||||
List<CountryDashboardRetentionUserDTO> cohorts = countryDashboardDAO.listNewUserCohorts(
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.sysOrigin);
|
||||
if (cohorts == null || cohorts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
Set<Long> userIds = new HashSet<>();
|
||||
Set<String> activeDates = new HashSet<>();
|
||||
for (CountryDashboardRetentionUserDTO cohort : cohorts) {
|
||||
if (cohort.getUserId() == null || !hasText(cohort.getRegisterDate())) {
|
||||
continue;
|
||||
}
|
||||
userIds.add(cohort.getUserId());
|
||||
collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 1, today);
|
||||
collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 7, today);
|
||||
collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 30, today);
|
||||
}
|
||||
|
||||
Set<String> activeUserDates = listActiveUserDateSet(userIds, activeDates, condition.sysOrigin);
|
||||
for (CountryDashboardRetentionUserDTO cohort : cohorts) {
|
||||
if (cohort.getUserId() == null || !hasText(cohort.getRegisterDate())) {
|
||||
continue;
|
||||
}
|
||||
CountryDashboardMetricCO row = getRow(rows, cohort.getCountryCode(), cohort.getCountryName(),
|
||||
cohort.getPeriodKey(), cohort.getPeriodName());
|
||||
mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 1, today, activeUserDates);
|
||||
mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 7, today, activeUserDates);
|
||||
mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 30, today, activeUserDates);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MongoMetric> listGiftConsume(QueryCondition condition) {
|
||||
List<Criteria> criteria = baseGiftCriteria(condition);
|
||||
criteria.add(Criteria.where("luckyGift").ne(Boolean.TRUE));
|
||||
@ -163,6 +235,23 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return aggregateGiftAmount(criteria, "$acceptUsers.targetAmount", true);
|
||||
}
|
||||
|
||||
private List<MongoUserDayMetric> listDailyActive(QueryCondition condition) {
|
||||
List<Criteria> criteria = baseActiveCriteria(condition);
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
if (!criteria.isEmpty()) {
|
||||
operations.add(Aggregation.match(new Criteria().andOperator(criteria.toArray(new Criteria[0]))));
|
||||
}
|
||||
operations.add(Aggregation.group("userId", "activeDate"));
|
||||
|
||||
return mongoTemplate.aggregate(Aggregation.newAggregation(operations),
|
||||
COLLECTION_USER_DAILY_ACTIVE_LOG, Document.class)
|
||||
.getMappedResults()
|
||||
.stream()
|
||||
.map(this::toMongoUserDayMetric)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<Criteria> baseGiftCriteria(QueryCondition condition) {
|
||||
List<Criteria> criteria = new ArrayList<>();
|
||||
criteria.add(Criteria.where("refunded").ne(Boolean.TRUE));
|
||||
@ -179,6 +268,27 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private List<Criteria> baseActiveCriteria(QueryCondition condition) {
|
||||
List<Criteria> criteria = new ArrayList<>();
|
||||
Criteria activeDate = Criteria.where("activeDate");
|
||||
boolean hasActiveDateCriteria = false;
|
||||
if (hasText(condition.startDate)) {
|
||||
activeDate = activeDate.gte(condition.startDate);
|
||||
hasActiveDateCriteria = true;
|
||||
}
|
||||
if (hasText(condition.endDate)) {
|
||||
activeDate = activeDate.lte(condition.endDate);
|
||||
hasActiveDateCriteria = true;
|
||||
}
|
||||
if (hasActiveDateCriteria) {
|
||||
criteria.add(activeDate);
|
||||
}
|
||||
if (hasText(condition.sysOrigin)) {
|
||||
criteria.add(Criteria.where("sysOrigin").is(condition.sysOrigin));
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private List<MongoMetric> aggregateGiftAmount(List<Criteria> criteria, String amountPath,
|
||||
boolean unwindAcceptUsers) {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
@ -220,6 +330,79 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return new MongoMetric(userId, day, amount);
|
||||
}
|
||||
|
||||
private MongoUserDayMetric toMongoUserDayMetric(Document document) {
|
||||
Object id = document.get("_id");
|
||||
if (!(id instanceof Document idDoc)) {
|
||||
return null;
|
||||
}
|
||||
Long userId = toLong(idDoc.get("userId"));
|
||||
String day = Objects.toString(idDoc.get("activeDate"), null);
|
||||
if (userId == null || !hasText(day)) {
|
||||
return null;
|
||||
}
|
||||
return new MongoUserDayMetric(userId, day);
|
||||
}
|
||||
|
||||
private void collectRetentionActiveDate(Set<String> activeDates, String registerDate,
|
||||
int retentionDays, LocalDate today) {
|
||||
LocalDate targetDate = LocalDate.parse(registerDate, DATE_FORMATTER).plusDays(retentionDays);
|
||||
if (!targetDate.isAfter(today)) {
|
||||
activeDates.add(DATE_FORMATTER.format(targetDate));
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeRetentionDay(CountryDashboardMetricCO row, Long userId, String registerDate,
|
||||
int retentionDays, LocalDate today, Set<String> activeUserDates) {
|
||||
LocalDate targetDate = LocalDate.parse(registerDate, DATE_FORMATTER).plusDays(retentionDays);
|
||||
if (targetDate.isAfter(today)) {
|
||||
return;
|
||||
}
|
||||
String activeKey = userId + "|" + DATE_FORMATTER.format(targetDate);
|
||||
boolean retained = activeUserDates.contains(activeKey);
|
||||
if (retentionDays == 1) {
|
||||
row.setD1RetentionBaseUser(row.getD1RetentionBaseUser() + 1);
|
||||
row.setD1RetentionUser(row.getD1RetentionUser() + (retained ? 1 : 0));
|
||||
return;
|
||||
}
|
||||
if (retentionDays == 7) {
|
||||
row.setD7RetentionBaseUser(row.getD7RetentionBaseUser() + 1);
|
||||
row.setD7RetentionUser(row.getD7RetentionUser() + (retained ? 1 : 0));
|
||||
return;
|
||||
}
|
||||
row.setD30RetentionBaseUser(row.getD30RetentionBaseUser() + 1);
|
||||
row.setD30RetentionUser(row.getD30RetentionUser() + (retained ? 1 : 0));
|
||||
}
|
||||
|
||||
private Set<String> listActiveUserDateSet(Set<Long> userIds, Set<String> activeDates,
|
||||
String sysOrigin) {
|
||||
Set<String> result = new HashSet<>();
|
||||
if (userIds == null || userIds.isEmpty() || activeDates == null || activeDates.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Long> ids = new ArrayList<>(userIds);
|
||||
for (int start = 0; start < ids.size(); start += USER_COUNTRY_BATCH_SIZE) {
|
||||
int end = Math.min(start + USER_COUNTRY_BATCH_SIZE, ids.size());
|
||||
List<Criteria> criteria = new ArrayList<>();
|
||||
criteria.add(Criteria.where("userId").in(ids.subList(start, end)));
|
||||
criteria.add(Criteria.where("activeDate").in(activeDates));
|
||||
if (hasText(sysOrigin)) {
|
||||
criteria.add(Criteria.where("sysOrigin").is(sysOrigin));
|
||||
}
|
||||
Query query = Query.query(new Criteria().andOperator(criteria.toArray(new Criteria[0])));
|
||||
query.fields().include("userId").include("activeDate");
|
||||
List<Document> documents = mongoTemplate.find(query, Document.class, COLLECTION_USER_DAILY_ACTIVE_LOG);
|
||||
for (Document document : documents) {
|
||||
Long userId = toLong(document.get("userId"));
|
||||
String activeDate = Objects.toString(document.get("activeDate"), null);
|
||||
if (userId != null && hasText(activeDate)) {
|
||||
result.add(userId + "|" + activeDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, CountryDashboardUserCountryDTO> mapUserCountries(Collection<Long> userIds) {
|
||||
Map<Long, CountryDashboardUserCountryDTO> result = new HashMap<>();
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
@ -268,8 +451,15 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
row.setLuckyGiftProfit(safe(row.getLuckyGiftTotalFlow())
|
||||
.subtract(safe(row.getLuckyGiftPayout()))
|
||||
.subtract(safe(row.getLuckyGiftAnchorShare())));
|
||||
row.setLuckyGiftUserRate(percent(row.getLuckyGiftUser(), row.getDailyActiveUser()));
|
||||
row.setLuckyGiftPayoutRate(percent(row.getLuckyGiftPayout(), row.getLuckyGiftTotalFlow()));
|
||||
row.setLuckyGiftProfitRate(percent(row.getLuckyGiftProfit(), row.getLuckyGiftTotalFlow()));
|
||||
row.setGameProfit(safe(row.getGameTotalFlow()).subtract(safe(row.getGamePayout())));
|
||||
row.setGamePayoutRate(percent(row.getGamePayout(), row.getGameTotalFlow()));
|
||||
row.setGameProfitRate(percent(row.getGameProfit(), row.getGameTotalFlow()));
|
||||
row.setD1RetentionRate(percent(row.getD1RetentionUser(), row.getD1RetentionBaseUser()));
|
||||
row.setD7RetentionRate(percent(row.getD7RetentionUser(), row.getD7RetentionBaseUser()));
|
||||
row.setD30RetentionRate(percent(row.getD30RetentionUser(), row.getD30RetentionBaseUser()));
|
||||
}
|
||||
|
||||
private CountryDashboardMetricCO buildTotal(List<CountryDashboardMetricCO> records,
|
||||
@ -281,16 +471,25 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
.setPeriodName("合计");
|
||||
for (CountryDashboardMetricCO row : records) {
|
||||
total.setCountryNewUser(total.getCountryNewUser() + safeLong(row.getCountryNewUser()));
|
||||
total.setDailyActiveUser(total.getDailyActiveUser() + safeLong(row.getDailyActiveUser()));
|
||||
total.setNewUserRecharge(add(total.getNewUserRecharge(), row.getNewUserRecharge()));
|
||||
total.setOfficialRecharge(add(total.getOfficialRecharge(), row.getOfficialRecharge()));
|
||||
total.setDealerRecharge(add(total.getDealerRecharge(), row.getDealerRecharge()));
|
||||
total.setSalaryExchange(add(total.getSalaryExchange(), row.getSalaryExchange()));
|
||||
total.setGiftConsume(add(total.getGiftConsume(), row.getGiftConsume()));
|
||||
total.setLuckyGiftTotalFlow(add(total.getLuckyGiftTotalFlow(), row.getLuckyGiftTotalFlow()));
|
||||
total.setLuckyGiftUser(total.getLuckyGiftUser() + safeLong(row.getLuckyGiftUser()));
|
||||
total.setLuckyGiftPayout(add(total.getLuckyGiftPayout(), row.getLuckyGiftPayout()));
|
||||
total.setLuckyGiftAnchorShare(add(total.getLuckyGiftAnchorShare(), row.getLuckyGiftAnchorShare()));
|
||||
total.setGameTotalFlow(add(total.getGameTotalFlow(), row.getGameTotalFlow()));
|
||||
total.setGameUser(total.getGameUser() + safeLong(row.getGameUser()));
|
||||
total.setGamePayout(add(total.getGamePayout(), row.getGamePayout()));
|
||||
total.setD1RetentionUser(total.getD1RetentionUser() + safeLong(row.getD1RetentionUser()));
|
||||
total.setD1RetentionBaseUser(total.getD1RetentionBaseUser() + safeLong(row.getD1RetentionBaseUser()));
|
||||
total.setD7RetentionUser(total.getD7RetentionUser() + safeLong(row.getD7RetentionUser()));
|
||||
total.setD7RetentionBaseUser(total.getD7RetentionBaseUser() + safeLong(row.getD7RetentionBaseUser()));
|
||||
total.setD30RetentionUser(total.getD30RetentionUser() + safeLong(row.getD30RetentionUser()));
|
||||
total.setD30RetentionBaseUser(total.getD30RetentionBaseUser() + safeLong(row.getD30RetentionBaseUser()));
|
||||
}
|
||||
calculateFormulaFields(total);
|
||||
return total;
|
||||
@ -314,6 +513,10 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
.divide(safeDenominator, 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private static BigDecimal percent(Long numerator, Long denominator) {
|
||||
return percent(BigDecimal.valueOf(safeLong(numerator)), BigDecimal.valueOf(safeLong(denominator)));
|
||||
}
|
||||
|
||||
private static long safeLong(Long value) {
|
||||
return value == null ? 0L : value;
|
||||
}
|
||||
@ -351,6 +554,9 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
private record MongoMetric(Long userId, String day, BigDecimal amount) {
|
||||
}
|
||||
|
||||
private record MongoUserDayMetric(Long userId, String day) {
|
||||
}
|
||||
|
||||
private record PeriodBucket(String key, String name) {
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ public class CountryDashboardMetricCO implements Serializable {
|
||||
|
||||
private Long countryNewUser = 0L;
|
||||
|
||||
private Long dailyActiveUser = 0L;
|
||||
|
||||
private BigDecimal newUserRecharge = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal dealerRecharge = BigDecimal.ZERO;
|
||||
@ -38,8 +40,14 @@ public class CountryDashboardMetricCO implements Serializable {
|
||||
|
||||
private BigDecimal luckyGiftTotalFlow = BigDecimal.ZERO;
|
||||
|
||||
private Long luckyGiftUser = 0L;
|
||||
|
||||
private BigDecimal luckyGiftUserRate = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal luckyGiftPayout = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal luckyGiftPayoutRate = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal luckyGiftAnchorShare = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal luckyGiftProfit = BigDecimal.ZERO;
|
||||
@ -48,10 +56,34 @@ public class CountryDashboardMetricCO implements Serializable {
|
||||
|
||||
private BigDecimal gameTotalFlow = BigDecimal.ZERO;
|
||||
|
||||
private Long gameUser = 0L;
|
||||
|
||||
private BigDecimal gamePayout = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal gamePayoutRate = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal gameProfit = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal gameProfitRate = BigDecimal.ZERO;
|
||||
|
||||
private Long d1RetentionUser = 0L;
|
||||
|
||||
private Long d1RetentionBaseUser = 0L;
|
||||
|
||||
private BigDecimal d1RetentionRate = BigDecimal.ZERO;
|
||||
|
||||
private Long d7RetentionUser = 0L;
|
||||
|
||||
private Long d7RetentionBaseUser = 0L;
|
||||
|
||||
private BigDecimal d7RetentionRate = BigDecimal.ZERO;
|
||||
|
||||
private Long d30RetentionUser = 0L;
|
||||
|
||||
private Long d30RetentionBaseUser = 0L;
|
||||
|
||||
private BigDecimal d30RetentionRate = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 官方直充总额,服务端用于计算总充值.
|
||||
*/
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.red.circle.console.infra.database.rds.dao.datav;
|
||||
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO;
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardRetentionUserDTO;
|
||||
import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardUserCountryDTO;
|
||||
import com.red.circle.console.infra.database.rds.entity.app.IUserBaseInfo;
|
||||
import com.red.circle.framework.mybatis.dao.BaseDAO;
|
||||
@ -70,5 +71,19 @@ public interface CountryDashboardDAO extends BaseDAO<IUserBaseInfo> {
|
||||
@Param("countryKeyword") String countryKeyword,
|
||||
@Param("sysOrigin") String sysOrigin);
|
||||
|
||||
List<CountryDashboardMetricDTO> listGameUser(
|
||||
@Param("periodType") String periodType,
|
||||
@Param("startTime") LocalDateTime startTime,
|
||||
@Param("endTime") LocalDateTime endTime,
|
||||
@Param("countryKeyword") String countryKeyword,
|
||||
@Param("sysOrigin") String sysOrigin);
|
||||
|
||||
List<CountryDashboardRetentionUserDTO> listNewUserCohorts(
|
||||
@Param("periodType") String periodType,
|
||||
@Param("startTime") LocalDateTime startTime,
|
||||
@Param("endTime") LocalDateTime endTime,
|
||||
@Param("countryKeyword") String countryKeyword,
|
||||
@Param("sysOrigin") String sysOrigin);
|
||||
|
||||
List<CountryDashboardUserCountryDTO> listUserCountryByIds(@Param("userIds") Collection<Long> userIds);
|
||||
}
|
||||
|
||||
@ -31,9 +31,13 @@ public class CountryDashboardMetricDTO {
|
||||
|
||||
private BigDecimal luckyGiftTotalFlow;
|
||||
|
||||
private Long luckyGiftUser;
|
||||
|
||||
private BigDecimal luckyGiftPayout;
|
||||
|
||||
private BigDecimal gameTotalFlow;
|
||||
|
||||
private Long gameUser;
|
||||
|
||||
private BigDecimal gamePayout;
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.red.circle.console.infra.database.rds.dto.datav;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 国家数据大屏新增用户留存队列.
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class CountryDashboardRetentionUserDTO {
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String periodKey;
|
||||
|
||||
private String periodName;
|
||||
|
||||
private String registerDate;
|
||||
}
|
||||
@ -117,6 +117,43 @@
|
||||
FROM likei_wallet.wallet_gold_asset_record_12
|
||||
</sql>
|
||||
|
||||
<sql id="WalletGameEventWhere">
|
||||
(
|
||||
t.event_type IN (
|
||||
'GAME_SLOT_MACHINE',
|
||||
'GAME_BARBECUE',
|
||||
'TURNTABLE_GAME',
|
||||
'LUDO_GAME',
|
||||
'GAME_BURST_CRYSTAL',
|
||||
'EGG',
|
||||
'FRUIT_GAME',
|
||||
'DOUBLE_LAYER_FRUIT',
|
||||
'DOUBLE_LAYER_BARBECUE',
|
||||
'GAME_FRUIT_BET',
|
||||
'GAME_FRUIT_AWARD',
|
||||
'GAME_BACK',
|
||||
'GAME_WINNING',
|
||||
'LUDO_VICTORY',
|
||||
'LUDO_REFUND',
|
||||
'GAME_LUDO_REFUND',
|
||||
'USD_GAME_WIN',
|
||||
'USD_GAME_REFUND',
|
||||
'USD_GAME_REFUND_DRAW',
|
||||
'TEEN_PATTI',
|
||||
'BET_TEEN_PATTI',
|
||||
'LOTTERY_NUMBER_BET',
|
||||
'LOTTERY_REWARD'
|
||||
)
|
||||
OR t.event_type LIKE 'BAISHUN_GAME%'
|
||||
OR t.event_type LIKE 'YOMI_GAME%'
|
||||
OR t.event_type LIKE 'HKYS_GAME%'
|
||||
OR t.event_type LIKE 'HOT_GAME%'
|
||||
)
|
||||
AND t.event_type NOT IN ('LUCKY_GIFT', 'LUCKY_GIFT_GOLD_REWARD', 'LUCKY_BOX_GAME', 'NEW_LUCKY_BOX_GAME', 'GAME_RAKE')
|
||||
AND t.event_type NOT LIKE 'GIVE_GIFT%'
|
||||
AND t.event_type NOT LIKE 'ACCEPT_GIFT%'
|
||||
</sql>
|
||||
|
||||
<select id="listNewUsers"
|
||||
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||
SELECT
|
||||
@ -214,6 +251,7 @@
|
||||
<include refid="CountryColumns"/>,
|
||||
<include refid="PeriodColumnsT"/>,
|
||||
IFNULL(SUM(t.pay_amount), 0) AS luckyGiftTotalFlow,
|
||||
COUNT(DISTINCT t.user_id) AS luckyGiftUser,
|
||||
IFNULL(SUM(t.award_amount), 0) AS luckyGiftPayout
|
||||
FROM game_lucky_gift_count t
|
||||
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||
@ -235,40 +273,7 @@
|
||||
<include refid="WalletGoldAssetRecordSource"/>
|
||||
) t
|
||||
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||
WHERE (
|
||||
t.event_type IN (
|
||||
'GAME_SLOT_MACHINE',
|
||||
'GAME_BARBECUE',
|
||||
'TURNTABLE_GAME',
|
||||
'LUDO_GAME',
|
||||
'GAME_BURST_CRYSTAL',
|
||||
'EGG',
|
||||
'FRUIT_GAME',
|
||||
'DOUBLE_LAYER_FRUIT',
|
||||
'DOUBLE_LAYER_BARBECUE',
|
||||
'GAME_FRUIT_BET',
|
||||
'GAME_FRUIT_AWARD',
|
||||
'GAME_BACK',
|
||||
'GAME_WINNING',
|
||||
'LUDO_VICTORY',
|
||||
'LUDO_REFUND',
|
||||
'GAME_LUDO_REFUND',
|
||||
'USD_GAME_WIN',
|
||||
'USD_GAME_REFUND',
|
||||
'USD_GAME_REFUND_DRAW',
|
||||
'TEEN_PATTI',
|
||||
'BET_TEEN_PATTI',
|
||||
'LOTTERY_NUMBER_BET',
|
||||
'LOTTERY_REWARD'
|
||||
)
|
||||
OR t.event_type LIKE 'BAISHUN_GAME%'
|
||||
OR t.event_type LIKE 'YOMI_GAME%'
|
||||
OR t.event_type LIKE 'HKYS_GAME%'
|
||||
OR t.event_type LIKE 'HOT_GAME%'
|
||||
)
|
||||
AND t.event_type NOT IN ('LUCKY_GIFT', 'LUCKY_GIFT_GOLD_REWARD', 'LUCKY_BOX_GAME', 'NEW_LUCKY_BOX_GAME', 'GAME_RAKE')
|
||||
AND t.event_type NOT LIKE 'GIVE_GIFT%'
|
||||
AND t.event_type NOT LIKE 'ACCEPT_GIFT%'
|
||||
WHERE <include refid="WalletGameEventWhere"/>
|
||||
<include refid="UserWhere"/>
|
||||
<include refid="SourceTimeWhere"/>
|
||||
<include refid="SourceSysOriginWhere"/>
|
||||
@ -291,6 +296,43 @@
|
||||
GROUP BY countryCode, countryName, periodKey, periodName
|
||||
</select>
|
||||
|
||||
<select id="listGameUser"
|
||||
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||
SELECT
|
||||
<include refid="CountryColumns"/>,
|
||||
<include refid="PeriodColumnsT"/>,
|
||||
COUNT(DISTINCT t.user_id) AS gameUser
|
||||
FROM (
|
||||
SELECT t.user_id, t.sys_origin, t.create_time
|
||||
FROM (
|
||||
<include refid="WalletGoldAssetRecordSource"/>
|
||||
) t
|
||||
WHERE <include refid="WalletGameEventWhere"/>
|
||||
UNION ALL
|
||||
SELECT user_id, sys_origin, create_time
|
||||
FROM game_lucky_box_count
|
||||
) t
|
||||
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||
WHERE 1 = 1
|
||||
<include refid="UserWhere"/>
|
||||
<include refid="SourceTimeWhere"/>
|
||||
<include refid="SourceSysOriginWhere"/>
|
||||
GROUP BY countryCode, countryName, periodKey, periodName
|
||||
</select>
|
||||
|
||||
<select id="listNewUserCohorts"
|
||||
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardRetentionUserDTO">
|
||||
SELECT
|
||||
u.id AS userId,
|
||||
<include refid="CountryColumns"/>,
|
||||
<include refid="PeriodColumnsU"/>,
|
||||
DATE_FORMAT(u.create_time, '%Y-%m-%d') AS registerDate
|
||||
FROM user_base_info u
|
||||
WHERE 1 = 1
|
||||
<include refid="UserWhere"/>
|
||||
<include refid="UserTimeWhere"/>
|
||||
</select>
|
||||
|
||||
<select id="listUserCountryByIds"
|
||||
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardUserCountryDTO">
|
||||
SELECT
|
||||
|
||||
@ -68,8 +68,9 @@ public class RoomVoiceDiscoverQryExe {
|
||||
private final GameLudoService gameLudoService;
|
||||
private final UserProfileGateway userProfileGateway;
|
||||
private final CountryCodeAliasSupport countryCodeAliasSupport;
|
||||
|
||||
|
||||
private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:*";
|
||||
private static final String MEMBER_QUANTITY_EXT_KEY = "memberQuantity";
|
||||
private static final int DISCOVERY_LIMIT = 50;
|
||||
|
||||
public List<RoomVoiceProfileCO> execute(AppExtCommand cmd) {
|
||||
@ -147,6 +148,7 @@ public class RoomVoiceDiscoverQryExe {
|
||||
roomList = mergeRoomList(roomList, roomVoiceProfileCommon.listRoomVoiceProfilesV2(fallbackRoomManagers),
|
||||
DISCOVERY_LIMIT);
|
||||
}
|
||||
roomList = sortByMemberQuantityDesc(roomList);
|
||||
|
||||
if (CollectionUtils.isEmpty(roomList)) {
|
||||
return CollectionUtils.newArrayList();
|
||||
@ -168,6 +170,33 @@ public class RoomVoiceDiscoverQryExe {
|
||||
return roomList;
|
||||
}
|
||||
|
||||
private List<RoomVoiceProfileCO> sortByMemberQuantityDesc(List<RoomVoiceProfileCO> roomList) {
|
||||
if (CollectionUtils.isEmpty(roomList)) {
|
||||
return CollectionUtils.newArrayList();
|
||||
}
|
||||
return roomList.stream()
|
||||
.sorted(Comparator.comparingInt(this::roomMemberQuantity).reversed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private int roomMemberQuantity(RoomVoiceProfileCO room) {
|
||||
if (Objects.isNull(room) || Objects.isNull(room.getExtValues())) {
|
||||
return 0;
|
||||
}
|
||||
Object value = room.getExtValues().get(MEMBER_QUANTITY_EXT_KEY);
|
||||
if (value instanceof Number number) {
|
||||
return Math.max(number.intValue(), 0);
|
||||
}
|
||||
if (value instanceof String text) {
|
||||
try {
|
||||
return Math.max(Integer.parseInt(text), 0);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String discoveryCacheKey(String sysOrigin, String region, boolean allRegionWhiteListUser) {
|
||||
return allRegionWhiteListUser ? sysOrigin + region + ":ALL_REGION" : sysOrigin + region;
|
||||
}
|
||||
|
||||
@ -146,8 +146,11 @@ public class GameListConfigQryExe {
|
||||
|
||||
private List<GameListConfigCO> getGameListConfigs(GameListQryCmd cmd) {
|
||||
|
||||
String activeBaishunProfile = gameListConfigService.getActiveBaishunProfile(cmd.requireReqSysOrigin());
|
||||
String cacheCategory = String.valueOf(cmd.getCategory()) + ":BAISHUN:" + activeBaishunProfile;
|
||||
String reqSysOrigin = cmd.requireReqSysOrigin();
|
||||
String activeBaishunProfile = gameListConfigService.getActiveBaishunProfile(reqSysOrigin);
|
||||
String activeLingxianProfile = gameListConfigService.getActiveLingxianProfile(reqSysOrigin);
|
||||
String cacheCategory = String.valueOf(cmd.getCategory()) + ":BAISHUN:" + activeBaishunProfile
|
||||
+ ":LINGXIAN:" + activeLingxianProfile;
|
||||
String data = gameListCacheService.listCache(cmd.requireReqSysOrigin(), cacheCategory,
|
||||
sysOrigin -> JacksonUtils.toJson(sysConfigAppConvertor.toListGameListConfigCO(
|
||||
gameListConfigService.listShowcaseConfig(cmd.requireReqSysOrigin(), cmd.getCategory(), cmd.getRoomSessionId()))), cmd.getReqAppIntel().getVersion());
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
package com.red.circle.other.app.common.invite;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.red.circle.other.infra.config.InviteCampaignGoConfig;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InviteCampaignGoClient {
|
||||
|
||||
private final InviteCampaignGoConfig inviteCampaignGoConfig;
|
||||
|
||||
public RegisterBindCodeResponse bindRegisterCode(RegisterBindCodeRequest request) {
|
||||
if (!inviteCampaignGoConfig.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String baseUrl = Objects.toString(inviteCampaignGoConfig.getBaseUrl(), "").trim();
|
||||
if (baseUrl.isEmpty()) {
|
||||
log.warn("Invite campaign go baseUrl is blank, skip register bind. userId={}",
|
||||
request.getInviteeUserId());
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponse response = HttpUtil.createPost(trimRightSlash(baseUrl)
|
||||
+ "/internal/invite-campaign/register-bind-code")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("X-Internal-Token", Objects.toString(inviteCampaignGoConfig.getInternalToken(), ""))
|
||||
.timeout(Objects.requireNonNullElse(inviteCampaignGoConfig.getTimeoutMillis(), 10000))
|
||||
.body(JSON.toJSONString(request))
|
||||
.execute();
|
||||
|
||||
String responseBody = response.body();
|
||||
if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
||||
throw new IllegalStateException("Invite campaign go http error, status=" + response.getStatus()
|
||||
+ ", body=" + responseBody);
|
||||
}
|
||||
if (responseBody == null || responseBody.trim().isEmpty()) {
|
||||
throw new IllegalStateException("Invite campaign go response is empty");
|
||||
}
|
||||
|
||||
JSONObject root = JSON.parseObject(responseBody);
|
||||
if (root == null) {
|
||||
throw new IllegalStateException("Invite campaign go response is invalid");
|
||||
}
|
||||
if (root.containsKey("status") && !root.getBooleanValue("status")) {
|
||||
throw new IllegalStateException("Invite campaign go business error: " + root.getString("message"));
|
||||
}
|
||||
|
||||
JSONObject body = root.getJSONObject("body");
|
||||
return Objects.nonNull(body)
|
||||
? body.toJavaObject(RegisterBindCodeResponse.class)
|
||||
: root.toJavaObject(RegisterBindCodeResponse.class);
|
||||
}
|
||||
|
||||
private String trimRightSlash(String value) {
|
||||
while (value.endsWith("/")) {
|
||||
value = value.substring(0, value.length() - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class RegisterBindCodeRequest {
|
||||
private String inviteCode;
|
||||
private String sysOrigin;
|
||||
private Long inviteeUserId;
|
||||
private String clientIp;
|
||||
private String deviceFingerprint;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class RegisterBindCodeResponse {
|
||||
private Long relationId;
|
||||
private Boolean eligible;
|
||||
private String blockReason;
|
||||
private Long inviterUserId;
|
||||
private String inviteCode;
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@ public interface UserProfileAppConvertor {
|
||||
default LoginLoggerEvent toLoginLoggerEventRegister(CreateAccountCmd cmd) {
|
||||
LoginLoggerEvent event = toLoginLoggerEvent(cmd);
|
||||
event.setInvitePeople(cmd.getInvitePeople());
|
||||
event.setInvitationCode(cmd.getInvitationCode());
|
||||
event.setEventType(0);
|
||||
event.setAuthType(cmd.getType());
|
||||
return event;
|
||||
|
||||
@ -18,6 +18,7 @@ import com.red.circle.mq.business.model.event.approval.CensorProfileEvent;
|
||||
import com.red.circle.mq.business.model.event.user.LoginLoggerEvent;
|
||||
import com.red.circle.mq.rocket.business.producer.CensorMqMessage;
|
||||
import com.red.circle.mq.rocket.business.streams.LoginRegisterLoggerSink;
|
||||
import com.red.circle.other.app.common.invite.InviteCampaignGoClient;
|
||||
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
|
||||
import com.red.circle.other.domain.gateway.user.ability.RegisterDeviceGateway;
|
||||
import com.red.circle.other.domain.model.user.UserConstant;
|
||||
@ -71,6 +72,7 @@ public class LoginLoggerListener implements MessageListener {
|
||||
private final RegisterDeviceGateway registerDeviceGateway;
|
||||
private final EnumConfigCacheService enumConfigCacheService;
|
||||
private final InviteUserSummaryService inviteUserSummaryService;
|
||||
private final InviteCampaignGoClient inviteCampaignGoClient;
|
||||
private final LatestMobileDeviceService latestMobileDeviceService;
|
||||
private final InviteUserRegisterService inviteUserRegisterService;
|
||||
private final InviteActivityCountService inviteActivityCountService;
|
||||
@ -149,6 +151,7 @@ public class LoginLoggerListener implements MessageListener {
|
||||
);
|
||||
approvalProfile(userProfile);
|
||||
processLogin(event);
|
||||
handleInviteCampaignCode(event);
|
||||
handleInvitePeople(event);
|
||||
}
|
||||
|
||||
@ -171,6 +174,34 @@ public class LoginLoggerListener implements MessageListener {
|
||||
? ApprovalStatusEnum.NONE : ApprovalStatusEnum.PENDING);
|
||||
}
|
||||
|
||||
private void handleInviteCampaignCode(LoginLoggerEvent event) {
|
||||
String invitationCode = Objects.toString(event.getInvitationCode(), "").trim();
|
||||
if (invitationCode.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (Objects.isNull(event.getUserId()) || Objects.toString(event.getSysOrigin(), "").trim()
|
||||
.isEmpty()) {
|
||||
log.warn("invite campaign register bind skipped, missing userId or sysOrigin. event={}",
|
||||
JacksonUtils.toJson(event));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
InviteCampaignGoClient.RegisterBindCodeResponse response = inviteCampaignGoClient.bindRegisterCode(
|
||||
new InviteCampaignGoClient.RegisterBindCodeRequest()
|
||||
.setInviteCode(invitationCode)
|
||||
.setSysOrigin(event.getSysOrigin())
|
||||
.setInviteeUserId(event.getUserId())
|
||||
.setClientIp(Objects.toString(event.getIp(), ""))
|
||||
.setDeviceFingerprint(Objects.toString(event.getImei(), "")));
|
||||
log.info("invite campaign register bind result, userId={}, inviteCode={}, response={}",
|
||||
event.getUserId(), invitationCode, JacksonUtils.toJson(response));
|
||||
} catch (Exception ex) {
|
||||
log.warn("invite campaign register bind failed, userId={}, inviteCode={}",
|
||||
event.getUserId(), invitationCode, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInvitePeople(LoginLoggerEvent event) {
|
||||
log.warn("event {}", event);
|
||||
if (Objects.isNull(event.getInvitePeople())) {
|
||||
|
||||
@ -86,8 +86,10 @@ class RoomVoiceDiscoverQryExeTest {
|
||||
fallbackRoomManager.setId(102L);
|
||||
fallbackRoomManager.setUserId(202L);
|
||||
|
||||
RoomVoiceProfileCO activeRoomCo = new RoomVoiceProfileCO().setId(101L).setUserId(201L);
|
||||
RoomVoiceProfileCO fallbackRoomCo = new RoomVoiceProfileCO().setId(102L).setUserId(202L);
|
||||
RoomVoiceProfileCO activeRoomCo = new RoomVoiceProfileCO().setId(101L).setUserId(201L)
|
||||
.putRoomMemberQuantity(0);
|
||||
RoomVoiceProfileCO fallbackRoomCo = new RoomVoiceProfileCO().setId(102L).setUserId(202L)
|
||||
.putRoomMemberQuantity(2);
|
||||
|
||||
when(roomRegionFilterCommon.requireRegionConfig(1L)).thenReturn(currentRegion);
|
||||
when(userProfileGateway.getByUserId(1L)).thenReturn(userProfile);
|
||||
@ -112,12 +114,12 @@ class RoomVoiceDiscoverQryExeTest {
|
||||
when(userSVipGateway.checkSVipIdentity(anySet())).thenReturn(
|
||||
Map.of(201L, SVIPLevelEnum.NONE, 202L, SVIPLevelEnum.NONE)
|
||||
);
|
||||
when(rocketStatusCacheService.batchGetRocketStatus(List.of(101L, 102L))).thenReturn(Map.of());
|
||||
when(gameLudoService.listGameCoverByRoomIds(List.of(101L, 102L))).thenReturn(Map.of());
|
||||
when(rocketStatusCacheService.batchGetRocketStatus(List.of(102L, 101L))).thenReturn(Map.of());
|
||||
when(gameLudoService.listGameCoverByRoomIds(List.of(102L, 101L))).thenReturn(Map.of());
|
||||
|
||||
List<RoomVoiceProfileCO> roomList = exe.execute(cmd);
|
||||
|
||||
assertEquals(List.of(activeRoomCo, fallbackRoomCo), roomList);
|
||||
assertEquals(List.of(fallbackRoomCo, activeRoomCo), roomList);
|
||||
verify(activeVoiceRoomService).listByRegion("YOLO", "AE", 50);
|
||||
verify(activeVoiceRoomService, never()).listDiscover("YOLO", true, "AE", null, 50);
|
||||
}
|
||||
@ -162,7 +164,8 @@ class RoomVoiceDiscoverQryExeTest {
|
||||
cmd.setReqSysOrigin(ReqSysOrigin.of("YOLO", "YOLO"));
|
||||
|
||||
RegionConfig currentRegion = new RegionConfig().setRegionCode("AE").setCountryCodes("AE,SA");
|
||||
UserProfile userProfile = new UserProfile().setCountryCode("AE");
|
||||
UserProfile userProfile = new UserProfile();
|
||||
userProfile.setCountryCode("AE");
|
||||
ActiveVoiceRoom activeVoiceRoom = new ActiveVoiceRoom().setId(101L).setUserId(201L);
|
||||
RoomProfileManager fallbackRoomManager = new RoomProfileManager();
|
||||
fallbackRoomManager.setId(102L);
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
package com.red.circle.other.infra.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "invite.campaign.go")
|
||||
public class InviteCampaignGoConfig {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
private String internalToken;
|
||||
|
||||
private Integer timeoutMillis = 10000;
|
||||
}
|
||||
@ -68,6 +68,7 @@ public interface UserProfileInfraConvertor {
|
||||
default LoginLoggerEvent toLoginLoggerEventRegister(CreateAccountCmd cmd) {
|
||||
LoginLoggerEvent event = toLoginLoggerEvent(cmd);
|
||||
event.setInvitePeople(cmd.getInvitePeople());
|
||||
event.setInvitationCode(cmd.getInvitationCode());
|
||||
event.setEventType(0);
|
||||
event.setAuthType(cmd.getType());
|
||||
return event;
|
||||
|
||||
@ -124,11 +124,11 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService {
|
||||
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
if (Objects.nonNull(qryCmd.getOrderByStrategy())) {
|
||||
query.with(
|
||||
Sort.by(Stream.of("fixedWeights", "weights", "onlineQuantity").map(Order::desc)
|
||||
.toArray(Order[]::new)));
|
||||
}
|
||||
if (Objects.nonNull(qryCmd.getOrderByStrategy())) {
|
||||
query.with(
|
||||
Sort.by(Stream.of("onlineQuantity", "fixedWeights", "weights").map(Order::desc)
|
||||
.toArray(Order[]::new)));
|
||||
}
|
||||
|
||||
return mongoTemplate.find(query.limit(qryCmd.getLimit()), ActiveVoiceRoom.class);
|
||||
}
|
||||
@ -172,13 +172,13 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService {
|
||||
).as("fixedWeightsScore")
|
||||
.and("weights").as("weights"),
|
||||
|
||||
// 3. 排序逻辑
|
||||
Aggregation.sort(Sort.by(
|
||||
Sort.Order.desc("regionMatchScore"),
|
||||
Sort.Order.desc("fixedWeightsScore"), // 如果区域不匹配,fixedWeights 为 0,不影响排序
|
||||
Sort.Order.desc("weights"),
|
||||
Sort.Order.desc("onlineQuantity")
|
||||
)),
|
||||
// 3. 排序逻辑
|
||||
Aggregation.sort(Sort.by(
|
||||
Sort.Order.desc("onlineQuantity"),
|
||||
Sort.Order.desc("regionMatchScore"),
|
||||
Sort.Order.desc("fixedWeightsScore"), // 如果区域不匹配,fixedWeights 为 0,不影响排序
|
||||
Sort.Order.desc("weights")
|
||||
)),
|
||||
|
||||
// 4. 分页
|
||||
Aggregation.skip(0),
|
||||
@ -196,9 +196,9 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService {
|
||||
Criteria.where("sysOrigin").is(sysOrigin)
|
||||
.and("region").is(region)
|
||||
).with(Sort.by(
|
||||
Sort.Order.desc("onlineQuantity"),
|
||||
Sort.Order.desc("fixedWeights"),
|
||||
Sort.Order.desc("weights"),
|
||||
Sort.Order.desc("onlineQuantity")
|
||||
Sort.Order.desc("weights")
|
||||
));
|
||||
|
||||
if (Objects.nonNull(limit)) {
|
||||
|
||||
@ -322,8 +322,7 @@ public class RoomProfileManagerServiceImpl implements RoomProfileManagerService
|
||||
|
||||
Query query = Query.query(criteria)
|
||||
.with(Sort.by(
|
||||
Sort.Order.desc("activeTime"),
|
||||
Sort.Order.desc("updateTime"),
|
||||
Sort.Order.desc("counter.memberCount"),
|
||||
Sort.Order.desc("createTime"),
|
||||
Sort.Order.desc("id")
|
||||
));
|
||||
|
||||
@ -41,6 +41,11 @@ public interface GameListConfigService extends BaseService<GameListConfig> {
|
||||
* 获取百顺当前启用的配置档案.
|
||||
*/
|
||||
String getActiveBaishunProfile(String sysOrigin);
|
||||
|
||||
/**
|
||||
* 获取灵仙当前启用的配置档案.
|
||||
*/
|
||||
String getActiveLingxianProfile(String sysOrigin);
|
||||
|
||||
/**
|
||||
* 查询游戏信息
|
||||
|
||||
@ -16,6 +16,7 @@ import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -30,7 +31,9 @@ public class GameListConfigServiceImpl extends
|
||||
BaseServiceImpl<GameListConfigDAO, GameListConfig> implements GameListConfigService {
|
||||
|
||||
private static final String BAISHUN_GAME_ORIGIN = "BAISHUN";
|
||||
private static final String LINGXIAN_GAME_ORIGIN = "LINGXIAN";
|
||||
private static final String BAISHUN_PROFILE_PROD = "PROD";
|
||||
private static final String LINGXIAN_PROFILE_PROD = "PROD";
|
||||
private static final String BAISHUN_ACTIVE_PROFILE_EXISTS_SQL = """
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
@ -56,11 +59,43 @@ public class GameListConfigServiceImpl extends
|
||||
OR %s
|
||||
)
|
||||
""".formatted(BAISHUN_ACTIVE_PROFILE_EXISTS_SQL);
|
||||
private static final String LINGXIAN_ACTIVE_PROFILE_EXISTS_SQL = """
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_game_list_vendor_ext ext
|
||||
WHERE ext.game_list_config_id = sys_game_list_config.id
|
||||
AND ext.sys_origin = sys_game_list_config.sys_origin
|
||||
AND ext.vendor_type = 'LINGXIAN'
|
||||
AND ext.enabled = 1
|
||||
AND ext.profile = COALESCE((
|
||||
SELECT provider.profile
|
||||
FROM lingxian_provider_config provider
|
||||
WHERE provider.sys_origin = sys_game_list_config.sys_origin
|
||||
AND provider.active = 1
|
||||
ORDER BY provider.update_time DESC
|
||||
LIMIT 1
|
||||
), 'PROD')
|
||||
)
|
||||
""";
|
||||
private static final String ACTIVE_PROFILE_FILTER_SQL = """
|
||||
(
|
||||
game_origin IS NULL
|
||||
OR (
|
||||
game_origin <> 'BAISHUN'
|
||||
AND game_origin <> 'LINGXIAN'
|
||||
)
|
||||
OR %s
|
||||
OR %s
|
||||
)
|
||||
""".formatted(BAISHUN_ACTIVE_PROFILE_EXISTS_SQL, LINGXIAN_ACTIVE_PROFILE_EXISTS_SQL);
|
||||
|
||||
private final BaishunProviderConfigService baishunProviderConfigService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public GameListConfigServiceImpl(BaishunProviderConfigService baishunProviderConfigService) {
|
||||
public GameListConfigServiceImpl(BaishunProviderConfigService baishunProviderConfigService,
|
||||
JdbcTemplate jdbcTemplate) {
|
||||
this.baishunProviderConfigService = baishunProviderConfigService;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,7 +105,7 @@ public class GameListConfigServiceImpl extends
|
||||
.eq(StringUtils.isNotBlank(category), GameListConfig::getCategory, category)
|
||||
.ne(StringUtils.isBlank(roomId), GameListConfig::getCategory, "HIDDEN")
|
||||
.eq(GameListConfig::getShowcase, Boolean.TRUE)
|
||||
.apply(BAISHUN_ACTIVE_PROFILE_FILTER_SQL)
|
||||
.apply(ACTIVE_PROFILE_FILTER_SQL)
|
||||
.orderByDesc(TimestampBaseEntity::getCreateTime, GameListConfig::getSort)
|
||||
.list();
|
||||
}
|
||||
@ -94,6 +129,7 @@ public class GameListConfigServiceImpl extends
|
||||
.eq(GameListConfig::getSysOrigin, sysOrigin)
|
||||
.eq(GameListConfig::getShowcase, Boolean.TRUE)
|
||||
.apply(isBaishunGameOrigin(gameOrigin), BAISHUN_ACTIVE_PROFILE_EXISTS_SQL)
|
||||
.apply(isLingxianGameOrigin(gameOrigin), LINGXIAN_ACTIVE_PROFILE_EXISTS_SQL)
|
||||
.list();
|
||||
if (CollectionUtils.isEmpty(gameListConfigs)) {
|
||||
return "";
|
||||
@ -140,7 +176,7 @@ public class GameListConfigServiceImpl extends
|
||||
query.getGameOrigin())
|
||||
.like(StringUtils.isNotBlank(query.getRegion()), GameListConfig::getRegions,
|
||||
query.getRegion())
|
||||
.apply(BAISHUN_ACTIVE_PROFILE_FILTER_SQL)
|
||||
.apply(ACTIVE_PROFILE_FILTER_SQL)
|
||||
.orderByDesc(GameListConfig::getSort)
|
||||
.page(query.getPageQuery());
|
||||
}
|
||||
@ -157,12 +193,32 @@ public class GameListConfigServiceImpl extends
|
||||
return normalizeBaishunProfile(config.getProfile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActiveLingxianProfile(String sysOrigin) {
|
||||
if (StringUtils.isBlank(sysOrigin)) {
|
||||
return LINGXIAN_PROFILE_PROD;
|
||||
}
|
||||
try {
|
||||
String profile = jdbcTemplate.queryForObject("""
|
||||
SELECT profile
|
||||
FROM lingxian_provider_config
|
||||
WHERE sys_origin = ?
|
||||
AND active = 1
|
||||
ORDER BY update_time DESC
|
||||
LIMIT 1
|
||||
""", String.class, sysOrigin);
|
||||
return normalizeProfile(profile, LINGXIAN_PROFILE_PROD);
|
||||
} catch (Exception e) {
|
||||
return LINGXIAN_PROFILE_PROD;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameListConfig getByGameCode(String gameCode, String sysOrigin) {
|
||||
return query()
|
||||
.eq(GameListConfig::getGameCode, gameCode)
|
||||
.eq(GameListConfig::getSysOrigin, sysOrigin)
|
||||
.apply(BAISHUN_ACTIVE_PROFILE_FILTER_SQL)
|
||||
.apply(ACTIVE_PROFILE_FILTER_SQL)
|
||||
.last(PageConstant.LIMIT_ONE)
|
||||
.getOne();
|
||||
}
|
||||
@ -177,6 +233,7 @@ public class GameListConfigServiceImpl extends
|
||||
.eq(GameListConfig::getGameOrigin, gameOrigin)
|
||||
.eq(GameListConfig::getSysOrigin, sysOrigin)
|
||||
.apply(isBaishunGameOrigin(gameOrigin), BAISHUN_ACTIVE_PROFILE_EXISTS_SQL)
|
||||
.apply(isLingxianGameOrigin(gameOrigin), LINGXIAN_ACTIVE_PROFILE_EXISTS_SQL)
|
||||
.last(PageConstant.LIMIT_ONE)
|
||||
.getOne();
|
||||
}
|
||||
@ -196,6 +253,11 @@ public class GameListConfigServiceImpl extends
|
||||
&& BAISHUN_GAME_ORIGIN.equalsIgnoreCase(gameOrigin.trim());
|
||||
}
|
||||
|
||||
private boolean isLingxianGameOrigin(String gameOrigin) {
|
||||
return StringUtils.isNotBlank(gameOrigin)
|
||||
&& LINGXIAN_GAME_ORIGIN.equalsIgnoreCase(gameOrigin.trim());
|
||||
}
|
||||
|
||||
private String normalizeBaishunProfile(String profile) {
|
||||
if (StringUtils.isBlank(profile)) {
|
||||
return BAISHUN_PROFILE_PROD;
|
||||
@ -207,4 +269,16 @@ public class GameListConfigServiceImpl extends
|
||||
default -> value;
|
||||
};
|
||||
}
|
||||
|
||||
private String normalizeProfile(String profile, String defaultProfile) {
|
||||
if (StringUtils.isBlank(profile)) {
|
||||
return defaultProfile;
|
||||
}
|
||||
String value = profile.trim().toUpperCase(Locale.ROOT);
|
||||
return switch (value) {
|
||||
case "PRODUCT", "PRODUCTION", "ONLINE", "OFFICIAL", "FORMAL" -> defaultProfile;
|
||||
case "TESTING", "SANDBOX", "DEV" -> "TEST";
|
||||
default -> value;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
package com.red.circle.other.infra.database.mongo.service.live.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport;
|
||||
import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom;
|
||||
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
|
||||
import java.util.List;
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
@ -43,8 +43,30 @@ class ActiveVoiceRoomServiceImplTest {
|
||||
assertEquals(50, query.getLimit());
|
||||
|
||||
Document sortObject = query.getSortObject();
|
||||
assertIterableEquals(List.of("onlineQuantity", "fixedWeights", "weights"),
|
||||
sortObject.keySet());
|
||||
assertEquals(-1, sortObject.getInteger("onlineQuantity"));
|
||||
assertEquals(-1, sortObject.getInteger("fixedWeights"));
|
||||
assertEquals(-1, sortObject.getInteger("weights"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listOps_shouldUseOnlineQuantityFirstForDiscoverySort() {
|
||||
ArgumentCaptor<Query> queryCaptor = ArgumentCaptor.forClass(Query.class);
|
||||
when(mongoTemplate.find(queryCaptor.capture(), eq(ActiveVoiceRoom.class))).thenReturn(List.of());
|
||||
|
||||
AppActiveVoiceRoomQryCmd cmd = new AppActiveVoiceRoomQryCmd();
|
||||
cmd.setSysOrigin("YOLO");
|
||||
cmd.setLimit(50);
|
||||
cmd.setOrderByStrategy(AppActiveVoiceRoomQryCmd.OrderByStrategy.APP_DISCOVERY);
|
||||
|
||||
service.listOps(cmd);
|
||||
|
||||
Document sortObject = queryCaptor.getValue().getSortObject();
|
||||
assertIterableEquals(List.of("onlineQuantity", "fixedWeights", "weights"),
|
||||
sortObject.keySet());
|
||||
assertEquals(-1, sortObject.getInteger("onlineQuantity"));
|
||||
assertEquals(-1, sortObject.getInteger("fixedWeights"));
|
||||
assertEquals(-1, sortObject.getInteger("weights"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ class RoomProfileManagerServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSelectiveLimitByCountryCodes_shouldQueryIncludedCountriesWithRecentSort() {
|
||||
void listSelectiveLimitByCountryCodes_shouldQueryIncludedCountriesWithMemberCountSort() {
|
||||
ArgumentCaptor<Query> queryCaptor = ArgumentCaptor.forClass(Query.class);
|
||||
when(mongoTemplate.find(queryCaptor.capture(), eq(RoomProfileManager.class))).thenReturn(
|
||||
List.of()
|
||||
@ -126,8 +126,7 @@ class RoomProfileManagerServiceImplTest {
|
||||
assertEquals(50, query.getLimit());
|
||||
|
||||
Document sortObject = query.getSortObject();
|
||||
assertEquals(-1, sortObject.getInteger("activeTime"));
|
||||
assertEquals(-1, sortObject.getInteger("updateTime"));
|
||||
assertEquals(-1, sortObject.getInteger("counter.memberCount"));
|
||||
assertEquals(-1, sortObject.getInteger("createTime"));
|
||||
assertEquals(-1, sortObject.getInteger("id"));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user