修复bug

This commit is contained in:
zhx 2026-07-08 16:33:57 +08:00
parent 49a9e10ba2
commit 292177e7ff
13 changed files with 356 additions and 29 deletions

View File

@ -58,6 +58,16 @@
<build> <build>
<finalName>${project.name}-${revision}</finalName> <finalName>${project.name}-${revision}</finalName>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<!-- GeoLite2 数据库是二进制资源,不能按文本变量过滤。 -->
<nonFilteredFileExtension>mmdb</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<plugin> <plugin>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -310,6 +310,11 @@ public class EndpointRestController {
} }
} }
log.info("IP:{}, 国家信息:{}", ip, dataObject); log.info("IP:{}, 国家信息:{}", ip, dataObject);
// IP 归属接口超时或返回异常时不能让登录链路 NPE中港台拦截只在拿到明确国家信息时执行
if (dataObject == null) {
log.warn("IP:{} 国家信息获取失败,跳过 IP 国家校验", ip);
return null;
}
if (dataObject != null && "中国".equals(dataObject.getString("country"))) { if (dataObject != null && "中国".equals(dataObject.getString("country"))) {
if (!Arrays.asList("香港", "澳门", "台湾").contains(dataObject.getString("region"))) { if (!Arrays.asList("香港", "澳门", "台湾").contains(dataObject.getString("region"))) {
ResponseAssert.isTrue(UserErrorCode.REGISTRATION_FAILED, false); ResponseAssert.isTrue(UserErrorCode.REGISTRATION_FAILED, false);

View File

@ -36,6 +36,7 @@ import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.enums.material.NobleVipEnum; import com.red.circle.other.inner.enums.material.NobleVipEnum;
import com.red.circle.other.inner.enums.material.PropsCommodityType; import com.red.circle.other.inner.enums.material.PropsCommodityType;
import com.red.circle.other.inner.enums.team.TeamMemberRole; import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.model.cmd.count.PropsSaleQuotaQryCmd; import com.red.circle.other.inner.model.cmd.count.PropsSaleQuotaQryCmd;
import com.red.circle.other.inner.model.cmd.count.SysOriginUserQryCmd; import com.red.circle.other.inner.model.cmd.count.SysOriginUserQryCmd;
import com.red.circle.other.inner.model.cmd.tools.UserRoomRegionUpdateCmd; import com.red.circle.other.inner.model.cmd.tools.UserRoomRegionUpdateCmd;
@ -199,6 +200,15 @@ public class UserBaseInfoServiceImpl implements
if (Objects.isNull(param)) { if (Objects.isNull(param)) {
return; return;
} }
UserProfileDTO currentProfile = ResponseAssert.requiredSuccess(
userProfileClient.getByUserId(param.getId()));
ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, currentProfile);
boolean countryChanged = countryChanged(param, currentProfile);
if (countryChanged) {
fillCountryAndAssertAllowed(param, currentProfile);
}
userProfileClient.updateSelectiveById(userAppConvertor.toUserBaseInfoCmd(param)); userProfileClient.updateSelectiveById(userAppConvertor.toUserBaseInfoCmd(param));
// 移除所有缓存 // 移除所有缓存
userProfileClient.removeCacheAll(param.getId()); userProfileClient.removeCacheAll(param.getId());
@ -207,7 +217,61 @@ public class UserBaseInfoServiceImpl implements
setUserIds(List.of(param.getId())) setUserIds(List.of(param.getId()))
.setEvent(NewsletterEvent.USER_INFO_CHANGE) .setEvent(NewsletterEvent.USER_INFO_CHANGE)
); );
roomManagerClient.updateUserCountry(param.getId(), param.getCountryCode(), param.getCountryName()); if (countryChanged) {
roomManagerClient.updateUserCountry(param.getId(), param.getCountryCode(),
param.getCountryName());
}
}
private boolean countryChanged(UserProfileUpdateCmd param, UserProfileDTO currentProfile) {
return Objects.nonNull(param.getCountryId())
&& !Objects.equals(param.getCountryId(), currentProfile.getCountryId());
}
private void fillCountryAndAssertAllowed(UserProfileUpdateCmd param,
UserProfileDTO currentProfile) {
SysCountryCodeDTO country = ResponseAssert.requiredSuccess(
countryCodeClient.getById(param.getCountryId()));
ResponseAssert.notNull(UserErrorCode.SYS_REGION_NOT_EXIST_ERROR, country);
boolean locked = hasCountryLockedIdentity(param.getId());
log.warn("console country-change identity check userId={}, locked={}, currentCountryId={}, targetCountryId={}",
param.getId(), locked, currentProfile.getCountryId(), param.getCountryId());
// 国家变更只限制平台身份用户普通用户允许后台多次调整并同步房间国家
ResponseAssert.isFalse(UserErrorCode.USER_COUNTRY_UPDATE_NOT_ALLOWED, locked);
param.setCountryCode(country.getAlphaTwo());
param.setCountryName(country.getCountryName());
}
private boolean hasCountryLockedIdentity(Long userId) {
TeamMemberDTO teamMember = getTeamMember(userId);
boolean anchor = Objects.nonNull(teamMember);
boolean agent = anchor && TeamMemberRole.OWN.eq(teamMember.getRole());
boolean bd = checkBd(userId);
boolean bdLeader = checkBdLeader(userId);
log.warn("console country-change identity detail userId={}, anchor={}, agent={}, bd={}, bdLeader={}, teamMemberRole={}",
userId, anchor, agent, bd, bdLeader,
Objects.isNull(teamMember) ? null : teamMember.getRole());
return anchor || agent || bd || bdLeader;
}
private TeamMemberDTO getTeamMember(Long userId) {
try {
return ResponseAssert.requiredSuccess(teamProfileClient.getByMemberId(userId));
} catch (Exception ex) {
log.warn("console country-change team member check failed, treat as no team member, userId={}",
userId, ex);
return null;
}
}
private boolean checkBd(Long userId) {
return Boolean.TRUE.equals(ResponseAssert.requiredSuccess(bdTeamInfoClient.check(userId)));
}
private boolean checkBdLeader(Long userId) {
return Boolean.TRUE.equals(ResponseAssert.requiredSuccess(bdTeamLeaderClient.check(userId)));
} }
@Override @Override

View File

@ -131,6 +131,10 @@ public class TencentCosOssService implements OssService {
if (StringUtils.isAnyBlank(sourceKey, targetKey)) { if (StringUtils.isAnyBlank(sourceKey, targetKey)) {
return; return;
} }
if (StringUtils.equals(sourceKey, targetKey)) {
log.warn("Skip COS image process because source and target key are the same, key={}", sourceKey);
return;
}
try { try {
PicOperations picOperations = new PicOperations(); PicOperations picOperations = new PicOperations();
@ -165,7 +169,18 @@ public class TencentCosOssService implements OssService {
@Override @Override
public String processImgSaveAsCompressZoom(String source, int height) { public String processImgSaveAsCompressZoom(String source, int height) {
return processImgSaveAsCompressZoom(source, source, height); if (isHttpUrl(source)) {
return source;
}
if (StringUtils.endsWith(source, ".gift")) {
return getAccessUrl(source);
}
String sourceKey = normalizeKey(source);
if (StringUtils.isBlank(sourceKey)) {
return "";
}
// COS does not allow persistent image processing to save over the source object.
return processImgSaveAsCompressZoom(sourceKey, buildProcessedImageKey(sourceKey, height), height);
} }
private void uploadInputStream(InputStream inputStream, String fileName, ObjectMetadata metadata) { private void uploadInputStream(InputStream inputStream, String fileName, ObjectMetadata metadata) {
@ -203,6 +218,17 @@ public class TencentCosOssService implements OssService {
return "imageMogr2/thumbnail/x1024"; return "imageMogr2/thumbnail/x1024";
} }
private String buildProcessedImageKey(String sourceKey, int height) {
String key = normalizeKey(sourceKey);
String suffix = "_x" + height;
String extension = StringUtils.substringAfterLast(key, ".");
String baseName = StringUtils.substringBeforeLast(key, ".");
if (StringUtils.isBlank(extension) || StringUtils.equals(baseName, key)) {
return key + suffix;
}
return baseName + suffix + "." + extension;
}
private String getFilePath(String urlOrKey) { private String getFilePath(String urlOrKey) {
if (StringUtils.isBlank(urlOrKey)) { if (StringUtils.isBlank(urlOrKey)) {
return urlOrKey; return urlOrKey;

View File

@ -79,7 +79,8 @@ public class GiftGiveAwayBatchCmdExe {
checkDynamicRegion(cmd); checkDynamicRegion(cmd);
if (giftConfig.getGiftCandy().equals(new BigDecimal("0.00"))) { if (giftConfig.getGiftCandy().equals(new BigDecimal("0.00"))) {
BigDecimal dollarAmount = walletGoldClient.getBalance(cmd.requiredReqUserId()).getBody().getDollarAmount(); BigDecimal dollarAmount = ResponseAssert.requiredSuccess(
walletGoldClient.getBalance(cmd.requiredReqUserId())).getDollarAmount();
log.info("赠送普通礼物金额为0,余额为 {}", dollarAmount); log.info("赠送普通礼物金额为0,余额为 {}", dollarAmount);
return dollarAmount; return dollarAmount;
} }
@ -115,7 +116,8 @@ public class GiftGiveAwayBatchCmdExe {
checkPrivateAcceptUser(cmd); checkPrivateAcceptUser(cmd);
if (isZeroGiftCandy(giftConfig)) { if (isZeroGiftCandy(giftConfig)) {
BigDecimal dollarAmount = walletGoldClient.getBalance(cmd.requiredReqUserId()).getBody().getDollarAmount(); BigDecimal dollarAmount = ResponseAssert.requiredSuccess(
walletGoldClient.getBalance(cmd.requiredReqUserId())).getDollarAmount();
log.info("私聊赠送普通礼物金额为0,余额为 {}", dollarAmount); log.info("私聊赠送普通礼物金额为0,余额为 {}", dollarAmount);
return dollarAmount; return dollarAmount;
} }
@ -175,7 +177,9 @@ public class GiftGiveAwayBatchCmdExe {
if (isGiftTypeEqGold(giftConfig)) { if (isGiftTypeEqGold(giftConfig)) {
BigDecimal amount = cmd.calculateConsumeGolds(giftConfig.getGiftCandy()); BigDecimal amount = cmd.calculateConsumeGolds(giftConfig.getGiftCandy());
// 消耗金币 // 消耗金币
WalletReceiptResDTO receiptRes = walletGoldClient.changeBalance(GoldReceiptCmd.builder() // 钱包返回余额不足等业务错误时必须直接透传避免空 body 继续组装礼物流水触发 500
WalletReceiptResDTO receiptRes = ResponseAssert.requiredSuccess(
walletGoldClient.changeBalance(GoldReceiptCmd.builder()
.appExpenditure() .appExpenditure()
.userId(cmd.requiredReqUserId()) .userId(cmd.requiredReqUserId())
.eventId(giftConfig.getId()) .eventId(giftConfig.getId())
@ -183,7 +187,7 @@ public class GiftGiveAwayBatchCmdExe {
.origin(GoldOrigin.GIVE_GIFT + "_" + giftConfig.getGiftTab()) .origin(GoldOrigin.GIVE_GIFT + "_" + giftConfig.getGiftTab())
.amount(amount) .amount(amount)
.closeDelayAsset() .closeDelayAsset()
.build()).getBody(); .build()));
return new WalletReceiptDTO() return new WalletReceiptDTO()
.setConsumeId(receiptRes.getAssetRecordId()) .setConsumeId(receiptRes.getAssetRecordId())
.setBalance(receiptRes.getBalance().getDollarAmount()) .setBalance(receiptRes.getBalance().getDollarAmount())
@ -192,7 +196,7 @@ public class GiftGiveAwayBatchCmdExe {
if (isGiftTypeEqDiamond(giftConfig)) { if (isGiftTypeEqDiamond(giftConfig)) {
// 消费积分 // 消费积分
return walletDiamondClient.changeBalance(new DiamondReceiptCmd() return ResponseAssert.requiredSuccess(walletDiamondClient.changeBalance(new DiamondReceiptCmd()
.setConsumeId(Objects.toString(giftConfig.getId())) .setConsumeId(Objects.toString(giftConfig.getId()))
.setType(ReceiptType.EXPENDITURE) .setType(ReceiptType.EXPENDITURE)
.setTrackId(giftConfig.getId()) .setTrackId(giftConfig.getId())
@ -201,7 +205,7 @@ public class GiftGiveAwayBatchCmdExe {
.setOrigin(DiamondOrigin.GIVE_GIFT) .setOrigin(DiamondOrigin.GIVE_GIFT)
.setAmount(cmd.calculateConsumeGolds(giftConfig.getGiftCandy())) .setAmount(cmd.calculateConsumeGolds(giftConfig.getGiftCandy()))
.setCreateTime(TimestampUtils.now()) .setCreateTime(TimestampUtils.now())
).getBody(); ));
} }
// 类型不在范围 // 类型不在范围
@ -226,7 +230,9 @@ public class GiftGiveAwayBatchCmdExe {
if (isGiftTypeEqGold(giftConfig)) { if (isGiftTypeEqGold(giftConfig)) {
BigDecimal amount = cmd.calculateConsumeGolds(giftConfig.getGiftCandy()); BigDecimal amount = cmd.calculateConsumeGolds(giftConfig.getGiftCandy());
WalletReceiptResDTO receiptRes = walletGoldClient.changeBalance(GoldReceiptCmd.builder() // 钱包返回余额不足等业务错误时必须直接透传避免空 body 继续组装私信礼物流水触发 500
WalletReceiptResDTO receiptRes = ResponseAssert.requiredSuccess(
walletGoldClient.changeBalance(GoldReceiptCmd.builder()
.appExpenditure() .appExpenditure()
.userId(cmd.requiredReqUserId()) .userId(cmd.requiredReqUserId())
.eventId(giftConfig.getId()) .eventId(giftConfig.getId())
@ -234,7 +240,7 @@ public class GiftGiveAwayBatchCmdExe {
.origin(GoldOrigin.GIVE_GIFT + "_" + giftConfig.getGiftTab()) .origin(GoldOrigin.GIVE_GIFT + "_" + giftConfig.getGiftTab())
.amount(amount) .amount(amount)
.closeDelayAsset() .closeDelayAsset()
.build()).getBody(); .build()));
return new WalletReceiptDTO() return new WalletReceiptDTO()
.setConsumeId(receiptRes.getAssetRecordId()) .setConsumeId(receiptRes.getAssetRecordId())
.setBalance(receiptRes.getBalance().getDollarAmount()) .setBalance(receiptRes.getBalance().getDollarAmount())
@ -242,7 +248,7 @@ public class GiftGiveAwayBatchCmdExe {
} }
if (isGiftTypeEqDiamond(giftConfig)) { if (isGiftTypeEqDiamond(giftConfig)) {
return walletDiamondClient.changeBalance(new DiamondReceiptCmd() return ResponseAssert.requiredSuccess(walletDiamondClient.changeBalance(new DiamondReceiptCmd()
.setConsumeId(Objects.toString(giftConfig.getId())) .setConsumeId(Objects.toString(giftConfig.getId()))
.setType(ReceiptType.EXPENDITURE) .setType(ReceiptType.EXPENDITURE)
.setTrackId(giftConfig.getId()) .setTrackId(giftConfig.getId())
@ -251,7 +257,7 @@ public class GiftGiveAwayBatchCmdExe {
.setOrigin(DiamondOrigin.GIVE_GIFT) .setOrigin(DiamondOrigin.GIVE_GIFT)
.setAmount(cmd.calculateConsumeGolds(giftConfig.getGiftCandy())) .setAmount(cmd.calculateConsumeGolds(giftConfig.getGiftCandy()))
.setCreateTime(TimestampUtils.now()) .setCreateTime(TimestampUtils.now())
).getBody(); ));
} }
ResponseAssert.failure(CommonErrorCode.TYPE_IS_NOT_IN_SCOPE); ResponseAssert.failure(CommonErrorCode.TYPE_IS_NOT_IN_SCOPE);

View File

@ -1,5 +1,6 @@
package com.red.circle.other.app.command.room.query; package com.red.circle.other.app.command.room.query;
import com.alibaba.fastjson.JSON;
import com.red.circle.other.app.common.room.RoomRegionCountryCommon; import com.red.circle.other.app.common.room.RoomRegionCountryCommon;
import com.red.circle.other.app.common.room.RoomRegionCountryCommon.RegionCountryScope; import com.red.circle.other.app.common.room.RoomRegionCountryCommon.RegionCountryScope;
import com.red.circle.other.app.common.room.RoomVoiceProfileCommon; import com.red.circle.other.app.common.room.RoomVoiceProfileCommon;
@ -10,11 +11,19 @@ import com.red.circle.other.app.service.room.RocketStatusCacheService;
import com.red.circle.other.domain.rocket.RocketStatus; import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom;
import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService;
import com.red.circle.other.inner.model.dto.live.ActiveVoiceRoomCO;
import com.red.circle.tool.core.collection.CollectionUtils; import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -24,15 +33,18 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class RoomVoiceExploreQryExe { public class RoomVoiceExploreQryExe {
private static final int EXPLORE_LIMIT = 100; private static final int EXPLORE_LIMIT = 100;
private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:*";
private final ActiveVoiceRoomService activeVoiceRoomService; private final ActiveVoiceRoomService activeVoiceRoomService;
private final RoomVoiceProfileCommon roomVoiceProfileCommon; private final RoomVoiceProfileCommon roomVoiceProfileCommon;
private final RocketStatusCacheService rocketStatusCacheService; private final RocketStatusCacheService rocketStatusCacheService;
private final GameLudoService gameLudoService; private final GameLudoService gameLudoService;
private final RoomRegionCountryCommon roomRegionCountryCommon; private final RoomRegionCountryCommon roomRegionCountryCommon;
private final RedisTemplate<String, Object> redisTemplate;
public List<RoomVoiceProfileCO> execute(RoomExploreQryCmd cmd) { public List<RoomVoiceProfileCO> execute(RoomExploreQryCmd cmd) {
RegionCountryScope regionScope = roomRegionCountryCommon.resolve( RegionCountryScope regionScope = roomRegionCountryCommon.resolve(
@ -42,6 +54,8 @@ public class RoomVoiceExploreQryExe {
List<ActiveVoiceRoom> rooms = activeVoiceRoomService.listExplore( List<ActiveVoiceRoom> rooms = activeVoiceRoomService.listExplore(
cmd.requireReqSysOrigin(), regionScope.getRegionCode(), countryCode, cmd.requireReqSysOrigin(), regionScope.getRegionCode(), countryCode,
regionScope.getCountryCodes(), EXPLORE_LIMIT); regionScope.getCountryCodes(), EXPLORE_LIMIT);
rooms = appendEmptyRooms(
rooms, cmd.requireReqSysOrigin(), regionScope.getCountryCodes(), countryCode);
List<RoomVoiceProfileCO> roomList = roomVoiceProfileCommon.toListRoomVoiceProfileCO(rooms); List<RoomVoiceProfileCO> roomList = roomVoiceProfileCommon.toListRoomVoiceProfileCO(rooms);
if (CollectionUtils.isEmpty(roomList)) { if (CollectionUtils.isEmpty(roomList)) {
@ -54,6 +68,92 @@ public class RoomVoiceExploreQryExe {
return roomList; return roomList;
} }
private List<ActiveVoiceRoom> appendEmptyRooms(
List<ActiveVoiceRoom> rooms, String sysOrigin, Set<String> visibleCountryCodes,
String selectedCountryCode) {
List<ActiveVoiceRoom> emptyRooms = getEmptyRoomsFromCache(
sysOrigin, visibleCountryCodes, selectedCountryCode);
if (CollectionUtils.isEmpty(emptyRooms)) {
return rooms;
}
List<ActiveVoiceRoom> mergedRooms = CollectionUtils.isEmpty(rooms)
? new ArrayList<>()
: new ArrayList<>(rooms);
Set<Long> existingRoomIds = CollectionUtils.isEmpty(rooms)
? Set.of()
: rooms.stream().map(ActiveVoiceRoom::getId).collect(Collectors.toSet());
emptyRooms.stream()
.filter(room -> !existingRoomIds.contains(room.getId()))
.forEach(mergedRooms::add);
return mergedRooms;
}
private List<ActiveVoiceRoom> getEmptyRoomsFromCache(
String sysOrigin, Set<String> visibleCountryCodes, String selectedCountryCode) {
try {
Set<String> keys = redisTemplate.keys(EMPTY_ROOM_CACHE_KEY);
if (CollectionUtils.isEmpty(keys)) {
return CollectionUtils.newArrayList();
}
List<ActiveVoiceRoom> emptyRooms = new ArrayList<>();
for (String key : keys) {
Object cachedRoom = redisTemplate.opsForValue().get(key);
ActiveVoiceRoom room = convertToActiveVoiceRoom(cachedRoom);
if (isVisibleEmptyRoom(room, sysOrigin, visibleCountryCodes, selectedCountryCode)) {
emptyRooms.add(room);
}
}
return emptyRooms;
} catch (Exception e) {
log.warn("获取国家筛选空房间缓存失败", e);
return CollectionUtils.newArrayList();
}
}
private boolean isVisibleEmptyRoom(
ActiveVoiceRoom room, String sysOrigin, Set<String> visibleCountryCodes,
String selectedCountryCode) {
if (room == null || !Objects.equals(sysOrigin, room.getSysOrigin())) {
return false;
}
String roomCountryCode = roomRegionCountryCommon.normalizeCountryCode(room.getCountryCode());
if (!visibleCountryCodes.contains(roomCountryCode)) {
return false;
}
// 国家筛选页需要和首页 hot 使用同一套空房缓存否则退出房间后的 10 分钟内两边数量会不一致
return StringUtils.isBlank(selectedCountryCode)
|| Objects.equals(selectedCountryCode, roomCountryCode);
}
private ActiveVoiceRoom convertToActiveVoiceRoom(Object cachedRoom) {
if (cachedRoom == null) {
return null;
}
try {
ActiveVoiceRoomCO roomCO = JSON.parseObject(String.valueOf(cachedRoom), ActiveVoiceRoomCO.class);
if (roomCO == null) {
return null;
}
ActiveVoiceRoom room = new ActiveVoiceRoom();
BeanUtils.copyProperties(roomCO, room);
room.setOnlineQuantity(0L);
if (room.getWeights() == null) {
room.setWeights(0);
}
if (room.getFixedWeights() == null) {
room.setFixedWeights(0);
}
return room;
} catch (Exception e) {
log.warn("转换国家筛选空房间缓存失败", e);
return null;
}
}
private void fillRocketStatus(List<RoomVoiceProfileCO> roomList) { private void fillRocketStatus(List<RoomVoiceProfileCO> roomList) {
List<Long> roomIds = roomList.stream().map(RoomVoiceProfileCO::getId).toList(); List<Long> roomIds = roomList.stream().map(RoomVoiceProfileCO::getId).toList();
Map<Long, RocketStatus> rocketStatusMap = rocketStatusCacheService.batchGetRocketStatus(roomIds); Map<Long, RocketStatus> rocketStatusMap = rocketStatusCacheService.batchGetRocketStatus(roomIds);

View File

@ -5,6 +5,7 @@ import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.dto.cmd.user.user.AccountBindUserAuthTypeCmd; import com.red.circle.other.app.dto.cmd.user.user.AccountBindUserAuthTypeCmd;
import com.red.circle.other.infra.database.cache.service.user.UserAccountAuthCacheService;
import com.red.circle.other.infra.database.rds.entity.user.user.AccountAuth; import com.red.circle.other.infra.database.rds.entity.user.user.AccountAuth;
import com.red.circle.other.infra.database.rds.entity.user.user.AuthType; import com.red.circle.other.infra.database.rds.entity.user.user.AuthType;
import com.red.circle.other.infra.database.rds.entity.user.user.PasswordLog; import com.red.circle.other.infra.database.rds.entity.user.user.PasswordLog;
@ -34,6 +35,7 @@ public class AccountBindUserAuthTypeCmdExe {
private final AuthTypeService authTypeService; private final AuthTypeService authTypeService;
private final AccountAuthService accountAuthService; private final AccountAuthService accountAuthService;
private final PasswordLogService passwordLogService; private final PasswordLogService passwordLogService;
private final UserAccountAuthCacheService accountAuthCacheService;
public void execute(AccountBindUserAuthTypeCmd cmd) { public void execute(AccountBindUserAuthTypeCmd cmd) {
AuthType authType = authTypeService.getAuthInfo( AuthType authType = authTypeService.getAuthInfo(
@ -58,6 +60,8 @@ public class AccountBindUserAuthTypeCmdExe {
); );
authTypeService.save(toAuthType(cmd.requiredReqUserId(), cmd)); authTypeService.save(toAuthType(cmd.requiredReqUserId(), cmd));
// 账号密码设置成功后清理旧的登录失败次数避免新密码立即被历史错误次数拦截
accountAuthCacheService.deleteByUserId(cmd.requiredReqUserId());
} }
public void accountUpdate(AccountBindUserAuthTypeCmd cmd) { public void accountUpdate(AccountBindUserAuthTypeCmd cmd) {
@ -85,6 +89,9 @@ public class AccountBindUserAuthTypeCmdExe {
.pwd(cmd.getPwd()) .pwd(cmd.getPwd())
.imei(cmd.getReqImei()) .imei(cmd.getReqImei())
.build()); .build());
// 修改密码成功后清理旧的登录失败次数确保用户可以立即用新密码登录
accountAuthCacheService.deleteByUserId(cmd.requiredReqUserId());
} }

View File

@ -10,11 +10,17 @@ import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.cmd.user.user.UserProfileModifyCmd; import com.red.circle.other.app.dto.cmd.user.user.UserProfileModifyCmd;
import com.red.circle.other.app.service.user.user.UserIdentityService;
import com.red.circle.other.domain.gateway.approval.ProfileApprovalGateway; import com.red.circle.other.domain.gateway.approval.ProfileApprovalGateway;
import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.model.approval.ProfileApprovalContent; import com.red.circle.other.domain.model.approval.ProfileApprovalContent;
import com.red.circle.other.domain.model.user.UserProfile; import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.cache.service.cp.CpRelationshipCacheService; import com.red.circle.other.infra.database.cache.service.cp.CpRelationshipCacheService;
import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode;
import com.red.circle.other.infra.database.rds.service.sys.SysCountryCodeService;
import com.red.circle.other.inner.asserts.DynamicErrorCode; import com.red.circle.other.inner.asserts.DynamicErrorCode;
import com.red.circle.tool.core.collection.CollectionUtils; import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils; import com.red.circle.tool.core.text.StringUtils;
@ -25,7 +31,9 @@ import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -47,6 +55,11 @@ public class UpdateUserProfileCmdExe {
private final ProfileApprovalGateway profileApprovalGateway; private final ProfileApprovalGateway profileApprovalGateway;
private final UserProfileAppConvertor userProfileAppConvertor; private final UserProfileAppConvertor userProfileAppConvertor;
private final CpRelationshipCacheService cpRelationshipCacheService; private final CpRelationshipCacheService cpRelationshipCacheService;
private final UserIdentityService userIdentityService;
private final SysCountryCodeService sysCountryCodeService;
private final RoomProfileManagerService roomProfileManagerService;
private final ActiveVoiceRoomService activeVoiceRoomService;
private final UserRegionGateway userRegionGateway;
public UserProfileDTO execute(UserProfileModifyCmd cmd) { public UserProfileDTO execute(UserProfileModifyCmd cmd) {
//敏感词 //敏感词
@ -56,7 +69,7 @@ public class UpdateUserProfileCmdExe {
userProfileGateway.removeCacheAll(cmd.requiredReqUserId()); userProfileGateway.removeCacheAll(cmd.requiredReqUserId());
UserProfile userProfile = userProfileGateway.getByUserId(cmd.requiredReqUserId()); UserProfile userProfile = userProfileGateway.getByUserId(cmd.requiredReqUserId());
ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, userProfile); ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, userProfile);
assertCountryNotChanged(cmd, userProfile); SysCountryCode changeCountry = getChangeCountry(cmd, userProfile);
if (StringUtils.isNotBlank(cmd.getUserAvatar())) { if (StringUtils.isNotBlank(cmd.getUserAvatar())) {
cmd.setUserAvatar(ossServiceClient.processImgSaveAsCompressZoom(cmd.getUserAvatar(), cmd.setUserAvatar(ossServiceClient.processImgSaveAsCompressZoom(cmd.getUserAvatar(),
ImageSizeConst.COVER_HEIGHT).getBody() ImageSizeConst.COVER_HEIGHT).getBody()
@ -66,7 +79,13 @@ public class UpdateUserProfileCmdExe {
UserProfile updateUserProfile = userProfileAppConvertor.toUserProfile(cmd); UserProfile updateUserProfile = userProfileAppConvertor.toUserProfile(cmd);
ResponseAssert.notNull(CommonErrorCode.UPDATE_FAILURE, updateUserProfile); ResponseAssert.notNull(CommonErrorCode.UPDATE_FAILURE, updateUserProfile);
updateUserProfile.setId(userProfile.getId()); updateUserProfile.setId(userProfile.getId());
if (Objects.nonNull(changeCountry)) {
updateUserProfile.setCountryId(changeCountry.getId());
updateUserProfile.setCountryCode(changeCountry.getAlphaTwo());
updateUserProfile.setCountryName(changeCountry.getCountryName());
} else {
updateUserProfile.setCountryId(null); updateUserProfile.setCountryId(null);
}
// 处理背景照片如果传入非null则更新包括空列表 // 处理背景照片如果传入非null则更新包括空列表
if (cmd.getBackgroundPhotos() != null) { if (cmd.getBackgroundPhotos() != null) {
@ -101,8 +120,11 @@ public class UpdateUserProfileCmdExe {
processPhotoStatus(updateUserProfile.getPersonalPhotos(), userProfile.getPersonalPhotos()); processPhotoStatus(updateUserProfile.getPersonalPhotos(), userProfile.getPersonalPhotos());
userProfileGateway.updateSelectiveById(updateUserProfile); userProfileGateway.updateSelectiveById(updateUserProfile);
if (Objects.nonNull(changeCountry)) {
syncRoomCountry(userProfile.getId(), changeCountry);
}
syncProperty(userProfile, cmd); syncProperty(userProfile, cmd, changeCountry);
log.warn("cmd {},{}", cmd, userProfile); log.warn("cmd {},{}", cmd, userProfile);
updateUserProfile.setOriginSys(cmd.requireReqSysOrigin()); updateUserProfile.setOriginSys(cmd.requireReqSysOrigin());
submitApproval(updateUserProfile); submitApproval(updateUserProfile);
@ -112,12 +134,31 @@ public class UpdateUserProfileCmdExe {
return userProfileAppConvertor.toUserProfileDTO(userProfile); return userProfileAppConvertor.toUserProfileDTO(userProfile);
} }
private void assertCountryNotChanged(UserProfileModifyCmd cmd, UserProfile userProfile) { private SysCountryCode getChangeCountry(UserProfileModifyCmd cmd, UserProfile userProfile) {
// App 保存资料可能会携带当前国家 id只拦截真实变更避免昵称头像等普通资料保存被误伤 // App 保存资料可能会携带当前国家 id只拦截真实变更避免昵称头像等普通资料保存被误伤
if (Objects.nonNull(cmd.getCountryId()) if (Objects.isNull(cmd.getCountryId())
&& !Objects.equals(userProfile.getCountryId(), cmd.getCountryId())) { || Objects.equals(userProfile.getCountryId(), cmd.getCountryId())) {
ResponseAssert.failure(UserErrorCode.USER_COUNTRY_UPDATE_NOT_ALLOWED); return null;
} }
Map<Long, SysCountryCode> countryMap = sysCountryCodeService.mapByIdes(Set.of(cmd.getCountryId()));
SysCountryCode country = countryMap.get(cmd.getCountryId());
ResponseAssert.notNull(UserErrorCode.SYS_REGION_NOT_EXIST_ERROR, country);
assertAllowCountryChange(userProfile.getId());
return country;
}
private void assertAllowCountryChange(Long userId) {
ResponseAssert.isFalse(UserErrorCode.USER_COUNTRY_UPDATE_NOT_ALLOWED,
userIdentityService.hasCountryLockedIdentity(userId));
}
private void syncRoomCountry(Long userId, SysCountryCode country) {
userProfileGateway.removeCacheAll(userId);
String regionCode = userRegionGateway.getRegionCode(userId);
roomProfileManagerService.updateUserCountry(userId, country.getAlphaTwo(), country.getCountryName());
activeVoiceRoomService.updateUserCountry(userId, country.getAlphaTwo(), country.getCountryName(),
regionCode);
} }
private void submitApproval(UserProfile updateUserProfile) { private void submitApproval(UserProfile updateUserProfile) {
@ -183,7 +224,7 @@ public class UpdateUserProfileCmdExe {
} }
private void syncProperty(UserProfile oldProfile, UserProfileModifyCmd cmd) { private void syncProperty(UserProfile oldProfile, UserProfileModifyCmd cmd, SysCountryCode changeCountry) {
if (StringUtils.isNotBlank(cmd.getUserNickname())) { if (StringUtils.isNotBlank(cmd.getUserNickname())) {
oldProfile.setUserNickname(cmd.getUserNickname()); oldProfile.setUserNickname(cmd.getUserNickname());
@ -204,6 +245,12 @@ public class UpdateUserProfileCmdExe {
oldProfile.setUserSex(cmd.getUserSex()); oldProfile.setUserSex(cmd.getUserSex());
} }
if (Objects.nonNull(changeCountry)) {
oldProfile.setCountryId(changeCountry.getId());
oldProfile.setCountryCode(changeCountry.getAlphaTwo());
oldProfile.setCountryName(changeCountry.getCountryName());
}
} }
private void processAge(UserProfile userProfile) { private void processAge(UserProfile userProfile) {

View File

@ -2,22 +2,21 @@ package com.red.circle.other.app.service.user.user;
import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.app.dto.cmd.material.PropsStoreTypeQryCmd; import com.red.circle.other.app.dto.cmd.material.PropsStoreTypeQryCmd;
import com.red.circle.other.app.dto.h5.UserIdentityVO; import com.red.circle.other.app.dto.h5.UserIdentityVO;
import com.red.circle.other.app.service.team.TeamBdService; import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMember;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberService;
import com.red.circle.other.infra.database.rds.entity.sys.Administrator; import com.red.circle.other.infra.database.rds.entity.sys.Administrator;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorAuthService; import com.red.circle.other.infra.database.rds.service.sys.AdministratorAuthService;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorService; import com.red.circle.other.infra.database.rds.service.sys.AdministratorService;
import com.red.circle.other.inner.endpoint.team.bd.BdTeamInfoClient; import com.red.circle.other.inner.endpoint.team.bd.BdTeamInfoClient;
import com.red.circle.other.inner.endpoint.team.bd.BdTeamLeaderClient; import com.red.circle.other.inner.endpoint.team.bd.BdTeamLeaderClient;
import com.red.circle.other.inner.endpoint.team.target.TeamProfileClient;
import com.red.circle.other.inner.enums.material.PropsCommodityType; import com.red.circle.other.inner.enums.material.PropsCommodityType;
import com.red.circle.other.inner.enums.sys.appmanager.RoomRolesEnum; import com.red.circle.other.inner.enums.sys.appmanager.RoomRolesEnum;
import com.red.circle.other.inner.enums.team.TeamMemberRole; import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.model.dto.agency.agency.TeamMemberDTO;
import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient; import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@ -26,9 +25,10 @@ import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class UserIdentityServiceImpl implements UserIdentityService { public class UserIdentityServiceImpl implements UserIdentityService {
private final TeamProfileClient teamProfileClient; private final TeamMemberService teamMemberService;
private final FreightGoldClient freightGoldClient; private final FreightGoldClient freightGoldClient;
private final BdTeamInfoClient bdTeamInfoClient; private final BdTeamInfoClient bdTeamInfoClient;
private final AdministratorService administratorService; private final AdministratorService administratorService;
@ -37,8 +37,7 @@ public class UserIdentityServiceImpl implements UserIdentityService {
@Override @Override
public UserIdentityVO getUserIdentity(Long userId) { public UserIdentityVO getUserIdentity(Long userId) {
TeamMemberDTO teamMember = ResponseAssert.requiredSuccess( TeamMember teamMember = teamMemberService.getByMemberId(userId);
teamProfileClient.getByMemberId(userId));
Administrator administrator = administratorService.getByUserId(userId); Administrator administrator = administratorService.getByUserId(userId);
boolean isValidAdmin = administrator != null && Boolean.TRUE.equals(administrator.getStatus()); boolean isValidAdmin = administrator != null && Boolean.TRUE.equals(administrator.getStatus());
@ -63,6 +62,48 @@ public class UserIdentityServiceImpl implements UserIdentityService {
; ;
} }
@Override
public boolean hasCountryLockedIdentity(Long userId) {
TeamMember teamMember = teamMemberService.getByMemberId(userId);
boolean anchor = hasAnchorIdentity(teamMember);
boolean agent = hasAgentIdentity(teamMember);
boolean bd = checkBd(userId);
boolean bdLeader = checkBdLeader(userId);
boolean locked = anchor || agent || bd || bdLeader;
log.warn("country-change identity check userId={}, locked={}, anchor={}, agent={}, bd={}, bdLeader={}, teamMemberId={}, teamMemberRole={}, teamId={}",
userId, locked, anchor, agent, bd, bdLeader, getTeamMemberId(teamMember),
getTeamMemberRole(teamMember), getTeamId(teamMember));
return locked;
}
private boolean hasAnchorIdentity(TeamMember teamMember) {
return Objects.nonNull(teamMember);
}
private boolean hasAgentIdentity(TeamMember teamMember) {
return Objects.nonNull(teamMember) && TeamMemberRole.OWN.eq(teamMember.getRole());
}
private boolean checkBd(Long userId) {
return Boolean.TRUE.equals(ResponseAssert.requiredSuccess(bdTeamInfoClient.check(userId)));
}
private boolean checkBdLeader(Long userId) {
return Boolean.TRUE.equals(ResponseAssert.requiredSuccess(bdTeamLeaderClient.check(userId)));
}
private String getTeamMemberId(TeamMember teamMember) {
return Objects.isNull(teamMember) ? null : teamMember.getId();
}
private TeamMemberRole getTeamMemberRole(TeamMember teamMember) {
return Objects.isNull(teamMember) ? null : teamMember.getRole();
}
private Long getTeamId(TeamMember teamMember) {
return Objects.isNull(teamMember) ? null : teamMember.getTeamId();
}
@Override @Override
public Boolean hasPermission(PropsStoreTypeQryCmd cmd) { public Boolean hasPermission(PropsStoreTypeQryCmd cmd) {
List<String> authResources = administratorAuthService.listAuthResources(cmd.requiredReqUserId()); List<String> authResources = administratorAuthService.listAuthResources(cmd.requiredReqUserId());

View File

@ -10,6 +10,11 @@ public interface UserIdentityService {
*/ */
UserIdentityVO getUserIdentity(Long userId); UserIdentityVO getUserIdentity(Long userId);
/**
* 是否存在不允许自由修改国家的平台身份.
*/
boolean hasCountryLockedIdentity(Long userId);
/** /**
* 用户是否有指定道具类型的权限 * 用户是否有指定道具类型的权限
* @param cmd * @param cmd

View File

@ -44,6 +44,11 @@ public interface ActiveVoiceRoomService {
*/ */
void updateQuantityByRoomAccount(String roomAccount, Long quantity); void updateQuantityByRoomAccount(String roomAccount, Long quantity);
/**
* 修改用户在线房间国家.
*/
void updateUserCountry(Long userId, String countryCode, String countryName, String region);
/** /**
* 移除首页房间. * 移除首页房间.
* *

View File

@ -8,6 +8,7 @@ import com.red.circle.other.infra.database.mongo.dto.query.live.FamilyOnlineRoom
import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom;
import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService;
import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd; import com.red.circle.other.inner.model.cmd.live.AppActiveVoiceRoomQryCmd;
import com.red.circle.tool.core.date.TimestampUtils;
import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Aggregation;
import com.red.circle.tool.core.text.StringUtils; import com.red.circle.tool.core.text.StringUtils;
@ -64,6 +65,16 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService {
new Update().set("onlineQuantity", quantity), ActiveVoiceRoom.class); new Update().set("onlineQuantity", quantity), ActiveVoiceRoom.class);
} }
@Override
public void updateUserCountry(Long userId, String countryCode, String countryName, String region) {
mongoTemplate.updateMulti(Query.query(Criteria.where("userId").is(userId)),
new Update().set("updateTime", TimestampUtils.now())
.set("countryCode", StringUtils.toUpperCase(countryCode))
.set("countryName", countryName)
.set("region", region),
ActiveVoiceRoom.class);
}
@Override @Override
public void removeByIds(List<Long> ids) { public void removeByIds(List<Long> ids) {
mongoTemplate.remove(Query.query(Criteria.where("id").in(ids)), mongoTemplate.remove(Query.query(Criteria.where("id").in(ids)),

View File

@ -514,9 +514,9 @@ public class RoomProfileManagerServiceImpl implements RoomProfileManagerService
@Override @Override
public void updateUserCountry(Long userId, String countryCode, String countryName) { public void updateUserCountry(Long userId, String countryCode, String countryName) {
mongoTemplate.updateFirst(Query.query(Criteria.where("userId").is(userId)), mongoTemplate.updateMulti(Query.query(Criteria.where("userId").is(userId)),
new Update().set("updateTime", TimestampUtils.now()) new Update().set("updateTime", TimestampUtils.now())
.set("countryCode", countryCode) .set("countryCode", StringUtils.toUpperCase(countryCode))
.set("countryName", countryName), .set("countryName", countryName),
RoomProfileManager.class); RoomProfileManager.class);
} }