增加灵仙游戏,修复房间排序,数据大屏增加
This commit is contained in:
hy001 2026-04-24 17:10:36 +08:00
parent 19675dfe19
commit ff9b790de5
23 changed files with 689 additions and 68 deletions

View File

@ -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:

View File

@ -29,6 +29,11 @@ public class LoginLoggerEvent implements Serializable {
@JsonSerialize(using = ToStringSerializer.class)
private String invitePeople;
/**
* 邀请活动邀请码注册时才有.
*/
private String invitationCode;
/**
* 0.注册登录1.登陆.
*/

View File

@ -25,6 +25,11 @@ public class CreateAccountCmd extends AppExtCommand {
*/
private String invitePeople;
/**
* 邀请活动邀请码.
*/
private String invitationCode;
/**
* 注册类型:MOBILEFACEBOOKGOOGLEAPPLESnapchat.
*/

View File

@ -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) {
}

View File

@ -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;
/**
* 官方直充总额服务端用于计算总充值.
*/

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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())) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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)) {

View File

@ -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")
));

View File

@ -41,6 +41,11 @@ public interface GameListConfigService extends BaseService<GameListConfig> {
* 获取百顺当前启用的配置档案.
*/
String getActiveBaishunProfile(String sysOrigin);
/**
* 获取灵仙当前启用的配置档案.
*/
String getActiveLingxianProfile(String sysOrigin);
/**
* 查询游戏信息

View File

@ -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;
};
}
}

View File

@ -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"));
}
}

View File

@ -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"));
}