feat: add aslan integrated stats endpoint

This commit is contained in:
zhx 2026-07-02 11:37:55 +08:00
parent 5580dcabb4
commit c3d5784b91
10 changed files with 426 additions and 7 deletions

2
.gitignore vendored
View File

@ -96,3 +96,5 @@ devops-monitor-id_rsa.key
/.claude/
build.md
reports/

View File

@ -45,4 +45,5 @@ gateway:
- /console/datav/online/user/count
- /console/datav/online/room/count
- /console/datav/aslan/region-country/statistics
- /console/datav/aslan/integrated-statistics
- /console/user/base/info/im/sig

View File

@ -1,7 +1,8 @@
package com.red.circle.console.adapter.app.user;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanIntegratedStatisticsCO;
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanRegionCountryStatisticsCO;
import com.red.circle.console.app.service.app.count.AslanIntegratedStatisticsService;
import com.red.circle.console.app.service.app.count.AslanRegionCountryStatisticsService;
import com.red.circle.console.app.service.app.user.DatavService;
import com.red.circle.framework.web.controller.BaseController;
@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.RestController;
public class DatavRestController extends BaseController {
private final DatavService userExpandService;
private final AslanIntegratedStatisticsService aslanIntegratedStatisticsService;
private final AslanRegionCountryStatisticsService aslanRegionCountryStatisticsService;
@GetMapping("/active/user-country-code")
@ -53,4 +55,17 @@ public class DatavRestController extends BaseController {
return aslanRegionCountryStatisticsService.listRegionCountryStatistics(date);
}
@GetMapping("/aslan/integrated-statistics")
public AslanIntegratedStatisticsCO aslanIntegratedStatistics(
@RequestParam(value = "sysOrigin", required = false) String sysOrigin,
@RequestParam(value = "areaCode", required = false) String areaCode,
@RequestParam(value = "dateNumber", required = false) Integer dateNumber,
@RequestParam(value = "goldOrigin", required = false) List<String> goldOrigins,
@RequestParam(value = "propsDate", required = false) String propsDate,
@RequestParam(value = "propsDateType", required = false) String propsDateType,
@RequestParam(value = "propsType", required = false) String propsType) {
return aslanIntegratedStatisticsService.aggregate(sysOrigin, areaCode, dateNumber,
goldOrigins, propsDate, propsDateType, propsType);
}
}

View File

@ -0,0 +1,274 @@
package com.red.circle.console.app.service.app.count;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.console.app.dto.clienobject.count.PropsSaleCO;
import com.red.circle.console.app.dto.clienobject.count.StatisticsDailyOperationActiveIndexCO;
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanIntegratedStatisticsCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyCurrencyCountGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyCurrencyCountPlatformCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyGoldOriginGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyRegisterLogoutGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountPurchaseGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountUserRechargeRankGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CurrencyAnalyzeUserLogGroupCO;
import com.red.circle.console.app.service.app.count.ops.CountCurrencyService;
import com.red.circle.console.app.service.app.count.ops.CountDailyPurchaseService;
import com.red.circle.console.app.service.app.count.ops.CountUserService;
import com.red.circle.console.infra.database.util.LocalDateUtils;
import com.red.circle.other.inner.model.cmd.count.PropsSaleQuotaQryCmd;
import com.red.circle.tool.core.collection.CollectionUtils;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* Aslan 后台数据图表聚合查询.
*/
@Service
@RequiredArgsConstructor
public class AslanIntegratedStatisticsServiceImpl implements AslanIntegratedStatisticsService {
private static final String DEFAULT_SYS_ORIGIN = "ATYOU";
private static final String DEFAULT_AREA_CODE = "DAILY";
private static final String DEFAULT_PROPS_DATE_TYPE = "MONTH_DAY";
private final CountUserService countUserService;
private final CountCurrencyService countCurrencyService;
private final CountDailyPurchaseService countDailyPurchaseService;
private final CountOperationActiveIndexService countOperationActiveIndexService;
@Override
public AslanIntegratedStatisticsCO aggregate(
String sysOrigin,
String areaCode,
Integer dateNumber,
List<String> goldOrigins,
String propsDate,
String propsDateType,
String propsType) {
String normalizedSysOrigin = normalizeSysOrigin(sysOrigin);
String normalizedAreaCode = normalizeAreaCode(areaCode);
// 先拿不依赖 dateNumber 的后台图表数据再用这些已存在读模型推断 Top 榜默认日期
CountPurchaseGroupCO dailyPurchase = pickBySysOrigin(
countDailyPurchaseService.listAllSysOriginLast30Days(normalizedAreaCode),
CountPurchaseGroupCO::getSysOrigin,
normalizedSysOrigin);
CountPurchaseGroupCO monthlyPurchase = pickBySysOrigin(
countDailyPurchaseService.listMonthlyPurchaseAmountPlatformLatestMonth(12),
CountPurchaseGroupCO::getSysOrigin,
normalizedSysOrigin);
CountDailyRegisterLogoutGroupCO registerLogout = pickBySysOrigin(
countUserService.getLatestRegisterLogout(),
CountDailyRegisterLogoutGroupCO::getSysOrigin,
normalizedSysOrigin);
CountDailyRegisterLogoutGroupCO registerRegion = pickBySysOrigin(
countUserService.latestRegisterRegion(),
CountDailyRegisterLogoutGroupCO::getSysOrigin,
normalizedSysOrigin);
CountDailyCurrencyCountGroupCO currency = pickBySysOrigin(
countCurrencyService.listLast30Days(),
CountDailyCurrencyCountGroupCO::getSysOrigin,
normalizedSysOrigin);
Integer resolvedDateNumber = resolveDateNumber(dateNumber, dailyPurchase, registerLogout,
currency);
CurrencyAnalyzeUserLogGroupCO dailyUserGoldTop = pickBySysOrigin(
countCurrencyService.listDailyUserGoldTop(resolvedDateNumber),
CurrencyAnalyzeUserLogGroupCO::getSysOrigin,
normalizedSysOrigin);
CountUserRechargeRankGroupCO dailyUserRechargeTop = pickBySysOrigin(
countCurrencyService.listDailyUserRechargeTop(resolvedDateNumber),
CountUserRechargeRankGroupCO::getSysOrigin,
normalizedSysOrigin);
List<StatisticsDailyOperationActiveIndexCO> activeIndexLatestDays =
countOperationActiveIndexService.listLatestDays(30);
Map<String, CountDailyGoldOriginGroupCO> goldDetails = mapGoldDetails(normalizedSysOrigin,
goldOrigins);
PropsSaleCO propsSale = queryPropsSale(normalizedSysOrigin, propsDate, propsDateType,
propsType);
return new AslanIntegratedStatisticsCO()
.setSysOrigin(normalizedSysOrigin)
.setSysOriginName(SysOriginPlatformEnum.getSysOriginName(normalizedSysOrigin))
.setAreaCode(normalizedAreaCode)
.setDateNumber(resolvedDateNumber)
.setRegisterLogout(registerLogout)
.setRegisterRegion(registerRegion)
.setDailyPurchase(dailyPurchase)
.setMonthlyPurchase(monthlyPurchase)
.setCurrency(currency)
.setOperationActiveIndexLatestDays(activeIndexLatestDays)
.setGoldDetails(goldDetails)
.setDailyUserGoldTop(dailyUserGoldTop)
.setDailyUserRechargeTop(dailyUserRechargeTop)
.setPropsSale(propsSale)
.setMissingSections(buildMissingSections(
registerLogout,
registerRegion,
dailyPurchase,
monthlyPurchase,
currency,
activeIndexLatestDays,
dailyUserGoldTop,
dailyUserRechargeTop,
normalizeGoldOrigins(goldOrigins),
goldDetails,
propsDate,
propsSale));
}
private String normalizeSysOrigin(String sysOrigin) {
String value = hasText(sysOrigin)
? sysOrigin.trim().toUpperCase(Locale.ROOT)
: DEFAULT_SYS_ORIGIN;
if (Objects.isNull(SysOriginPlatformEnum.toEnum(value))) {
throw new IllegalArgumentException("unsupported sysOrigin: " + sysOrigin);
}
return value;
}
private String normalizeAreaCode(String areaCode) {
return hasText(areaCode) ? areaCode.trim().toUpperCase(Locale.ROOT) : DEFAULT_AREA_CODE;
}
private <T> T pickBySysOrigin(List<T> source, Function<T, String> sysOriginGetter,
String sysOrigin) {
if (CollectionUtils.isEmpty(source)) {
return null;
}
return source.stream()
.filter(item -> Objects.equals(sysOriginGetter.apply(item), sysOrigin))
.findFirst()
.orElse(null);
}
private Integer resolveDateNumber(
Integer dateNumber,
CountPurchaseGroupCO dailyPurchase,
CountDailyRegisterLogoutGroupCO registerLogout,
CountDailyCurrencyCountGroupCO currency) {
if (Objects.nonNull(dateNumber)) {
return dateNumber;
}
if (Objects.nonNull(dailyPurchase) && Objects.nonNull(dailyPurchase.getLast())
&& Objects.nonNull(dailyPurchase.getLast().getDateNumber())) {
return dailyPurchase.getLast().getDateNumber();
}
if (Objects.nonNull(registerLogout) && Objects.nonNull(registerLogout.getRegisterLogout())
&& Objects.nonNull(registerLogout.getRegisterLogout().getDateNumber())) {
return registerLogout.getRegisterLogout().getDateNumber();
}
if (Objects.nonNull(currency) && CollectionUtils.isNotEmpty(currency.getLastMonths())) {
CountDailyCurrencyCountPlatformCO lastCurrency = CollectionUtils.getLastElement(
currency.getLastMonths());
if (Objects.nonNull(lastCurrency) && Objects.nonNull(lastCurrency.getDateNumber())) {
return lastCurrency.getDateNumber();
}
}
return LocalDateUtils.nowToInteger();
}
private Map<String, CountDailyGoldOriginGroupCO> mapGoldDetails(
String sysOrigin,
List<String> goldOrigins) {
Map<String, CountDailyGoldOriginGroupCO> details = new LinkedHashMap<>();
for (String goldOrigin : normalizeGoldOrigins(goldOrigins)) {
CountDailyGoldOriginGroupCO detail = pickBySysOrigin(
countCurrencyService.listLastGoldOrigin30Days(goldOrigin),
CountDailyGoldOriginGroupCO::getSysOrigin,
sysOrigin);
details.put(goldOrigin, detail);
}
return details;
}
private List<String> normalizeGoldOrigins(List<String> goldOrigins) {
if (CollectionUtils.isEmpty(goldOrigins)) {
return CollectionUtils.newArrayList();
}
// 兼容 goldOrigin=A&goldOrigin=B goldOrigin=A,B 两种调用方式
return goldOrigins.stream()
.filter(Objects::nonNull)
.flatMap(origin -> Arrays.stream(origin.split(",")))
.map(String::trim)
.filter(this::hasText)
.distinct()
.toList();
}
private PropsSaleCO queryPropsSale(
String sysOrigin,
String propsDate,
String propsDateType,
String propsType) {
if (!hasText(propsDate)) {
return null;
}
PropsSaleQuotaQryCmd query = new PropsSaleQuotaQryCmd()
.setSysOrigin(SysOriginPlatformEnum.valueOf(sysOrigin))
.setDateType(hasText(propsDateType) ? propsDateType.trim() : DEFAULT_PROPS_DATE_TYPE)
.setDate(propsDate.trim());
if (hasText(propsType)) {
query.setPropsType(propsType.trim());
}
return countOperationActiveIndexService.getPropsSale(query);
}
private List<String> buildMissingSections(
CountDailyRegisterLogoutGroupCO registerLogout,
CountDailyRegisterLogoutGroupCO registerRegion,
CountPurchaseGroupCO dailyPurchase,
CountPurchaseGroupCO monthlyPurchase,
CountDailyCurrencyCountGroupCO currency,
List<StatisticsDailyOperationActiveIndexCO> activeIndexLatestDays,
CurrencyAnalyzeUserLogGroupCO dailyUserGoldTop,
CountUserRechargeRankGroupCO dailyUserRechargeTop,
List<String> requestedGoldOrigins,
Map<String, CountDailyGoldOriginGroupCO> goldDetails,
String propsDate,
PropsSaleCO propsSale) {
List<String> missing = CollectionUtils.newArrayList();
addMissing(missing, "registerLogout", registerLogout);
addMissing(missing, "registerRegion", registerRegion);
addMissing(missing, "dailyPurchase", dailyPurchase);
addMissing(missing, "monthlyPurchase", monthlyPurchase);
addMissing(missing, "currency", currency);
addMissing(missing, "operationActiveIndexLatestDays", activeIndexLatestDays);
addMissing(missing, "dailyUserGoldTop", dailyUserGoldTop);
addMissing(missing, "dailyUserRechargeTop", dailyUserRechargeTop);
for (String goldOrigin : requestedGoldOrigins) {
addMissing(missing, "goldDetails:" + goldOrigin, goldDetails.get(goldOrigin));
}
if (hasText(propsDate)) {
addMissing(missing, "propsSale", propsSale);
}
return missing;
}
private void addMissing(List<String> missing, String section, Object value) {
if (Objects.isNull(value)) {
missing.add(section);
return;
}
if (value instanceof List<?> list && CollectionUtils.isEmpty(list)) {
missing.add(section);
return;
}
if (value instanceof Map<?, ?> map && map.isEmpty()) {
missing.add(section);
}
}
private boolean hasText(String value) {
return Objects.nonNull(value) && !value.trim().isEmpty();
}
}

View File

@ -0,0 +1,104 @@
package com.red.circle.console.app.dto.clienobject.count.aslan;
import com.red.circle.console.app.dto.clienobject.count.PropsSaleCO;
import com.red.circle.console.app.dto.clienobject.count.StatisticsDailyOperationActiveIndexCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyCurrencyCountGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyGoldOriginGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountDailyRegisterLogoutGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountPurchaseGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CountUserRechargeRankGroupCO;
import com.red.circle.console.app.dto.clienobject.count.ops.CurrencyAnalyzeUserLogGroupCO;
import com.red.circle.framework.dto.ClientObject;
import java.io.Serial;
import java.util.List;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* Aslan 后台数据图表聚合结果.
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class AslanIntegratedStatisticsCO extends ClientObject {
@Serial
private static final long serialVersionUID = 1L;
/**
* 平台编码默认 ATYOU.
*/
private String sysOrigin;
/**
* 平台名称.
*/
private String sysOriginName;
/**
* 内购区域口径默认 DAILY.
*/
private String areaCode;
/**
* Top 榜使用的统计日期格式 yyyyMMdd.
*/
private Integer dateNumber;
/**
* 每日新增/注销.
*/
private CountDailyRegisterLogoutGroupCO registerLogout;
/**
* 每日新增区域分布.
*/
private CountDailyRegisterLogoutGroupCO registerRegion;
/**
* 每日内购.
*/
private CountPurchaseGroupCO dailyPurchase;
/**
* 每月内购.
*/
private CountPurchaseGroupCO monthlyPurchase;
/**
* 每日金币收入/支出.
*/
private CountDailyCurrencyCountGroupCO currency;
/**
* 活跃情况.
*/
private List<StatisticsDailyOperationActiveIndexCO> operationActiveIndexLatestDays;
/**
* 每日金币来源明细key 为请求的 goldOrigin.
*/
private Map<String, CountDailyGoldOriginGroupCO> goldDetails;
/**
* 每日用户金币 Top.
*/
private CurrencyAnalyzeUserLogGroupCO dailyUserGoldTop;
/**
* 每日充值 Top.
*/
private CountUserRechargeRankGroupCO dailyUserRechargeTop;
/**
* 道具销售情况只有传 propsDate 时返回.
*/
private PropsSaleCO propsSale;
/**
* 已请求但当前没有读到数据的区块.
*/
private List<String> missingSections;
}

View File

@ -0,0 +1,22 @@
package com.red.circle.console.app.service.app.count;
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanIntegratedStatisticsCO;
import java.util.List;
/**
* Aslan 后台数据图表聚合查询.
*/
public interface AslanIntegratedStatisticsService {
/**
* 聚合后台数据图表现有统计区块.
*/
AslanIntegratedStatisticsCO aggregate(
String sysOrigin,
String areaCode,
Integer dateNumber,
List<String> goldOrigins,
String propsDate,
String propsDateType,
String propsType);
}

View File

@ -70,4 +70,5 @@ red-circle:
- /console/datav/online/user/count
- /console/datav/online/room/count
- /console/datav/aslan/region-country/statistics
- /console/datav/aslan/integrated-statistics
- /console/user/base/info/im/sig

View File

@ -30,8 +30,8 @@ management:
show-details: always
rtc:
appId: f424387a480e41088239416e10030034
certificate: c96d8494f80542bb83c843ad4045df03
appId: 4b5e5cea3b86476caf7f7a57d05b82d1
certificate: a6feb209de6448148a926f59634807bb
red-circle:
oss:

View File

@ -56,12 +56,12 @@ public class LiveMicrophoneServiceImpl implements LiveMicrophoneService {
private final RedisService redisService;
private final RoomUserBlacklistClient roomUserBlacklistClient;
private final String authKey = "Basic N2Q0ZTRiZDYyODUyNDc3ZGI4Yzk3OGU1NTVmMDRiOGI6ODdkMjcxMTg2NzgxNGY4YjgzZTNkOWIyZmRjOTc3OWQ=";
private final String authKey = "Basic ZjFjNWIzMDZiMzA0NGU5Y2E5Yzk5YjYzNDNlZGJlZDM6OGQ2Njc0OGQ1NjQyNDVlZmI5MTc3NDFlYTllYWUyNWU=";
private final String killRoom = "https://api.sd-rtn.com/dev/v1/kicking-rule";
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
private final String appId = "535633ae846441b09547b68f8353aad3";
private final String appId = "4b5e5cea3b86476caf7f7a57d05b82d1";
private final UserProfileClient userProfileClient;

View File

@ -97,12 +97,12 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway {
private final LiveRoomCacheService liveRoomCacheService;
// private final RedissonClient redissonClient;
//临时处理,后续房子啊Nacos配置处理
private final String authKey = "Basic MmIwZGFkOWQ0ZjEzNGQ2NmI5MmI2ZmY2MzljNmEwMTc6ZWRlMTQ1NTRjNTU0NDY2ZmE4OWNiYWFhNGNmZTM3YjA=";
private final String authKey = "Basic ZjFjNWIzMDZiMzA0NGU5Y2E5Yzk5YjYzNDNlZGJlZDM6OGQ2Njc0OGQ1NjQyNDVlZmI5MTc3NDFlYTllYWUyNWU=";
private final String killRoom = "https://api.sd-rtn.com/dev/v1/kicking-rule";
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
private final String appId = "660b61b534d04d44815ee855913a6efe";
private final String appId = "4b5e5cea3b86476caf7f7a57d05b82d1";
@Override