Merge branch 'main' into test
This commit is contained in:
commit
dfb71f55a8
@ -7,6 +7,7 @@ import com.red.circle.console.app.dto.cmd.datav.CountryDashboardGameMetricQryCmd
|
||||
import com.red.circle.console.app.dto.cmd.datav.CountryDashboardQryCmd;
|
||||
import com.red.circle.console.app.dto.cmd.datav.CountryDashboardRechargeDetailQryCmd;
|
||||
import com.red.circle.console.app.service.app.datav.CountryDashboardDailyMetricService;
|
||||
import com.red.circle.console.app.service.app.datav.CountryDashboardBackfillLockService;
|
||||
import com.red.circle.console.app.service.app.datav.CountryDashboardGameMetricService;
|
||||
import com.red.circle.console.app.service.app.datav.CountryDashboardService;
|
||||
import com.red.circle.console.app.service.app.user.DatavService;
|
||||
@ -41,6 +42,7 @@ public class DatavRestController extends BaseController {
|
||||
private final DatavService userExpandService;
|
||||
private final CountryDashboardService countryDashboardService;
|
||||
private final CountryDashboardDailyMetricService countryDashboardDailyMetricService;
|
||||
private final CountryDashboardBackfillLockService countryDashboardBackfillLockService;
|
||||
private final CountryDashboardGameMetricService countryDashboardGameMetricService;
|
||||
|
||||
@Value("${red-circle.country-dashboard.query-enabled:true}")
|
||||
@ -116,14 +118,21 @@ public class DatavRestController extends BaseController {
|
||||
}
|
||||
LocalDate parsedStartDate = LocalDate.parse(startDate);
|
||||
LocalDate parsedEndDate = LocalDate.parse(endDate);
|
||||
List<String> refreshedTimezones = countryDashboardDailyMetricService.refreshRange(
|
||||
parsedStartDate, parsedEndDate, sysOrigin, statTimezones);
|
||||
Map<String, Object> refreshed = countryDashboardBackfillLockService.call(() -> {
|
||||
List<String> countryTimezones = countryDashboardDailyMetricService.refreshRange(
|
||||
parsedStartDate, parsedEndDate, sysOrigin, statTimezones);
|
||||
List<String> gameTimezones = countryDashboardGameMetricService.refreshRange(
|
||||
parsedStartDate, parsedEndDate, sysOrigin, statTimezones);
|
||||
return Map.of(
|
||||
"countryStatTimezones", countryTimezones,
|
||||
"gameStatTimezones", gameTimezones);
|
||||
});
|
||||
return Map.of(
|
||||
"status", "success",
|
||||
"startDate", parsedStartDate.toString(),
|
||||
"endDate", parsedEndDate.toString(),
|
||||
"sysOrigin", sysOrigin,
|
||||
"statTimezones", refreshedTimezones);
|
||||
"statTimezones", refreshed);
|
||||
}
|
||||
|
||||
@PostMapping("/country-dashboard/game-metrics/backfill")
|
||||
@ -137,8 +146,9 @@ public class DatavRestController extends BaseController {
|
||||
}
|
||||
LocalDate parsedStartDate = LocalDate.parse(startDate);
|
||||
LocalDate parsedEndDate = LocalDate.parse(endDate);
|
||||
List<String> refreshedTimezones = countryDashboardGameMetricService.refreshRange(
|
||||
parsedStartDate, parsedEndDate, sysOrigin, statTimezones);
|
||||
List<String> refreshedTimezones = countryDashboardBackfillLockService.call(
|
||||
() -> countryDashboardGameMetricService.refreshRange(
|
||||
parsedStartDate, parsedEndDate, sysOrigin, statTimezones));
|
||||
return Map.of(
|
||||
"status", "success",
|
||||
"startDate", parsedStartDate.toString(),
|
||||
|
||||
@ -23,13 +23,19 @@
|
||||
<artifactId>business-rocketmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<version>${easyexcel.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<dependency>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<version>${easyexcel.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<parent>
|
||||
<artifactId>rc-service-console</artifactId>
|
||||
|
||||
@ -6,6 +6,7 @@ import com.red.circle.console.app.service.app.datav.CountryDashboardGameMetricSe
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -14,6 +15,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "red-circle.country-dashboard.refresh-enabled", havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
public class CountryDashboardDailyMetricTask {
|
||||
|
||||
|
||||
@ -37,7 +37,8 @@ import org.springframework.stereotype.Service;
|
||||
@RequiredArgsConstructor
|
||||
public class ResidentCpActivityServiceImpl implements ResidentCpActivityService {
|
||||
|
||||
private static final int CP_LEVEL_COUNT = 5;
|
||||
private static final int CP_LEVEL_COUNT = 6;
|
||||
private static final int CP_MIN_LEVEL = 0;
|
||||
private static final int QUERY_LIMIT = 50;
|
||||
private static final String CONFIG_GROUP_NAME = "CP";
|
||||
private static final String CONFIG_DATA_TYPE = "int";
|
||||
@ -53,8 +54,8 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService
|
||||
String normalizedSysOrigin = normalizeSysOrigin(sysOrigin);
|
||||
List<SysCpCabinConfigDTO> configs = listCpCabinConfigs(normalizedSysOrigin);
|
||||
List<ResidentCpLevelConfigCO> levels = new ArrayList<>(CP_LEVEL_COUNT);
|
||||
for (int level = 1; level <= CP_LEVEL_COUNT; level++) {
|
||||
SysCpCabinConfigDTO config = level <= configs.size() ? configs.get(level - 1) : null;
|
||||
for (int level = CP_MIN_LEVEL; level < CP_LEVEL_COUNT; level++) {
|
||||
SysCpCabinConfigDTO config = level < configs.size() ? configs.get(level) : null;
|
||||
levels.add(toLevelCO(level, config));
|
||||
}
|
||||
return new ResidentCpConfigCO()
|
||||
@ -76,8 +77,8 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService
|
||||
for (ResidentCpLevelConfigSaveCmd levelCmd : sortedLevels(cmd.getLevels())) {
|
||||
SysCpCabinConfigDTO config = Optional.ofNullable(levelCmd.getCabinConfigId())
|
||||
.map(configById::get)
|
||||
.orElseGet(() -> levelCmd.getLevel() <= configs.size()
|
||||
? configs.get(levelCmd.getLevel() - 1)
|
||||
.orElseGet(() -> levelCmd.getLevel() < configs.size()
|
||||
? configs.get(levelCmd.getLevel())
|
||||
: null);
|
||||
boolean exists = Objects.nonNull(config) && Objects.nonNull(config.getId());
|
||||
if (!exists) {
|
||||
@ -133,14 +134,14 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService
|
||||
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin());
|
||||
List<ResidentCpLevelConfigSaveCmd> levels = sortedLevels(cmd.getLevels());
|
||||
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
|
||||
"CP levels must contain exactly 5 items.", levels.size() == CP_LEVEL_COUNT);
|
||||
"CP levels must contain exactly 6 items.", levels.size() == CP_LEVEL_COUNT);
|
||||
Set<Integer> levelSet = new HashSet<>();
|
||||
long previousRequiredValue = -1L;
|
||||
for (ResidentCpLevelConfigSaveCmd level : levels) {
|
||||
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
|
||||
"CP level must be between 1 and 5.",
|
||||
Objects.nonNull(level.getLevel()) && level.getLevel() >= 1
|
||||
&& level.getLevel() <= CP_LEVEL_COUNT && levelSet.add(level.getLevel()));
|
||||
"CP level must be between 0 and 5.",
|
||||
Objects.nonNull(level.getLevel()) && level.getLevel() >= CP_MIN_LEVEL
|
||||
&& level.getLevel() < CP_LEVEL_COUNT && levelSet.add(level.getLevel()));
|
||||
long requiredValue = defaultRequiredValue(level.getRequiredIntimacyValue());
|
||||
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
|
||||
"requiredIntimacyValue must be greater than or equal to zero.", requiredValue >= 0);
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
package com.red.circle.console.app.service.app.datav;
|
||||
|
||||
import com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Coordinates dashboard backfill and CDC worker writes through a MySQL named lock.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CountryDashboardBackfillLockService {
|
||||
|
||||
private static final String LOCK_NAME = "country_dashboard_backfill_lock";
|
||||
|
||||
private final CountryDashboardDAO countryDashboardDAO;
|
||||
|
||||
@Value("${red-circle.country-dashboard.backfill-lock-timeout-seconds:10}")
|
||||
private Integer lockTimeoutSeconds;
|
||||
|
||||
public <T> T call(Supplier<T> supplier) {
|
||||
Integer acquired = countryDashboardDAO.getDashboardBackfillLock(
|
||||
LOCK_NAME, Math.max(Objects.requireNonNullElse(lockTimeoutSeconds, 10), 0));
|
||||
if (!Objects.equals(acquired, 1)) {
|
||||
throw new IllegalStateException("country dashboard backfill lock is held by CDC worker");
|
||||
}
|
||||
try {
|
||||
return supplier.get();
|
||||
} finally {
|
||||
try {
|
||||
countryDashboardDAO.releaseDashboardBackfillLock(LOCK_NAME);
|
||||
} catch (Exception ex) {
|
||||
log.warn("release country dashboard backfill lock failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,7 +56,7 @@ public class CountryDashboardDailyMetricService {
|
||||
@Value("${red-circle.country-dashboard.refresh-sys-origins:LIKEI}")
|
||||
private String refreshSysOrigins;
|
||||
|
||||
@Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh,UTC,Asia/Shanghai}")
|
||||
@Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh}")
|
||||
private String refreshTimezones;
|
||||
|
||||
private volatile boolean metricSchemaEnsured;
|
||||
@ -486,7 +486,7 @@ public class CountryDashboardDailyMetricService {
|
||||
config = trimToNull(refreshTimezones);
|
||||
}
|
||||
if (config == null) {
|
||||
config = "Asia/Riyadh,UTC,Asia/Shanghai";
|
||||
config = "Asia/Riyadh";
|
||||
}
|
||||
|
||||
Map<String, StatTimezone> zones = new LinkedHashMap<>();
|
||||
|
||||
@ -56,7 +56,7 @@ public class CountryDashboardGameMetricServiceImpl implements CountryDashboardGa
|
||||
@Value("${red-circle.country-dashboard.refresh-sys-origins:LIKEI}")
|
||||
private String refreshSysOrigins;
|
||||
|
||||
@Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh,UTC,Asia/Shanghai}")
|
||||
@Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh}")
|
||||
private String refreshTimezones;
|
||||
|
||||
private volatile boolean metricSchemaEnsured;
|
||||
@ -478,7 +478,7 @@ public class CountryDashboardGameMetricServiceImpl implements CountryDashboardGa
|
||||
config = trimToNull(refreshTimezones);
|
||||
}
|
||||
if (config == null) {
|
||||
config = "Asia/Riyadh,UTC,Asia/Shanghai";
|
||||
config = "Asia/Riyadh";
|
||||
}
|
||||
|
||||
Map<String, StatTimezone> zones = new LinkedHashMap<>();
|
||||
|
||||
@ -72,6 +72,7 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
|
||||
private final CountryDashboardDAO countryDashboardDAO;
|
||||
private final MongoTemplate mongoTemplate;
|
||||
private volatile boolean rechargeDetailSchemaEnsured;
|
||||
|
||||
@Override
|
||||
public CountryDashboardCO query(CountryDashboardQryCmd cmd) {
|
||||
@ -82,15 +83,6 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.statTimezone, 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)));
|
||||
mergeMongo(rows, condition, listBankSalaryTransfer(condition),
|
||||
(row, amount) -> row.setSalaryTransfer(add(row.getSalaryTransfer(), amount)));
|
||||
mergeDailyActive(rows, condition, listDailyActive(condition));
|
||||
mergeRetention(rows, condition);
|
||||
|
||||
List<CountryDashboardMetricCO> records = rows.values().stream()
|
||||
.peek(this::calculateFormulaFields)
|
||||
.sorted(Comparator.comparing(CountryDashboardMetricCO::getPeriodKey,
|
||||
@ -294,18 +286,45 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
@Override
|
||||
public PageResult<CountryDashboardRechargeDetailCO> pageRechargeDetails(
|
||||
CountryDashboardRechargeDetailQryCmd cmd) {
|
||||
ensureRechargeDetailSchema();
|
||||
int cursor = Math.max(1, cmd == null || cmd.getCursor() == null ? 1 : cmd.getCursor());
|
||||
int limit = Math.max(1, cmd == null || cmd.getLimit() == null ? 100 : cmd.getLimit());
|
||||
limit = Math.min(100, limit);
|
||||
|
||||
QueryCondition condition = QueryCondition.of(cmd);
|
||||
String countryCodes = trimToNull(cmd == null ? null : cmd.getCountryCodes());
|
||||
long total = safeLong(countryDashboardDAO.countRechargeDetails(
|
||||
condition.startTime, condition.endTime, condition.countryKeyword, countryCodes,
|
||||
condition.sysOrigin, condition.storageTimezoneOffset, condition.statTimezoneOffset));
|
||||
List<CountryDashboardRechargeDetailCO> records = total <= 0 ? List.of()
|
||||
: countryDashboardDAO.listRechargeDetails(
|
||||
condition.startTime, condition.endTime, condition.countryKeyword, countryCodes,
|
||||
condition.sysOrigin, condition.storageTimezoneOffset, condition.statTimezoneOffset,
|
||||
(cursor - 1) * limit, limit)
|
||||
.stream()
|
||||
.map(this::toRechargeDetailCO)
|
||||
.toList();
|
||||
PageResult<CountryDashboardRechargeDetailCO> result = new PageResult<>();
|
||||
result.setCurrent(cursor);
|
||||
result.setSize(limit);
|
||||
result.setTotal(0L);
|
||||
result.setRecords(List.of());
|
||||
result.setTotal(total);
|
||||
result.setRecords(records);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ensureRechargeDetailSchema() {
|
||||
if (rechargeDetailSchemaEnsured) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (rechargeDetailSchemaEnsured) {
|
||||
return;
|
||||
}
|
||||
countryDashboardDAO.createRechargeDetailTable();
|
||||
rechargeDetailSchemaEnsured = true;
|
||||
}
|
||||
}
|
||||
|
||||
private CountryDashboardRechargeDetailCO toRechargeDetailCO(
|
||||
CountryDashboardRechargeDetailDTO dto) {
|
||||
return new CountryDashboardRechargeDetailCO()
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package com.red.circle.console.app.service.app.activity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpConfigCO;
|
||||
import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpLevelConfigCO;
|
||||
import com.red.circle.console.app.service.app.sys.CpCabinConfigService;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.model.cmd.sys.SysCpCabinConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.sys.SysCpCabinConfigDTO;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ResidentCpActivityServiceImplTest {
|
||||
|
||||
@Test
|
||||
void getConfigShouldReturnCpLevelsFromZeroToFive() {
|
||||
CpCabinConfigService cpCabinConfigService = mock(CpCabinConfigService.class);
|
||||
ResidentCpActivityServiceImpl service = new ResidentCpActivityServiceImpl(
|
||||
cpCabinConfigService);
|
||||
PageResult<SysCpCabinConfigDTO> page = new PageResult<>();
|
||||
page.setRecords(List.of(
|
||||
config(100L, 1L, 100L),
|
||||
config(101L, 2L, 200L),
|
||||
config(102L, 3L, 300L),
|
||||
config(103L, 4L, 400L),
|
||||
config(104L, 5L, 500L)
|
||||
));
|
||||
when(cpCabinConfigService.getCpCabin(any(SysCpCabinConfigQryCmd.class))).thenReturn(page);
|
||||
|
||||
ResidentCpConfigCO result = service.getConfig("likei");
|
||||
|
||||
assertEquals("LIKEI", result.getSysOrigin());
|
||||
assertEquals(List.of(0, 1, 2, 3, 4, 5),
|
||||
result.getLevels().stream().map(ResidentCpLevelConfigCO::getLevel).toList());
|
||||
assertEquals(100L, result.getLevels().get(0).getRequiredIntimacyValue());
|
||||
assertEquals(100L, result.getLevels().get(0).getCabinConfigId());
|
||||
assertEquals(0L, result.getLevels().get(5).getRequiredIntimacyValue());
|
||||
}
|
||||
|
||||
private static SysCpCabinConfigDTO config(Long id, Long sort, Long unlockValue) {
|
||||
return new SysCpCabinConfigDTO()
|
||||
.setId(id)
|
||||
.setName("CP " + sort + "级")
|
||||
.setCabinUnlockValue(unlockValue)
|
||||
.setSort(sort);
|
||||
}
|
||||
}
|
||||
@ -30,8 +30,16 @@ public interface CountryDashboardDAO extends BaseDAO<IUserBaseInfo> {
|
||||
|
||||
int createGamePeriodUserMetricTable();
|
||||
|
||||
int createRechargeDetailTable();
|
||||
|
||||
int ensurePeriodMetricTimezoneSchema();
|
||||
|
||||
Integer getDashboardBackfillLock(
|
||||
@Param("lockName") String lockName,
|
||||
@Param("timeoutSeconds") Integer timeoutSeconds);
|
||||
|
||||
Integer releaseDashboardBackfillLock(@Param("lockName") String lockName);
|
||||
|
||||
List<CountryDashboardMetricDTO> listNewUsers(
|
||||
@Param("periodType") String periodType,
|
||||
@Param("startTime") LocalDateTime startTime,
|
||||
|
||||
@ -3,6 +3,14 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO">
|
||||
|
||||
<select id="getDashboardBackfillLock" resultType="java.lang.Integer">
|
||||
SELECT GET_LOCK(#{lockName}, #{timeoutSeconds})
|
||||
</select>
|
||||
|
||||
<select id="releaseDashboardBackfillLock" resultType="java.lang.Integer">
|
||||
SELECT RELEASE_LOCK(#{lockName})
|
||||
</select>
|
||||
|
||||
<update id="createDailyMetricTable">
|
||||
CREATE TABLE IF NOT EXISTS `country_dashboard_daily_metric` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
@ -181,6 +189,28 @@
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏游戏维度周期用户去重明细'
|
||||
</update>
|
||||
|
||||
<update id="createRechargeDetailTable">
|
||||
CREATE TABLE IF NOT EXISTS `country_dashboard_recharge_detail` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`source_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '充值来源',
|
||||
`record_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '来源记录ID',
|
||||
`source_channel` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '来源渠道',
|
||||
`sys_origin` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '系统来源',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`country_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家码',
|
||||
`country_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家名称',
|
||||
`amount` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT 'USD金额',
|
||||
`coin_quantity` decimal(20,2) DEFAULT NULL COMMENT '金币数量',
|
||||
`remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
|
||||
`create_time` datetime NOT NULL COMMENT '业务创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_recharge_source_record` (`source_type`, `record_id`),
|
||||
KEY `idx_recharge_query` (`sys_origin`, `create_time`, `country_code`),
|
||||
KEY `idx_recharge_user` (`user_id`, `create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏充值明细预聚合'
|
||||
</update>
|
||||
|
||||
<update id="ensurePeriodMetricTimezoneSchema">
|
||||
SET @country_dashboard_schema_lock = GET_LOCK('country_dashboard_period_timezone_schema', 60);
|
||||
SET @current_schema = DATABASE();
|
||||
@ -1423,41 +1453,68 @@
|
||||
|
||||
<select id="countRechargeDetails" resultType="java.lang.Long">
|
||||
SELECT COUNT(1)
|
||||
FROM (
|
||||
<include refid="RechargeDetailSource"/>
|
||||
) t
|
||||
INNER JOIN user_base_info u ON u.id = t.userId
|
||||
FROM country_dashboard_recharge_detail t
|
||||
WHERE 1 = 1
|
||||
<include refid="UserWhere"/>
|
||||
<include refid="CountryCodesWhere"/>
|
||||
<if test="startTime != null">
|
||||
AND t.create_time >= #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null">
|
||||
AND t.create_time < #{endTime}
|
||||
</if>
|
||||
<if test="countryKeyword != null and countryKeyword != ''">
|
||||
AND (
|
||||
t.country_code = #{countryKeyword}
|
||||
OR t.country_name LIKE CONCAT('%', #{countryKeyword}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="countryCodes != null and countryCodes != ''">
|
||||
AND FIND_IN_SET(t.country_code, #{countryCodes})
|
||||
</if>
|
||||
<if test="sysOrigin != null and sysOrigin != ''">
|
||||
AND t.sys_origin = #{sysOrigin}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="listRechargeDetails"
|
||||
resultType="com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardRechargeDetailDTO">
|
||||
SELECT
|
||||
t.recordId,
|
||||
t.sourceType,
|
||||
t.sourceChannel,
|
||||
t.userId,
|
||||
t.record_id AS recordId,
|
||||
t.source_type AS sourceType,
|
||||
t.source_channel AS sourceChannel,
|
||||
t.user_id AS userId,
|
||||
u.account AS userAccount,
|
||||
u.user_nickname AS userNickname,
|
||||
NULLIF(u.country_code, '') AS countryCode,
|
||||
NULLIF(u.country_name, '') AS countryName,
|
||||
t.country_code AS countryCode,
|
||||
t.country_name AS countryName,
|
||||
t.amount,
|
||||
t.coinQuantity,
|
||||
t.coin_quantity AS coinQuantity,
|
||||
t.remark,
|
||||
DATE_FORMAT(
|
||||
CONVERT_TZ(t.createTime, #{storageTimezoneOffset}, #{statTimezoneOffset}),
|
||||
CONVERT_TZ(t.create_time, #{storageTimezoneOffset}, #{statTimezoneOffset}),
|
||||
'%Y-%m-%d %H:%i:%s'
|
||||
) AS createTime
|
||||
FROM (
|
||||
<include refid="RechargeDetailSource"/>
|
||||
) t
|
||||
INNER JOIN user_base_info u ON u.id = t.userId
|
||||
FROM country_dashboard_recharge_detail t
|
||||
LEFT JOIN user_base_info u ON u.id = t.user_id
|
||||
WHERE 1 = 1
|
||||
<include refid="UserWhere"/>
|
||||
<include refid="CountryCodesWhere"/>
|
||||
ORDER BY t.createTime DESC, t.recordId DESC
|
||||
<if test="startTime != null">
|
||||
AND t.create_time >= #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null">
|
||||
AND t.create_time < #{endTime}
|
||||
</if>
|
||||
<if test="countryKeyword != null and countryKeyword != ''">
|
||||
AND (
|
||||
t.country_code = #{countryKeyword}
|
||||
OR t.country_name LIKE CONCAT('%', #{countryKeyword}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="countryCodes != null and countryCodes != ''">
|
||||
AND FIND_IN_SET(t.country_code, #{countryCodes})
|
||||
</if>
|
||||
<if test="sysOrigin != null and sysOrigin != ''">
|
||||
AND t.sys_origin = #{sysOrigin}
|
||||
</if>
|
||||
ORDER BY t.create_time DESC, t.record_id DESC
|
||||
LIMIT #{limit} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ import org.springframework.stereotype.Component;
|
||||
public class UserCpRightsConfigQueryExe {
|
||||
|
||||
private static final String RELATIONSHIP_STATUS_NONE = "NONE";
|
||||
private static final int CP_RIGHTS_LEVEL_COUNT = 5;
|
||||
private static final int CP_RIGHTS_LEVEL_COUNT = 6;
|
||||
|
||||
private final CpCabinConfigService cpCabinConfigService;
|
||||
private final CpRelationshipService cpRelationshipService;
|
||||
@ -106,7 +106,7 @@ public class UserCpRightsConfigQueryExe {
|
||||
: configs;
|
||||
return IntStream.range(0, CP_RIGHTS_LEVEL_COUNT)
|
||||
.mapToObj(index -> toLevel(relationType, getLevelConfig(resolvedConfigs, index),
|
||||
index + 1, hasRelationship,
|
||||
index, hasRelationship,
|
||||
intimacyValue))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
package com.red.circle.other.app.command.user.query;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.red.circle.framework.core.dto.ReqSysOrigin;
|
||||
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpRightsConfigCO;
|
||||
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpRightsConfigQueryCmd;
|
||||
import com.red.circle.other.infra.database.rds.entity.user.user.CpCabinConfig;
|
||||
import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship;
|
||||
import com.red.circle.other.infra.database.rds.entity.user.user.CpValue;
|
||||
import com.red.circle.other.infra.database.rds.service.user.user.CpCabinConfigService;
|
||||
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
|
||||
import com.red.circle.other.infra.database.rds.service.user.user.CpValueService;
|
||||
import com.red.circle.other.infra.enums.user.user.CpRelationshipStatus;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class UserCpRightsConfigQueryExeTest {
|
||||
|
||||
@Test
|
||||
void formedRelationRightsLevelShouldStartFromZero() {
|
||||
Fixture fixture = new Fixture();
|
||||
|
||||
for (String relationType : List.of("CP", "BROTHER", "SISTERS")) {
|
||||
CpRightsConfigCO result = fixture.exe.execute(cmd(relationType));
|
||||
|
||||
assertEquals(relationType, result.getRelationType());
|
||||
assertEquals("NORMAL", result.getRelationshipStatus());
|
||||
assertEquals(0L, result.getIntimacyValue());
|
||||
assertEquals(6, result.getLevels().size());
|
||||
assertEquals(List.of(0, 1, 2, 3, 4, 5),
|
||||
result.getLevels().stream().map(CpRightsConfigCO.LevelCO::getLevel).toList());
|
||||
assertEquals(relationType + "_CABIN_LEVEL_0",
|
||||
result.getLevels().get(0).getRights().get(0).getRightKey());
|
||||
assertTrue(result.getLevels().get(0).getUnlocked());
|
||||
assertTrue(result.getLevels().get(0).getRights().get(0).getUnlocked());
|
||||
}
|
||||
}
|
||||
|
||||
private static CpRightsConfigQueryCmd cmd(String relationType) {
|
||||
CpRightsConfigQueryCmd cmd = new CpRightsConfigQueryCmd();
|
||||
cmd.setReqUserId(1001L);
|
||||
cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI"));
|
||||
cmd.setRelationType(relationType);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static class Fixture {
|
||||
|
||||
private final CpCabinConfigService cpCabinConfigService = mock(CpCabinConfigService.class);
|
||||
private final CpRelationshipService cpRelationshipService = mock(CpRelationshipService.class);
|
||||
private final CpValueService cpValueService = mock(CpValueService.class);
|
||||
private final UserCpRightsConfigQueryExe exe = new UserCpRightsConfigQueryExe(
|
||||
cpCabinConfigService, cpRelationshipService, cpValueService);
|
||||
|
||||
private Fixture() {
|
||||
for (String relationType : List.of("CP", "BROTHER", "SISTERS")) {
|
||||
when(cpRelationshipService.getByUserIdOrCpUserId(1001L, relationType))
|
||||
.thenReturn(relationship(relationType));
|
||||
}
|
||||
when(cpValueService.getById(3001L)).thenReturn(new CpValue().setCpVal(BigDecimal.ZERO));
|
||||
when(cpCabinConfigService.getCpRightsLevelConfig("LIKEI")).thenReturn(List.of(
|
||||
levelConfig("Level Zero", 0L),
|
||||
levelConfig("Level One", 100L),
|
||||
levelConfig("Level Two", 200L),
|
||||
levelConfig("Level Three", 300L),
|
||||
levelConfig("Level Four", 400L),
|
||||
levelConfig("Level Five", 500L)
|
||||
));
|
||||
}
|
||||
|
||||
private static CpRelationship relationship(String relationType) {
|
||||
return new CpRelationship()
|
||||
.setUserId(1001L)
|
||||
.setCpUserId(2001L)
|
||||
.setCpValId(3001L)
|
||||
.setRelationType(relationType)
|
||||
.setStatus(CpRelationshipStatus.NORMAL.name());
|
||||
}
|
||||
|
||||
private static CpCabinConfig levelConfig(String name, Long cabinUnlockValue) {
|
||||
return new CpCabinConfig()
|
||||
.setName(name)
|
||||
.setCabinUnlockValue(cabinUnlockValue)
|
||||
.setCover("https://example.com/" + name + ".png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ public class CpRightsConfigCO extends ClientObject {
|
||||
public static class LevelCO {
|
||||
|
||||
/**
|
||||
* Level number.
|
||||
* Level number, starts from 0.
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ public class CpCabinConfigServiceImpl extends
|
||||
BaseServiceImpl<CpCabinConfigDAO, CpCabinConfig> implements
|
||||
CpCabinConfigService {
|
||||
|
||||
private static final int CP_RIGHTS_LEVEL_COUNT = 5;
|
||||
private static final int CP_RIGHTS_LEVEL_COUNT = 6;
|
||||
|
||||
@Override
|
||||
public List<CpCabinConfig> getCpCabinConfig(String sysOriginName) {
|
||||
|
||||
@ -414,8 +414,8 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
|
||||
|
||||
private Long firstVipResourceId(VipLevelConfig vipConfig) {
|
||||
return Stream.of(
|
||||
vipConfig.getBadgeResourceId(),
|
||||
vipConfig.getShortBadgeResourceId(),
|
||||
vipConfig.getBadgeResourceId(),
|
||||
vipConfig.getAvatarFrameResourceId(),
|
||||
vipConfig.getEntryEffectResourceId(),
|
||||
vipConfig.getChatBubbleResourceId(),
|
||||
@ -431,12 +431,17 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa
|
||||
private String resolveVipCover(VipLevelConfig vipConfig,
|
||||
Map<Long, PropsSourceRecord> vipCoverMap) {
|
||||
PropsSourceRecord sourceRecord = vipCoverMap.get(firstVipResourceId(vipConfig));
|
||||
if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getCover())) {
|
||||
if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getCover())
|
||||
&& !sourceRecord.getCover().isBlank()) {
|
||||
return sourceRecord.getCover();
|
||||
}
|
||||
if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getSourceUrl())
|
||||
&& !sourceRecord.getSourceUrl().isBlank()) {
|
||||
return sourceRecord.getSourceUrl();
|
||||
}
|
||||
return Stream.of(
|
||||
vipConfig.getBadgeUrl(),
|
||||
vipConfig.getShortBadgeUrl(),
|
||||
vipConfig.getBadgeUrl(),
|
||||
vipConfig.getAvatarFrameUrl(),
|
||||
vipConfig.getEntryEffectUrl(),
|
||||
vipConfig.getChatBubbleUrl(),
|
||||
|
||||
@ -392,8 +392,8 @@ public class PropsActivityRewardGroupClientServiceImpl implements
|
||||
|
||||
private Long firstVipResourceId(VipLevelConfig vipConfig) {
|
||||
return Stream.of(
|
||||
vipConfig.getBadgeResourceId(),
|
||||
vipConfig.getShortBadgeResourceId(),
|
||||
vipConfig.getBadgeResourceId(),
|
||||
vipConfig.getAvatarFrameResourceId(),
|
||||
vipConfig.getEntryEffectResourceId(),
|
||||
vipConfig.getChatBubbleResourceId(),
|
||||
@ -422,8 +422,8 @@ public class PropsActivityRewardGroupClientServiceImpl implements
|
||||
return sourceRecord.getSourceUrl();
|
||||
}
|
||||
return Stream.of(
|
||||
vipConfig.getBadgeUrl(),
|
||||
vipConfig.getShortBadgeUrl(),
|
||||
vipConfig.getBadgeUrl(),
|
||||
vipConfig.getAvatarFrameUrl(),
|
||||
vipConfig.getEntryEffectUrl(),
|
||||
vipConfig.getChatBubbleUrl(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user