From b69c58bddf3df099d75c9ee912158ce8af053101 Mon Sep 17 00:00:00 2001 From: hy001 Date: Fri, 24 Apr 2026 14:05:02 +0800 Subject: [PATCH] 1 --- .../adapter/app/user/DatavRestController.java | 23 +- .../datav/CountryDashboardServiceImpl.java | 454 ++++++++++++++++++ .../datav/CountryDashboardCO.java | 30 ++ .../datav/CountryDashboardMetricCO.java | 57 +++ .../dto/cmd/datav/CountryDashboardQryCmd.java | 42 ++ .../app/datav/CountryDashboardService.java | 12 + .../rds/dao/datav/CountryDashboardDAO.java | 74 +++ .../dto/datav/CountryDashboardMetricDTO.java | 39 ++ .../datav/CountryDashboardUserCountryDTO.java | 18 + .../dao/datav/CountryDashboardDAO.xml | 266 ++++++++++ .../src/main/resources/application.yml | 1 + .../service/game/BaishunRuntimeConfig.java | 1 + .../game/BaishunRuntimeConfigResolver.java | 11 +- .../BaishunRuntimeConfigResolverTest.java | 3 +- .../entity/game/BaishunProviderConfig.java | 3 + 15 files changed, 1024 insertions(+), 10 deletions(-) create mode 100644 rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java create mode 100644 rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardCO.java create mode 100644 rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardMetricCO.java create mode 100644 rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/cmd/datav/CountryDashboardQryCmd.java create mode 100644 rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardService.java create mode 100644 rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java create mode 100644 rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java create mode 100644 rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardUserCountryDTO.java create mode 100644 rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml diff --git a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java index 358798b3..5e7a77ac 100644 --- a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java +++ b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java @@ -1,7 +1,10 @@ package com.red.circle.console.adapter.app.user; import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; -import com.red.circle.console.app.service.app.user.DatavService; +import com.red.circle.console.app.dto.clientobject.datav.CountryDashboardCO; +import com.red.circle.console.app.dto.cmd.datav.CountryDashboardQryCmd; +import com.red.circle.console.app.service.app.datav.CountryDashboardService; +import com.red.circle.console.app.service.app.user.DatavService; import com.red.circle.framework.web.controller.BaseController; import com.red.circle.other.inner.model.cmd.user.OnlineCountQryCmd; import com.red.circle.other.inner.model.dto.user.ActiveUserCountryCodeDTO; @@ -24,7 +27,8 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping(value = "/datav", produces = MediaType.APPLICATION_JSON_VALUE) public class DatavRestController extends BaseController { - private final DatavService userExpandService; + private final DatavService userExpandService; + private final CountryDashboardService countryDashboardService; @GetMapping("/active/user-country-code") public List latestActiveUserCountryCode() { @@ -37,8 +41,13 @@ public class DatavRestController extends BaseController { } @GetMapping("/online/room/count") - public String onlineRoomCount(@RequestParam("sysOrigin") String sysOrigin) { - return Objects.toString(userExpandService.count(sysOrigin)); - } - -} + public String onlineRoomCount(@RequestParam("sysOrigin") String sysOrigin) { + return Objects.toString(userExpandService.count(sysOrigin)); + } + + @GetMapping("/country-dashboard") + public CountryDashboardCO countryDashboard(CountryDashboardQryCmd cmd) { + return countryDashboardService.query(cmd); + } + +} diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java new file mode 100644 index 00000000..615a9dfe --- /dev/null +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java @@ -0,0 +1,454 @@ +package com.red.circle.console.app.service.app.datav; + +import com.red.circle.console.app.dto.clientobject.datav.CountryDashboardCO; +import com.red.circle.console.app.dto.clientobject.datav.CountryDashboardMetricCO; +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.CountryDashboardUserCountryDTO; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.temporal.WeekFields; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import lombok.RequiredArgsConstructor; +import org.bson.Document; +import org.bson.types.Decimal128; +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.stereotype.Service; + +/** + * 国家数据大屏. + */ +@Service +@RequiredArgsConstructor +public class CountryDashboardServiceImpl implements CountryDashboardService { + + private static final String COLLECTION_GIFT_GIVE_RUNNING_WATER = "gift_give_running_water"; + 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"); + private static final int USER_COUNTRY_BATCH_SIZE = 1000; + + private final CountryDashboardDAO countryDashboardDAO; + private final MongoTemplate mongoTemplate; + + @Override + public CountryDashboardCO query(CountryDashboardQryCmd cmd) { + QueryCondition condition = QueryCondition.of(cmd); + Map rows = new LinkedHashMap<>(); + + mergeSql(rows, countryDashboardDAO.listNewUsers( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listNewUserRecharge( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listOfficialRecharge( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listDealerRecharge( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listSalaryExchange( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listLuckyGift( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listWalletGame( + condition.periodType, condition.startTime, condition.endTime, + condition.countryKeyword, condition.sysOrigin)); + mergeSql(rows, countryDashboardDAO.listLuckyBoxGame( + 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))); + + List records = rows.values().stream() + .peek(this::calculateFormulaFields) + .sorted(Comparator.comparing(CountryDashboardMetricCO::getPeriodKey, + Comparator.nullsLast(Comparator.reverseOrder())) + .thenComparing(CountryDashboardMetricCO::getCountryCode, + Comparator.nullsLast(String::compareTo))) + .toList(); + + CountryDashboardMetricCO total = buildTotal(records, condition.periodType); + + return new CountryDashboardCO() + .setPeriodType(condition.periodType) + .setStartDate(condition.startDate) + .setEndDate(condition.endDate) + .setComputedAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) + .setTotal(total) + .setRecords(records); + } + + private void mergeSql(Map rows, + List metrics) { + if (metrics == null || metrics.isEmpty()) { + return; + } + for (CountryDashboardMetricDTO metric : metrics) { + CountryDashboardMetricCO row = getRow(rows, metric.getCountryCode(), metric.getCountryName(), + metric.getPeriodKey(), metric.getPeriodName()); + row.setCountryNewUser(row.getCountryNewUser() + safeLong(metric.getCountryNewUser())); + row.setNewUserRecharge(add(row.getNewUserRecharge(), metric.getNewUserRecharge())); + row.setOfficialRecharge(add(row.getOfficialRecharge(), metric.getOfficialRecharge())); + row.setDealerRecharge(add(row.getDealerRecharge(), metric.getDealerRecharge())); + row.setSalaryExchange(add(row.getSalaryExchange(), metric.getSalaryExchange())); + row.setLuckyGiftTotalFlow(add(row.getLuckyGiftTotalFlow(), metric.getLuckyGiftTotalFlow())); + row.setLuckyGiftPayout(add(row.getLuckyGiftPayout(), metric.getLuckyGiftPayout())); + row.setGameTotalFlow(add(row.getGameTotalFlow(), metric.getGameTotalFlow())); + row.setGamePayout(add(row.getGamePayout(), metric.getGamePayout())); + } + } + + private void mergeMongo(Map rows, QueryCondition condition, + List metrics, BiConsumer merger) { + if (metrics == null || metrics.isEmpty()) { + return; + } + + Map countryMap = mapUserCountries( + metrics.stream().map(MongoMetric::userId).filter(Objects::nonNull).distinct().toList()); + + for (MongoMetric metric : metrics) { + CountryDashboardUserCountryDTO country = countryMap.get(metric.userId()); + if (country == null || !matchesCountry(country, condition.countryKeyword)) { + continue; + } + PeriodBucket bucket = condition.toPeriodBucket(metric.day()); + CountryDashboardMetricCO row = getRow(rows, country.getCountryCode(), country.getCountryName(), + bucket.key(), bucket.name()); + merger.accept(row, metric.amount()); + } + } + + private List listGiftConsume(QueryCondition condition) { + List criteria = baseGiftCriteria(condition); + criteria.add(Criteria.where("luckyGift").ne(Boolean.TRUE)); + criteria.add(Criteria.where("originId").regex("^\\d+$")); + criteria.add(new Criteria().orOperator( + Criteria.where("dynamicContentId").exists(false), + Criteria.where("dynamicContentId").is(null), + Criteria.where("dynamicContentId").is("") + )); + return aggregateGiftAmount(criteria, "$giftValue.actualAmount", false); + } + + private List listLuckyGiftAnchorShare(QueryCondition condition) { + List criteria = baseGiftCriteria(condition); + criteria.add(Criteria.where("luckyGift").is(Boolean.TRUE)); + return aggregateGiftAmount(criteria, "$acceptUsers.targetAmount", true); + } + + private List baseGiftCriteria(QueryCondition condition) { + List criteria = new ArrayList<>(); + criteria.add(Criteria.where("refunded").ne(Boolean.TRUE)); + criteria.add(Criteria.where("userId").ne(null)); + if (condition.startTime != null) { + criteria.add(Criteria.where("createTime").gte(Timestamp.valueOf(condition.startTime))); + } + if (condition.endTime != null) { + criteria.add(Criteria.where("createTime").lt(Timestamp.valueOf(condition.endTime))); + } + if (hasText(condition.sysOrigin)) { + criteria.add(Criteria.where("sysOrigin").is(condition.sysOrigin)); + } + return criteria; + } + + private List aggregateGiftAmount(List criteria, String amountPath, + boolean unwindAcceptUsers) { + List operations = new ArrayList<>(); + operations.add(Aggregation.match(new Criteria().andOperator(criteria.toArray(new Criteria[0])))); + if (unwindAcceptUsers) { + operations.add(Aggregation.unwind("acceptUsers")); + } + operations.add(projectGiftDayAmount(amountPath)); + operations.add(Aggregation.group("userId", "day").sum("amount").as("amount")); + + return mongoTemplate.aggregate(Aggregation.newAggregation(operations), + COLLECTION_GIFT_GIVE_RUNNING_WATER, Document.class) + .getMappedResults() + .stream() + .map(this::toMongoMetric) + .filter(Objects::nonNull) + .toList(); + } + + private AggregationOperation projectGiftDayAmount(String amountPath) { + return context -> new Document("$project", + new Document("userId", "$userId") + .append("amount", amountPath) + .append("day", new Document("$dateToString", + new Document("format", "%Y-%m-%d").append("date", "$createTime")))); + } + + private MongoMetric toMongoMetric(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("day"), null); + BigDecimal amount = toBigDecimal(document.get("amount")); + if (userId == null || !hasText(day) || amount == null) { + return null; + } + return new MongoMetric(userId, day, amount); + } + + private Map mapUserCountries(Collection userIds) { + Map result = new HashMap<>(); + if (userIds == null || userIds.isEmpty()) { + return result; + } + List 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 batch = countryDashboardDAO.listUserCountryByIds( + ids.subList(start, end)); + if (batch == null) { + continue; + } + for (CountryDashboardUserCountryDTO item : batch) { + result.put(item.getUserId(), item); + } + } + return result; + } + + private boolean matchesCountry(CountryDashboardUserCountryDTO country, String keyword) { + if (!hasText(keyword)) { + return true; + } + String countryCode = Objects.toString(country.getCountryCode(), ""); + String countryName = Objects.toString(country.getCountryName(), ""); + return countryCode.equals(keyword) || countryName.contains(keyword); + } + + private CountryDashboardMetricCO getRow(Map rows, + String countryCode, String countryName, String periodKey, String periodName) { + String fixedCountryCode = hasText(countryCode) ? countryCode : "UNKNOWN"; + String fixedCountryName = hasText(countryName) ? countryName : fixedCountryCode; + String fixedPeriodKey = hasText(periodKey) ? periodKey : PERIOD_ALL; + String fixedPeriodName = hasText(periodName) ? periodName : "全部"; + return rows.computeIfAbsent(fixedCountryCode + "|" + fixedPeriodKey, + key -> new CountryDashboardMetricCO() + .setCountryCode(fixedCountryCode) + .setCountryName(fixedCountryName) + .setPeriodKey(fixedPeriodKey) + .setPeriodName(fixedPeriodName)); + } + + private void calculateFormulaFields(CountryDashboardMetricCO row) { + row.setTotalRecharge(add(row.getOfficialRecharge(), row.getDealerRecharge())); + row.setLuckyGiftProfit(safe(row.getLuckyGiftTotalFlow()) + .subtract(safe(row.getLuckyGiftPayout())) + .subtract(safe(row.getLuckyGiftAnchorShare()))); + row.setGameProfit(safe(row.getGameTotalFlow()).subtract(safe(row.getGamePayout()))); + } + + private CountryDashboardMetricCO buildTotal(List records, + String periodType) { + CountryDashboardMetricCO total = new CountryDashboardMetricCO() + .setCountryCode("ALL") + .setCountryName("全部国家") + .setPeriodKey(PERIOD_ALL.equals(periodType) ? PERIOD_ALL : "TOTAL") + .setPeriodName("合计"); + for (CountryDashboardMetricCO row : records) { + total.setCountryNewUser(total.getCountryNewUser() + safeLong(row.getCountryNewUser())); + 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.setLuckyGiftPayout(add(total.getLuckyGiftPayout(), row.getLuckyGiftPayout())); + total.setLuckyGiftAnchorShare(add(total.getLuckyGiftAnchorShare(), row.getLuckyGiftAnchorShare())); + total.setGameTotalFlow(add(total.getGameTotalFlow(), row.getGameTotalFlow())); + total.setGamePayout(add(total.getGamePayout(), row.getGamePayout())); + } + calculateFormulaFields(total); + return total; + } + + private static BigDecimal add(BigDecimal left, BigDecimal right) { + return safe(left).add(safe(right)); + } + + private static BigDecimal safe(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + + private static long safeLong(Long value) { + return value == null ? 0L : value; + } + + private static BigDecimal toBigDecimal(Object value) { + if (value == null) { + return BigDecimal.ZERO; + } + if (value instanceof BigDecimal bigDecimal) { + return bigDecimal; + } + if (value instanceof Decimal128 decimal128) { + return decimal128.bigDecimalValue(); + } + if (value instanceof Number number) { + return new BigDecimal(number.toString()); + } + return new BigDecimal(Objects.toString(value, "0")); + } + + private static Long toLong(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value == null) { + return null; + } + return Long.valueOf(Objects.toString(value)); + } + + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } + + private record MongoMetric(Long userId, String day, BigDecimal amount) { + } + + private record PeriodBucket(String key, String name) { + } + + private static final class QueryCondition { + + private final String periodType; + private final String startDate; + private final String endDate; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final String countryKeyword; + private final String sysOrigin; + + private QueryCondition(String periodType, String startDate, String endDate, + LocalDateTime startTime, LocalDateTime endTime, String countryKeyword, + String sysOrigin) { + this.periodType = periodType; + this.startDate = startDate; + this.endDate = endDate; + this.startTime = startTime; + this.endTime = endTime; + this.countryKeyword = countryKeyword; + this.sysOrigin = sysOrigin; + } + + private static QueryCondition of(CountryDashboardQryCmd cmd) { + String periodType = normalizePeriodType(cmd == null ? null : cmd.getPeriodType()); + LocalDate start = parseDate(cmd == null ? null : cmd.getStartDate()); + LocalDate end = parseDate(cmd == null ? null : cmd.getEndDate()); + + if (!PERIOD_ALL.equals(periodType) && start == null && end == null) { + LocalDate today = LocalDate.now(); + switch (periodType) { + case "WEEK" -> { + start = today.with(DayOfWeek.MONDAY); + end = today.with(DayOfWeek.SUNDAY); + } + case "MONTH" -> { + start = today.withDayOfMonth(1); + end = today.withDayOfMonth(today.lengthOfMonth()); + } + default -> { + start = today; + end = today; + } + } + } + + if (start != null && end == null) { + end = start; + } + if (start == null && end != null) { + start = end; + } + if (start != null && end != null && end.isBefore(start)) { + throw new IllegalArgumentException("endDate must not be before startDate."); + } + + return new QueryCondition( + periodType, + start == null ? null : DATE_FORMATTER.format(start), + end == null ? null : DATE_FORMATTER.format(end), + start == null ? null : start.atStartOfDay(), + end == null ? null : end.plusDays(1).atStartOfDay(), + trimToNull(cmd == null ? null : cmd.getCountryKeyword()), + trimToNull(cmd == null ? null : cmd.getSysOrigin()) + ); + } + + private PeriodBucket toPeriodBucket(String day) { + if (PERIOD_ALL.equals(periodType)) { + return new PeriodBucket(PERIOD_ALL, "全部"); + } + LocalDate date = LocalDate.parse(day, DATE_FORMATTER); + if ("MONTH".equals(periodType)) { + return new PeriodBucket(date.format(MONTH_FORMATTER), date.format(MONTH_FORMATTER)); + } + if ("WEEK".equals(periodType)) { + WeekFields weekFields = WeekFields.ISO; + int weekYear = date.get(weekFields.weekBasedYear()); + int week = date.get(weekFields.weekOfWeekBasedYear()); + String key = String.format(Locale.ROOT, "%d-W%02d", weekYear, week); + return new PeriodBucket(key, key); + } + return new PeriodBucket(day, day); + } + + private static String normalizePeriodType(String periodType) { + String value = trimToNull(periodType); + if (value == null) { + return "DAY"; + } + value = value.toUpperCase(Locale.ROOT); + return switch (value) { + case "DAY", "WEEK", "MONTH", "ALL" -> value; + default -> "DAY"; + }; + } + + private static LocalDate parseDate(String value) { + String fixed = trimToNull(value); + return fixed == null ? null : LocalDate.parse(fixed, DATE_FORMATTER); + } + + private static String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } + } +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardCO.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardCO.java new file mode 100644 index 00000000..73a16c81 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardCO.java @@ -0,0 +1,30 @@ +package com.red.circle.console.app.dto.clientobject.datav; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 国家数据大屏结果. + */ +@Data +@Accessors(chain = true) +public class CountryDashboardCO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String periodType; + + private String startDate; + + private String endDate; + + private String computedAt; + + private CountryDashboardMetricCO total; + + private List records; +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardMetricCO.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardMetricCO.java new file mode 100644 index 00000000..101b1cc0 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clientobject/datav/CountryDashboardMetricCO.java @@ -0,0 +1,57 @@ +package com.red.circle.console.app.dto.clientobject.datav; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 国家数据大屏单行指标. + */ +@Data +@Accessors(chain = true) +public class CountryDashboardMetricCO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String countryCode; + + private String countryName; + + private String periodKey; + + private String periodName; + + private Long countryNewUser = 0L; + + private BigDecimal newUserRecharge = BigDecimal.ZERO; + + private BigDecimal dealerRecharge = BigDecimal.ZERO; + + private BigDecimal salaryExchange = BigDecimal.ZERO; + + private BigDecimal totalRecharge = BigDecimal.ZERO; + + private BigDecimal giftConsume = BigDecimal.ZERO; + + private BigDecimal luckyGiftTotalFlow = BigDecimal.ZERO; + + private BigDecimal luckyGiftPayout = BigDecimal.ZERO; + + private BigDecimal luckyGiftAnchorShare = BigDecimal.ZERO; + + private BigDecimal luckyGiftProfit = BigDecimal.ZERO; + + private BigDecimal gameTotalFlow = BigDecimal.ZERO; + + private BigDecimal gamePayout = BigDecimal.ZERO; + + private BigDecimal gameProfit = BigDecimal.ZERO; + + /** + * 官方直充总额,服务端用于计算总充值. + */ + private BigDecimal officialRecharge = BigDecimal.ZERO; +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/cmd/datav/CountryDashboardQryCmd.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/cmd/datav/CountryDashboardQryCmd.java new file mode 100644 index 00000000..03a25e22 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/cmd/datav/CountryDashboardQryCmd.java @@ -0,0 +1,42 @@ +package com.red.circle.console.app.dto.cmd.datav; + +import java.io.Serial; +import java.io.Serializable; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 国家数据大屏查询. + */ +@Data +@Accessors(chain = true) +public class CountryDashboardQryCmd implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * DAY / WEEK / MONTH / ALL. + */ + private String periodType; + + /** + * yyyy-MM-dd. + */ + private String startDate; + + /** + * yyyy-MM-dd. + */ + private String endDate; + + /** + * 国家编码或国家名称关键字. + */ + private String countryKeyword; + + /** + * 系统来源. + */ + private String sysOrigin; +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardService.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardService.java new file mode 100644 index 00000000..38d882cb --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardService.java @@ -0,0 +1,12 @@ +package com.red.circle.console.app.service.app.datav; + +import com.red.circle.console.app.dto.clientobject.datav.CountryDashboardCO; +import com.red.circle.console.app.dto.cmd.datav.CountryDashboardQryCmd; + +/** + * 国家数据大屏. + */ +public interface CountryDashboardService { + + CountryDashboardCO query(CountryDashboardQryCmd cmd); +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java new file mode 100644 index 00000000..25669183 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java @@ -0,0 +1,74 @@ +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.CountryDashboardUserCountryDTO; +import com.red.circle.console.infra.database.rds.entity.app.IUserBaseInfo; +import com.red.circle.framework.mybatis.dao.BaseDAO; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +/** + * 国家数据大屏聚合. + */ +public interface CountryDashboardDAO extends BaseDAO { + + List listNewUsers( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listNewUserRecharge( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listOfficialRecharge( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listDealerRecharge( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listSalaryExchange( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listLuckyGift( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listWalletGame( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listLuckyBoxGame( + @Param("periodType") String periodType, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("countryKeyword") String countryKeyword, + @Param("sysOrigin") String sysOrigin); + + List listUserCountryByIds(@Param("userIds") Collection userIds); +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java new file mode 100644 index 00000000..2f6b1a82 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java @@ -0,0 +1,39 @@ +package com.red.circle.console.infra.database.rds.dto.datav; + +import java.math.BigDecimal; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 国家数据大屏 SQL 聚合结果. + */ +@Data +@Accessors(chain = true) +public class CountryDashboardMetricDTO { + + private String countryCode; + + private String countryName; + + private String periodKey; + + private String periodName; + + private Long countryNewUser; + + private BigDecimal newUserRecharge; + + private BigDecimal officialRecharge; + + private BigDecimal dealerRecharge; + + private BigDecimal salaryExchange; + + private BigDecimal luckyGiftTotalFlow; + + private BigDecimal luckyGiftPayout; + + private BigDecimal gameTotalFlow; + + private BigDecimal gamePayout; +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardUserCountryDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardUserCountryDTO.java new file mode 100644 index 00000000..836927be --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardUserCountryDTO.java @@ -0,0 +1,18 @@ +package com.red.circle.console.infra.database.rds.dto.datav; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 用户国家归属. + */ +@Data +@Accessors(chain = true) +public class CountryDashboardUserCountryDTO { + + private Long userId; + + private String countryCode; + + private String countryName; +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml new file mode 100644 index 00000000..df7aa31a --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml @@ -0,0 +1,266 @@ + + + + + + NULLIF(u.country_code, '') AS countryCode, + NULLIF(u.country_name, '') AS countryName + + + + + DATE_FORMAT(t.create_time, '%Y-%m-%d') + DATE_FORMAT(t.create_time, '%x-W%v') + DATE_FORMAT(t.create_time, '%Y-%m') + 'ALL' + + AS periodKey, + + DATE_FORMAT(t.create_time, '%Y-%m-%d') + DATE_FORMAT(t.create_time, '%x-W%v') + DATE_FORMAT(t.create_time, '%Y-%m') + '全部' + + AS periodName + + + + + DATE_FORMAT(u.create_time, '%Y-%m-%d') + DATE_FORMAT(u.create_time, '%x-W%v') + DATE_FORMAT(u.create_time, '%Y-%m') + 'ALL' + + AS periodKey, + + DATE_FORMAT(u.create_time, '%Y-%m-%d') + DATE_FORMAT(u.create_time, '%x-W%v') + DATE_FORMAT(u.create_time, '%Y-%m') + '全部' + + AS periodName + + + + AND (u.is_del = 0 OR u.is_del IS NULL) + + AND ( + u.country_code = #{countryKeyword} + OR u.country_name LIKE CONCAT('%', #{countryKeyword}, '%') + ) + + + AND u.origin_sys = #{sysOrigin} + + + + + + AND t.create_time >= #{startTime} + + + AND t.create_time < #{endTime} + + + + + + AND u.create_time >= #{startTime} + + + AND u.create_time < #{endTime} + + + + + + AND t.sys_origin = #{sysOrigin} + + + + + + + + + + + + + + + + + + + + + diff --git a/rc-service/rc-service-console/console-start/src/main/resources/application.yml b/rc-service/rc-service-console/console-start/src/main/resources/application.yml index 00fd0a24..373c98a2 100644 --- a/rc-service/rc-service-console/console-start/src/main/resources/application.yml +++ b/rc-service/rc-service-console/console-start/src/main/resources/application.yml @@ -72,4 +72,5 @@ red-circle: - /console/datav/active/user-country-code - /console/datav/online/user/count - /console/datav/online/room/count + - /console/datav/country-dashboard - /console/user/base/info/im/sig diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfig.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfig.java index 1d858af9..8257a1d7 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfig.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfig.java @@ -12,6 +12,7 @@ public class BaishunRuntimeConfig { String sysOrigin; String platformBaseUrl; + String luckyGiftUrl; String appId; String appName; String appChannel; diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolver.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolver.java index d583b92c..8521cf9c 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolver.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolver.java @@ -52,7 +52,9 @@ public class BaishunRuntimeConfigResolver { ApiAccount resolved = new ApiAccount(); resolved.setStandardId(base.getStandardId()); - resolved.setUrl(defaultIfBlank(resolveLuckyGiftApiUrl(config.getPlatformBaseUrl()), base.getUrl())); + resolved.setUrl(defaultIfBlank( + resolveLuckyGiftApiUrl(config.getLuckyGiftUrl(), config.getPlatformBaseUrl()), + base.getUrl())); resolved.setAppId(defaultIfBlank(config.getAppId(), base.getAppId())); resolved.setAppChannel(defaultIfBlank(config.getAppChannel(), base.getAppChannel())); resolved.setAppKey(defaultIfBlank(config.getAppKey(), base.getAppKey())); @@ -72,6 +74,7 @@ public class BaishunRuntimeConfigResolver { return base.toBuilder() .sysOrigin(normalizeSysOrigin(defaultIfBlank(row.getSysOrigin(), base.getSysOrigin()))) .platformBaseUrl(defaultIfBlank(row.getPlatformBaseUrl(), base.getPlatformBaseUrl())) + .luckyGiftUrl(defaultIfBlank(row.getLuckyGiftUrl(), base.getLuckyGiftUrl())) .appId(row.getAppId() != null && row.getAppId() > 0 ? String.valueOf(row.getAppId()) : base.getAppId()) .appName(defaultIfBlank(row.getAppName(), base.getAppName())) .appChannel(defaultIfBlank(row.getAppChannel(), base.getAppChannel())) @@ -84,7 +87,11 @@ public class BaishunRuntimeConfigResolver { .build(); } - private String resolveLuckyGiftApiUrl(String platformBaseUrl) { + private String resolveLuckyGiftApiUrl(String luckyGiftUrl, String platformBaseUrl) { + String configuredUrl = trim(luckyGiftUrl); + if (StringUtils.isNotBlank(configuredUrl)) { + return trimTrailingSlash(configuredUrl); + } String baseUrl = trim(platformBaseUrl); if (StringUtils.isBlank(baseUrl)) { return null; diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolverTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolverTest.java index aabfe8c7..c0a2e78c 100644 --- a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolverTest.java +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/service/game/BaishunRuntimeConfigResolverTest.java @@ -35,6 +35,7 @@ class BaishunRuntimeConfigResolverTest { when(providerConfigService.getBySysOrigin("LIKEI")).thenReturn(new BaishunProviderConfig() .setSysOrigin("LIKEI") .setPlatformBaseUrl("https://provider.example.com/") + .setLuckyGiftUrl("https://lucky.example.com/lucky_gift/start_game") .setAppId(4581045902L) .setAppChannel("yumiparty") .setAppKey("provider-key")); @@ -42,7 +43,7 @@ class BaishunRuntimeConfigResolverTest { ApiAccount resolved = resolver.resolveLuckyGiftApiAccount("LIKEI", 99L); assertEquals(99L, resolved.getStandardId()); - assertEquals("https://provider.example.com/lucky_gift/start_game", resolved.getUrl()); + assertEquals("https://lucky.example.com/lucky_gift/start_game", resolved.getUrl()); assertEquals("4581045902", resolved.getAppId()); assertEquals("yumiparty", resolved.getAppChannel()); assertEquals("provider-key", resolved.getAppKey()); diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/BaishunProviderConfig.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/BaishunProviderConfig.java index b4db4c44..4da522ef 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/BaishunProviderConfig.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/game/BaishunProviderConfig.java @@ -37,6 +37,9 @@ public class BaishunProviderConfig extends TimestampBaseEntity { @TableField("platform_base_url") private String platformBaseUrl; + @TableField("lucky_gift_url") + private String luckyGiftUrl; + @TableField("app_id") private Long appId;