1
This commit is contained in:
parent
28ae34e2fa
commit
b69c58bddf
@ -1,6 +1,9 @@
|
|||||||
package com.red.circle.console.adapter.app.user;
|
package com.red.circle.console.adapter.app.user;
|
||||||
|
|
||||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||||
|
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.console.app.service.app.user.DatavService;
|
||||||
import com.red.circle.framework.web.controller.BaseController;
|
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.cmd.user.OnlineCountQryCmd;
|
||||||
@ -25,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class DatavRestController extends BaseController {
|
public class DatavRestController extends BaseController {
|
||||||
|
|
||||||
private final DatavService userExpandService;
|
private final DatavService userExpandService;
|
||||||
|
private final CountryDashboardService countryDashboardService;
|
||||||
|
|
||||||
@GetMapping("/active/user-country-code")
|
@GetMapping("/active/user-country-code")
|
||||||
public List<ActiveUserCountryCodeDTO> latestActiveUserCountryCode() {
|
public List<ActiveUserCountryCodeDTO> latestActiveUserCountryCode() {
|
||||||
@ -41,4 +45,9 @@ public class DatavRestController extends BaseController {
|
|||||||
return Objects.toString(userExpandService.count(sysOrigin));
|
return Objects.toString(userExpandService.count(sysOrigin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/country-dashboard")
|
||||||
|
public CountryDashboardCO countryDashboard(CountryDashboardQryCmd cmd) {
|
||||||
|
return countryDashboardService.query(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<String, CountryDashboardMetricCO> 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<CountryDashboardMetricCO> 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<String, CountryDashboardMetricCO> rows,
|
||||||
|
List<CountryDashboardMetricDTO> 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<String, CountryDashboardMetricCO> rows, QueryCondition condition,
|
||||||
|
List<MongoMetric> metrics, BiConsumer<CountryDashboardMetricCO, BigDecimal> merger) {
|
||||||
|
if (metrics == null || metrics.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, CountryDashboardUserCountryDTO> 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<MongoMetric> listGiftConsume(QueryCondition condition) {
|
||||||
|
List<Criteria> 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<MongoMetric> listLuckyGiftAnchorShare(QueryCondition condition) {
|
||||||
|
List<Criteria> criteria = baseGiftCriteria(condition);
|
||||||
|
criteria.add(Criteria.where("luckyGift").is(Boolean.TRUE));
|
||||||
|
return aggregateGiftAmount(criteria, "$acceptUsers.targetAmount", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Criteria> baseGiftCriteria(QueryCondition condition) {
|
||||||
|
List<Criteria> 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<MongoMetric> aggregateGiftAmount(List<Criteria> criteria, String amountPath,
|
||||||
|
boolean unwindAcceptUsers) {
|
||||||
|
List<AggregationOperation> 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<Long, CountryDashboardUserCountryDTO> mapUserCountries(Collection<Long> userIds) {
|
||||||
|
Map<Long, CountryDashboardUserCountryDTO> result = new HashMap<>();
|
||||||
|
if (userIds == null || userIds.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<CountryDashboardUserCountryDTO> 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<String, CountryDashboardMetricCO> 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<CountryDashboardMetricCO> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<CountryDashboardMetricCO> records;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
@ -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<IUserBaseInfo> {
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listNewUsers(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listNewUserRecharge(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listOfficialRecharge(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listDealerRecharge(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listSalaryExchange(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listLuckyGift(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listWalletGame(
|
||||||
|
@Param("periodType") String periodType,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime,
|
||||||
|
@Param("countryKeyword") String countryKeyword,
|
||||||
|
@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
List<CountryDashboardMetricDTO> listLuckyBoxGame(
|
||||||
|
@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);
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -0,0 +1,266 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO">
|
||||||
|
|
||||||
|
<sql id="CountryColumns">
|
||||||
|
NULLIF(u.country_code, '') AS countryCode,
|
||||||
|
NULLIF(u.country_name, '') AS countryName
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="PeriodColumnsT">
|
||||||
|
<choose>
|
||||||
|
<when test="periodType == 'DAY'">DATE_FORMAT(t.create_time, '%Y-%m-%d')</when>
|
||||||
|
<when test="periodType == 'WEEK'">DATE_FORMAT(t.create_time, '%x-W%v')</when>
|
||||||
|
<when test="periodType == 'MONTH'">DATE_FORMAT(t.create_time, '%Y-%m')</when>
|
||||||
|
<otherwise>'ALL'</otherwise>
|
||||||
|
</choose>
|
||||||
|
AS periodKey,
|
||||||
|
<choose>
|
||||||
|
<when test="periodType == 'DAY'">DATE_FORMAT(t.create_time, '%Y-%m-%d')</when>
|
||||||
|
<when test="periodType == 'WEEK'">DATE_FORMAT(t.create_time, '%x-W%v')</when>
|
||||||
|
<when test="periodType == 'MONTH'">DATE_FORMAT(t.create_time, '%Y-%m')</when>
|
||||||
|
<otherwise>'全部'</otherwise>
|
||||||
|
</choose>
|
||||||
|
AS periodName
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="PeriodColumnsU">
|
||||||
|
<choose>
|
||||||
|
<when test="periodType == 'DAY'">DATE_FORMAT(u.create_time, '%Y-%m-%d')</when>
|
||||||
|
<when test="periodType == 'WEEK'">DATE_FORMAT(u.create_time, '%x-W%v')</when>
|
||||||
|
<when test="periodType == 'MONTH'">DATE_FORMAT(u.create_time, '%Y-%m')</when>
|
||||||
|
<otherwise>'ALL'</otherwise>
|
||||||
|
</choose>
|
||||||
|
AS periodKey,
|
||||||
|
<choose>
|
||||||
|
<when test="periodType == 'DAY'">DATE_FORMAT(u.create_time, '%Y-%m-%d')</when>
|
||||||
|
<when test="periodType == 'WEEK'">DATE_FORMAT(u.create_time, '%x-W%v')</when>
|
||||||
|
<when test="periodType == 'MONTH'">DATE_FORMAT(u.create_time, '%Y-%m')</when>
|
||||||
|
<otherwise>'全部'</otherwise>
|
||||||
|
</choose>
|
||||||
|
AS periodName
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="UserWhere">
|
||||||
|
AND (u.is_del = 0 OR u.is_del IS NULL)
|
||||||
|
<if test="countryKeyword != null and countryKeyword != ''">
|
||||||
|
AND (
|
||||||
|
u.country_code = #{countryKeyword}
|
||||||
|
OR u.country_name LIKE CONCAT('%', #{countryKeyword}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="sysOrigin != null and sysOrigin != ''">
|
||||||
|
AND u.origin_sys = #{sysOrigin}
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="SourceTimeWhere">
|
||||||
|
<if test="startTime != null">
|
||||||
|
AND t.create_time >= #{startTime}
|
||||||
|
</if>
|
||||||
|
<if test="endTime != null">
|
||||||
|
AND t.create_time < #{endTime}
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="UserTimeWhere">
|
||||||
|
<if test="startTime != null">
|
||||||
|
AND u.create_time >= #{startTime}
|
||||||
|
</if>
|
||||||
|
<if test="endTime != null">
|
||||||
|
AND u.create_time < #{endTime}
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="SourceSysOriginWhere">
|
||||||
|
<if test="sysOrigin != null and sysOrigin != ''">
|
||||||
|
AND t.sys_origin = #{sysOrigin}
|
||||||
|
</if>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="listNewUsers"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsU"/>,
|
||||||
|
COUNT(1) AS countryNewUser
|
||||||
|
FROM user_base_info u
|
||||||
|
WHERE 1 = 1
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="UserTimeWhere"/>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listNewUserRecharge"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.unit_price), 0) AS newUserRecharge
|
||||||
|
FROM order_purchase_history t
|
||||||
|
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||||
|
WHERE t.evn = 'PROD'
|
||||||
|
AND t.is_trial_period = 0
|
||||||
|
AND t.status = 'COMPLETE'
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="SourceTimeWhere"/>
|
||||||
|
<include refid="UserTimeWhere"/>
|
||||||
|
<include refid="SourceSysOriginWhere"/>
|
||||||
|
<choose>
|
||||||
|
<when test="periodType == 'DAY'">AND DATE(u.create_time) = DATE(t.create_time)</when>
|
||||||
|
<when test="periodType == 'WEEK'">AND DATE_FORMAT(u.create_time, '%x-W%v') = DATE_FORMAT(t.create_time, '%x-W%v')</when>
|
||||||
|
<when test="periodType == 'MONTH'">AND DATE_FORMAT(u.create_time, '%Y-%m') = DATE_FORMAT(t.create_time, '%Y-%m')</when>
|
||||||
|
</choose>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listOfficialRecharge"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.unit_price), 0) AS officialRecharge
|
||||||
|
FROM order_purchase_history t
|
||||||
|
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||||
|
WHERE t.evn = 'PROD'
|
||||||
|
AND t.is_trial_period = 0
|
||||||
|
AND t.status = 'COMPLETE'
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="SourceTimeWhere"/>
|
||||||
|
<include refid="SourceSysOriginWhere"/>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listDealerRecharge"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.recharge_amount), 0) AS dealerRecharge
|
||||||
|
FROM user_gold_coin_recharge t
|
||||||
|
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||||
|
WHERE t.gold_coin_source IN (2, 3)
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="SourceTimeWhere"/>
|
||||||
|
<include refid="SourceSysOriginWhere"/>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listSalaryExchange"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.amount), 0) AS salaryExchange
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, sys_origin, amount, create_time
|
||||||
|
FROM user_salary_account_running_water
|
||||||
|
WHERE type = 1 AND salary_event = 'SALARY_EXCHANGE'
|
||||||
|
UNION ALL
|
||||||
|
SELECT user_id, sys_origin, amount, create_time
|
||||||
|
FROM user_salary_diamond_running_water
|
||||||
|
WHERE type = 1 AND salary_event = 'SALARY_EXCHANGE_GOLD_COINS'
|
||||||
|
) 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="listLuckyGift"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.pay_amount), 0) AS luckyGiftTotalFlow,
|
||||||
|
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
|
||||||
|
WHERE 1 = 1
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="SourceTimeWhere"/>
|
||||||
|
<include refid="SourceSysOriginWhere"/>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listWalletGame"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(CASE WHEN t.type = 1 THEN t.penny_amount ELSE 0 END) / 100.00, 0) AS gameTotalFlow,
|
||||||
|
IFNULL(SUM(CASE WHEN t.type = 0 THEN t.penny_amount ELSE 0 END) / 100.00, 0) AS gamePayout
|
||||||
|
FROM wallet_gold_asset_record 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%'
|
||||||
|
<include refid="UserWhere"/>
|
||||||
|
<include refid="SourceTimeWhere"/>
|
||||||
|
<include refid="SourceSysOriginWhere"/>
|
||||||
|
GROUP BY countryCode, countryName, periodKey, periodName
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listLuckyBoxGame"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO">
|
||||||
|
SELECT
|
||||||
|
<include refid="CountryColumns"/>,
|
||||||
|
<include refid="PeriodColumnsT"/>,
|
||||||
|
IFNULL(SUM(t.pay_amount), 0) AS gameTotalFlow,
|
||||||
|
IFNULL(SUM(t.gift_amount), 0) AS gamePayout
|
||||||
|
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="listUserCountryByIds"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardUserCountryDTO">
|
||||||
|
SELECT
|
||||||
|
id AS userId,
|
||||||
|
NULLIF(country_code, '') AS countryCode,
|
||||||
|
NULLIF(country_name, '') AS countryName
|
||||||
|
FROM user_base_info
|
||||||
|
WHERE id IN
|
||||||
|
<foreach collection="userIds" item="userId" open="(" separator="," close=")">
|
||||||
|
#{userId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@ -72,4 +72,5 @@ red-circle:
|
|||||||
- /console/datav/active/user-country-code
|
- /console/datav/active/user-country-code
|
||||||
- /console/datav/online/user/count
|
- /console/datav/online/user/count
|
||||||
- /console/datav/online/room/count
|
- /console/datav/online/room/count
|
||||||
|
- /console/datav/country-dashboard
|
||||||
- /console/user/base/info/im/sig
|
- /console/user/base/info/im/sig
|
||||||
|
|||||||
@ -12,6 +12,7 @@ public class BaishunRuntimeConfig {
|
|||||||
|
|
||||||
String sysOrigin;
|
String sysOrigin;
|
||||||
String platformBaseUrl;
|
String platformBaseUrl;
|
||||||
|
String luckyGiftUrl;
|
||||||
String appId;
|
String appId;
|
||||||
String appName;
|
String appName;
|
||||||
String appChannel;
|
String appChannel;
|
||||||
|
|||||||
@ -52,7 +52,9 @@ public class BaishunRuntimeConfigResolver {
|
|||||||
|
|
||||||
ApiAccount resolved = new ApiAccount();
|
ApiAccount resolved = new ApiAccount();
|
||||||
resolved.setStandardId(base.getStandardId());
|
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.setAppId(defaultIfBlank(config.getAppId(), base.getAppId()));
|
||||||
resolved.setAppChannel(defaultIfBlank(config.getAppChannel(), base.getAppChannel()));
|
resolved.setAppChannel(defaultIfBlank(config.getAppChannel(), base.getAppChannel()));
|
||||||
resolved.setAppKey(defaultIfBlank(config.getAppKey(), base.getAppKey()));
|
resolved.setAppKey(defaultIfBlank(config.getAppKey(), base.getAppKey()));
|
||||||
@ -72,6 +74,7 @@ public class BaishunRuntimeConfigResolver {
|
|||||||
return base.toBuilder()
|
return base.toBuilder()
|
||||||
.sysOrigin(normalizeSysOrigin(defaultIfBlank(row.getSysOrigin(), base.getSysOrigin())))
|
.sysOrigin(normalizeSysOrigin(defaultIfBlank(row.getSysOrigin(), base.getSysOrigin())))
|
||||||
.platformBaseUrl(defaultIfBlank(row.getPlatformBaseUrl(), base.getPlatformBaseUrl()))
|
.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())
|
.appId(row.getAppId() != null && row.getAppId() > 0 ? String.valueOf(row.getAppId()) : base.getAppId())
|
||||||
.appName(defaultIfBlank(row.getAppName(), base.getAppName()))
|
.appName(defaultIfBlank(row.getAppName(), base.getAppName()))
|
||||||
.appChannel(defaultIfBlank(row.getAppChannel(), base.getAppChannel()))
|
.appChannel(defaultIfBlank(row.getAppChannel(), base.getAppChannel()))
|
||||||
@ -84,7 +87,11 @@ public class BaishunRuntimeConfigResolver {
|
|||||||
.build();
|
.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);
|
String baseUrl = trim(platformBaseUrl);
|
||||||
if (StringUtils.isBlank(baseUrl)) {
|
if (StringUtils.isBlank(baseUrl)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class BaishunRuntimeConfigResolverTest {
|
|||||||
when(providerConfigService.getBySysOrigin("LIKEI")).thenReturn(new BaishunProviderConfig()
|
when(providerConfigService.getBySysOrigin("LIKEI")).thenReturn(new BaishunProviderConfig()
|
||||||
.setSysOrigin("LIKEI")
|
.setSysOrigin("LIKEI")
|
||||||
.setPlatformBaseUrl("https://provider.example.com/")
|
.setPlatformBaseUrl("https://provider.example.com/")
|
||||||
|
.setLuckyGiftUrl("https://lucky.example.com/lucky_gift/start_game")
|
||||||
.setAppId(4581045902L)
|
.setAppId(4581045902L)
|
||||||
.setAppChannel("yumiparty")
|
.setAppChannel("yumiparty")
|
||||||
.setAppKey("provider-key"));
|
.setAppKey("provider-key"));
|
||||||
@ -42,7 +43,7 @@ class BaishunRuntimeConfigResolverTest {
|
|||||||
ApiAccount resolved = resolver.resolveLuckyGiftApiAccount("LIKEI", 99L);
|
ApiAccount resolved = resolver.resolveLuckyGiftApiAccount("LIKEI", 99L);
|
||||||
|
|
||||||
assertEquals(99L, resolved.getStandardId());
|
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("4581045902", resolved.getAppId());
|
||||||
assertEquals("yumiparty", resolved.getAppChannel());
|
assertEquals("yumiparty", resolved.getAppChannel());
|
||||||
assertEquals("provider-key", resolved.getAppKey());
|
assertEquals("provider-key", resolved.getAppKey());
|
||||||
|
|||||||
@ -37,6 +37,9 @@ public class BaishunProviderConfig extends TimestampBaseEntity {
|
|||||||
@TableField("platform_base_url")
|
@TableField("platform_base_url")
|
||||||
private String platformBaseUrl;
|
private String platformBaseUrl;
|
||||||
|
|
||||||
|
@TableField("lucky_gift_url")
|
||||||
|
private String luckyGiftUrl;
|
||||||
|
|
||||||
@TableField("app_id")
|
@TableField("app_id")
|
||||||
private Long appId;
|
private Long appId;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user