Browse Source

对接拼团数据1

master
wangfukang 2 months ago
parent
commit
7316bbb745
  1. 16
      hiver-admin/test-output/test-report.html
  2. 7
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/constant/IeRedisKey.java
  3. 3
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeMatchStartDTO.java
  4. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeProfileDTO.java
  5. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeStatusDTO.java
  6. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeMatchAttempt.java
  7. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeUserProfile.java
  8. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeUserStatus.java
  9. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRedisService.java
  10. 93
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeMatchServiceImpl.java
  11. 64
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRedisServiceImpl.java
  12. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeMatchVO.java
  13. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeUserProfileVO.java
  14. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java
  15. 27
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/controller/PlanetController.java
  16. 23
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetAdventureRewardRecordVo.java
  17. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetAdventureService.java
  18. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdventurePkServiceImpl.java
  19. 64
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdventureServiceImpl.java
  20. 21
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java
  21. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
  22. 17
      hiver-modules/hiver-mall/src/main/resources/db/ie_school_fields.sql

16
hiver-admin/test-output/test-report.html

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a>
</li>
<li class="m-r-10">
<a href="#"><span class="badge badge-primary">六月 09, 2026 18:01:00</span></a>
<a href="#"><span class="badge badge-primary">六月 10, 2026 17:44:25</span></a>
</li>
</ul>
</div>
@ -84,7 +84,7 @@
<div class="test-detail">
<span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p>
<p class="text-sm"><span>18:01:01 下午</span> / <span>0.021 secs</span></p>
<p class="text-sm"><span>17:44:25 下午</span> / <span>0.018 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,9 +92,9 @@
<div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>06.09.2026 18:01:01</span>
<span class='badge badge-danger'>06.09.2026 18:01:01</span>
<span class='badge badge-default'>0.021 secs</span>
<span class='badge badge-success'>06.10.2026 17:44:25</span>
<span class='badge badge-danger'>06.10.2026 17:44:25</span>
<span class='badge badge-default'>0.018 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
</div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>18:01:01</td>
<td>17:44:25</td>
<td>
Test passed
</td>
@ -128,13 +128,13 @@
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Started</p>
<h3>六月 09, 2026 18:01:00</h3>
<h3>六月 10, 2026 17:44:25</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>六月 09, 2026 18:01:01</h3>
<h3>六月 10, 2026 17:44:25</h3>
</div></div>
</div>
<div class="col-md-3">

7
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/constant/IeRedisKey.java

@ -16,6 +16,9 @@ public final class IeRedisKey {
/** 匹配池:ZSET, key=ie:match:pool:{mode}:{mood}, member=userId, score=matchScore */
public static final String MATCH_POOL = PREFIX + "match:pool:";
/** 校区匹配池:ZSET, key=ie:match:pool:region:{regionId}:{mode}:{mood}, member=userId */
public static final String REGION_MATCH_POOL = PREFIX + "match:pool:region:";
/** 用户所在匹配池索引:STRING, key=ie:match:user-pool:{userId}, value=poolKey */
public static final String USER_MATCH_POOL = PREFIX + "match:user-pool:";
@ -61,4 +64,8 @@ public final class IeRedisKey {
public static String matchPool(String mode, String mood) {
return MATCH_POOL + mode + ":" + mood;
}
public static String regionMatchPool(String regionId, String mode, String mood) {
return REGION_MATCH_POOL + regionId + ":" + mode + ":" + mood;
}
}

3
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeMatchStartDTO.java

@ -9,8 +9,11 @@ public class IeMatchStartDTO {
private String mode;
private String targetMode;
private String targetGender;
private String matchScope;
private String mood;
private Double longitude;
private Double latitude;
private String regionId;
private String regionName;
private List<String> interestTags;
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeProfileDTO.java

@ -16,4 +16,6 @@ public class IeProfileDTO {
private String currentMode;
private String targetModePreference;
private String targetGenderPreference;
private String regionId;
private String regionName;
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeStatusDTO.java

@ -11,5 +11,7 @@ public class IeStatusDTO {
private String statusText;
private Double longitude;
private Double latitude;
private String regionId;
private String regionName;
private List<String> interestTags;
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeMatchAttempt.java

@ -16,6 +16,8 @@ public class IeMatchAttempt extends IeBaseEntity {
private Integer status;
private String anonymousName;
private String avatarText;
private String targetRegionId;
private String targetRegionName;
private String stateText;
private String quoteText;
private String failReason;

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeUserProfile.java

@ -22,6 +22,8 @@ public class IeUserProfile extends IeBaseEntity {
private String recentPreference;
private String targetModePreference;
private String targetGenderPreference;
private String regionId;
private String regionName;
private Integer defaultRoomMinutes;
private Integer dailyQuota;
private Integer usedQuota;

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeUserStatus.java

@ -14,6 +14,8 @@ public class IeUserStatus extends IeBaseEntity {
private String mode;
private String mood;
private String statusText;
private String regionId;
private String regionName;
private Integer onlineStatus;
private Date lastActiveTime;
}

4
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRedisService.java

@ -17,7 +17,7 @@ public interface IeRedisService {
void unlockMatchUser(Long userId);
List<Long> candidates(String mode, String mood, int limit);
List<Long> candidates(String mode, String mood, String regionId, boolean regionScope, int limit);
boolean isRecentlyMatched(Long userId, Long targetUserId);
@ -51,7 +51,7 @@ public interface IeRedisService {
long onlineCount();
long waitingCount(String mode, String mood);
long waitingCount(String mode, String mood, String regionId, boolean regionScope);
List<String> hotStatuses(int limit);
}

93
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeMatchServiceImpl.java

@ -62,7 +62,7 @@ public class IeMatchServiceImpl implements IeMatchService {
redisService.refreshOnline(userId);
IeHomeVO vo = new IeHomeVO();
vo.setOnlineCount(redisService.onlineCount());
vo.setWaitingCount(redisService.waitingCount(profile.getCurrentMode(), "quiet"));
vo.setWaitingCount(redisService.waitingCount(profile.getCurrentMode(), "quiet", profile.getRegionId(), hasText(profile.getRegionId())));
vo.setDailyQuota(profile.getDailyQuota());
vo.setUsedQuota((int) redisService.todayUsedQuota(userId));
vo.setUnreadCount(totalUnreadCount(userId));
@ -139,6 +139,8 @@ public class IeMatchServiceImpl implements IeMatchService {
profile.setRecentPreference(profile.getCurrentMode());
profile.setTargetModePreference(normalizeTargetMode(dto.getTargetModePreference()));
profile.setTargetGenderPreference(normalizeTargetGender(dto.getTargetGenderPreference()));
profile.setRegionId(defaultText(dto.getRegionId(), profile.getRegionId()));
profile.setRegionName(defaultText(dto.getRegionName(), profile.getRegionName()));
profile.setProfileCompleted(1);
profile.setLastActiveTime(new Date());
profile.setUpdateTime(new Date());
@ -147,6 +149,8 @@ public class IeMatchServiceImpl implements IeMatchService {
statusDTO.setMode(profile.getCurrentMode());
statusDTO.setMood("quiet");
statusDTO.setStatusText(defaultText(profile.getIntro(), profile.getCurrentMode()));
statusDTO.setRegionId(profile.getRegionId());
statusDTO.setRegionName(profile.getRegionName());
statusDTO.setInterestTags(jsonToTags(profile.getInterestTags()));
updateStatus(userId, statusDTO);
return toProfileVO(profile);
@ -163,7 +167,9 @@ public class IeMatchServiceImpl implements IeMatchService {
if (dto.getMood() == null) {
dto.setMood("quiet");
}
ensureProfile(userId);
IeUserProfile profile = ensureProfile(userId);
dto.setRegionId(defaultText(dto.getRegionId(), profile.getRegionId()));
dto.setRegionName(defaultText(dto.getRegionName(), profile.getRegionName()));
IeUserStatus status = userStatusMapper.selectOne(new LambdaQueryWrapper<IeUserStatus>()
.eq(IeUserStatus::getUserId, userId)
.last("limit 1"));
@ -176,6 +182,8 @@ public class IeMatchServiceImpl implements IeMatchService {
status.setMode(dto.getMode());
status.setMood(dto.getMood());
status.setStatusText(dto.getStatusText());
status.setRegionId(dto.getRegionId());
status.setRegionName(dto.getRegionName());
status.setOnlineStatus(1);
status.setLastActiveTime(now);
status.setUpdateTime(now);
@ -205,14 +213,23 @@ public class IeMatchServiceImpl implements IeMatchService {
}
IeStatusDTO statusDTO = new IeStatusDTO();
BeanUtils.copyProperties(dto == null ? new IeMatchStartDTO() : dto, statusDTO);
updateStatus(userId, statusDTO);
String mode = normalizeMode(statusDTO.getMode(), profile.getCurrentMode());
String targetMode = normalizeTargetMode(dto == null ? null : dto.getTargetMode());
String requestedTargetGender = dto == null || dto.getTargetGender() == null ? profile.getTargetGenderPreference() : dto.getTargetGender();
String targetGender = normalizeTargetGender(requestedTargetGender);
String matchScope = normalizeMatchScope(dto == null ? null : dto.getMatchScope());
String regionId = defaultText(dto == null ? null : dto.getRegionId(), profile.getRegionId());
String regionName = defaultText(dto == null ? null : dto.getRegionName(), profile.getRegionName());
boolean regionScope = "school".equals(matchScope);
if (regionScope && !hasText(regionId)) {
return fail(userId, dto, "请先选择校区后再匹配本校区用户");
}
statusDTO.setRegionId(regionId);
statusDTO.setRegionName(regionName);
updateStatus(userId, statusDTO);
String mood = statusDTO.getMood() == null ? "quiet" : statusDTO.getMood();
List<Long> candidates = candidateUsers(targetMode, mode, mood);
List<Long> candidates = candidateUsers(targetMode, mode, mood, regionId, regionScope);
List<MatchCandidate> matchedCandidates = new ArrayList<>();
for (Long candidateUserId : candidates) {
if (candidateUserId == null || candidateUserId.equals(userId)) {
@ -225,7 +242,7 @@ public class IeMatchServiceImpl implements IeMatchService {
continue;
}
IeUserProfile candidateProfile = findProfileByUserId(candidateUserId);
if (!matchCandidate(candidateProfile, targetMode, targetGender)) {
if (!matchCandidate(candidateProfile, targetMode, targetGender, regionId, regionScope)) {
continue;
}
matchedCandidates.add(new MatchCandidate(candidateUserId, matchScore(statusDTO, profile, candidateProfile)));
@ -256,6 +273,8 @@ public class IeMatchServiceImpl implements IeMatchService {
match.setStatus(3);
match.setAnonymousName(defaultText(targetProfile.getAnonymousName(), "半匿名漂流者"));
match.setAvatarText(defaultText(targetProfile.getAvatarText(), "◌"));
match.setTargetRegionId(targetProfile.getRegionId());
match.setTargetRegionName(targetProfile.getRegionName());
match.setStateText(targetProfile.getCurrentMode() == null || "i".equals(targetProfile.getCurrentMode()) ? "偏安静,适合慢慢靠近" : "偏轻松,愿意先开场");
match.setQuoteText(defaultText(targetProfile.getIntro(), "可以先安静待一会,不用急着找话题。"));
match.setCreateTime(new Date());
@ -274,6 +293,8 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setRoomNo(room.getRoomNo());
vo.setAvatarUrl(targetProfile.getAvatarUrl());
vo.setGender(targetProfile.getGender());
vo.setRegionId(targetProfile.getRegionId());
vo.setRegionName(targetProfile.getRegionName());
vo.setIntro(targetProfile.getIntro());
vo.setInterestTags(jsonToTags(targetProfile.getInterestTags()));
vo.setPersonaImages(jsonToTags(targetProfile.getPersonaImages()));
@ -349,6 +370,8 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setAnonymousName(defaultText(profile.getAnonymousName(), vo.getAnonymousName()));
vo.setAvatarText(defaultText(profile.getAvatarText(), vo.getAvatarText()));
vo.setAvatarUrl(profile.getAvatarUrl());
vo.setRegionId(defaultText(profile.getRegionId(), vo.getRegionId()));
vo.setRegionName(defaultText(profile.getRegionName(), vo.getRegionName()));
vo.setMode(defaultText(profile.getCurrentMode(), vo.getMode()));
vo.setQuoteText(defaultText(profile.getIntro(), vo.getQuoteText()));
}
@ -419,6 +442,8 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setTargetUserId(match.getTargetUserId());
vo.setAnonymousName(match.getAnonymousName());
vo.setAvatarText(match.getAvatarText());
vo.setRegionId(match.getTargetRegionId());
vo.setRegionName(match.getTargetRegionName());
vo.setMode(match.getMode());
vo.setMood(match.getMood());
vo.setStateText(match.getStateText());
@ -469,22 +494,22 @@ public class IeMatchServiceImpl implements IeMatchService {
.last("limit 1"));
}
private List<Long> candidateUsers(String targetMode, String selfMode, String mood) {
private List<Long> candidateUsers(String targetMode, String selfMode, String mood, String regionId, boolean regionScope) {
LinkedHashSet<Long> set = new LinkedHashSet<>();
if (!"any".equals(targetMode)) {
set.addAll(redisService.candidates(targetMode, mood, 50));
set.addAll(statusCandidates(targetMode, 50));
set.addAll(profileCandidates(targetMode, 50));
set.addAll(redisService.candidates(targetMode, mood, regionId, regionScope, 50));
set.addAll(statusCandidates(targetMode, regionId, regionScope, 50));
set.addAll(profileCandidates(targetMode, regionId, regionScope, 50));
return new ArrayList<>(set);
}
set.addAll(redisService.candidates(selfMode, mood, 30));
set.addAll(redisService.candidates("i".equals(selfMode) ? "e" : "i", mood, 30));
set.addAll(redisService.candidates("i", mood, 20));
set.addAll(redisService.candidates("e", mood, 20));
set.addAll(statusCandidates(selfMode, 30));
set.addAll(statusCandidates("i".equals(selfMode) ? "e" : "i", 30));
set.addAll(profileCandidates(selfMode, 30));
set.addAll(profileCandidates("i".equals(selfMode) ? "e" : "i", 30));
set.addAll(redisService.candidates(selfMode, mood, regionId, regionScope, 30));
set.addAll(redisService.candidates("i".equals(selfMode) ? "e" : "i", mood, regionId, regionScope, 30));
set.addAll(redisService.candidates("i", mood, regionId, regionScope, 20));
set.addAll(redisService.candidates("e", mood, regionId, regionScope, 20));
set.addAll(statusCandidates(selfMode, regionId, regionScope, 30));
set.addAll(statusCandidates("i".equals(selfMode) ? "e" : "i", regionId, regionScope, 30));
set.addAll(profileCandidates(selfMode, regionId, regionScope, 30));
set.addAll(profileCandidates("i".equals(selfMode) ? "e" : "i", regionId, regionScope, 30));
return new ArrayList<>(set);
}
@ -499,12 +524,14 @@ public class IeMatchServiceImpl implements IeMatchService {
return count != null && count > 0;
}
private List<Long> statusCandidates(String mode, int limit) {
List<IeUserStatus> statuses = userStatusMapper.selectList(new LambdaQueryWrapper<IeUserStatus>()
private List<Long> statusCandidates(String mode, String regionId, boolean regionScope, int limit) {
LambdaQueryWrapper<IeUserStatus> query = new LambdaQueryWrapper<IeUserStatus>()
.eq(IeUserStatus::getOnlineStatus, 1)
.eq(IeUserStatus::getMode, normalizeMode(mode, "i"))
.eq(regionScope, IeUserStatus::getRegionId, regionId)
.orderByDesc(IeUserStatus::getLastActiveTime)
.last("limit " + Math.max(1, Math.min(limit, 100))));
.last("limit " + Math.max(1, Math.min(limit, 100)));
List<IeUserStatus> statuses = userStatusMapper.selectList(query);
List<Long> users = new ArrayList<>();
for (IeUserStatus status : statuses) {
if (status.getUserId() != null) {
@ -514,13 +541,15 @@ public class IeMatchServiceImpl implements IeMatchService {
return users;
}
private List<Long> profileCandidates(String mode, int limit) {
List<IeUserProfile> profiles = userProfileMapper.selectList(new LambdaQueryWrapper<IeUserProfile>()
private List<Long> profileCandidates(String mode, String regionId, boolean regionScope, int limit) {
LambdaQueryWrapper<IeUserProfile> query = new LambdaQueryWrapper<IeUserProfile>()
.eq(IeUserProfile::getProfileCompleted, 1)
.eq(IeUserProfile::getCurrentMode, normalizeMode(mode, "i"))
.eq(IeUserProfile::getIsDeleted, 0)
.eq(regionScope, IeUserProfile::getRegionId, regionId)
.orderByDesc(IeUserProfile::getLastActiveTime)
.last("limit " + Math.max(1, Math.min(limit, 100))));
.last("limit " + Math.max(1, Math.min(limit, 100)));
List<IeUserProfile> profiles = userProfileMapper.selectList(query);
List<Long> users = new ArrayList<>();
for (IeUserProfile profile : profiles) {
if (profile.getUserId() != null) {
@ -566,6 +595,8 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setRecentPreference(profile.getRecentPreference());
vo.setTargetModePreference(profile.getTargetModePreference());
vo.setTargetGenderPreference(profile.getTargetGenderPreference());
vo.setRegionId(profile.getRegionId());
vo.setRegionName(profile.getRegionName());
vo.setDefaultRoomMinutes(profile.getDefaultRoomMinutes());
vo.setDailyQuota(profile.getDailyQuota());
vo.setUsedQuota(profile.getUsedQuota());
@ -612,10 +643,24 @@ public class IeMatchServiceImpl implements IeMatchService {
return "any";
}
private boolean matchCandidate(IeUserProfile candidateProfile, String selfTargetMode, String selfTargetGender) {
private String normalizeMatchScope(String value) {
if ("world".equalsIgnoreCase(value)) {
return "world";
}
return "school";
}
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
private boolean matchCandidate(IeUserProfile candidateProfile, String selfTargetMode, String selfTargetGender, String regionId, boolean regionScope) {
if (candidateProfile == null) {
return false;
}
if (regionScope && !Objects.equals(regionId, candidateProfile.getRegionId())) {
return false;
}
String candidateMode = normalizeMode(candidateProfile.getCurrentMode(), "i");
if (!"any".equals(selfTargetMode) && !selfTargetMode.equals(candidateMode)) {
return false;

64
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRedisServiceImpl.java

@ -47,6 +47,8 @@ public class IeRedisServiceImpl implements IeRedisService {
stringRedisTemplate.opsForHash().put(key, "statusText", defaultText(dto.getStatusText(), ""));
stringRedisTemplate.opsForHash().put(key, "longitude", dto.getLongitude() == null ? "" : String.valueOf(dto.getLongitude()));
stringRedisTemplate.opsForHash().put(key, "latitude", dto.getLatitude() == null ? "" : String.valueOf(dto.getLatitude()));
stringRedisTemplate.opsForHash().put(key, "regionId", defaultText(dto.getRegionId(), ""));
stringRedisTemplate.opsForHash().put(key, "regionName", defaultText(dto.getRegionName(), ""));
stringRedisTemplate.opsForHash().put(key, "interestTags", dto.getInterestTags() == null ? "[]" : JSONUtil.toJsonStr(dto.getInterestTags()));
stringRedisTemplate.opsForHash().put(key, "lastActiveTime", DateUtil.now());
stringRedisTemplate.expire(key, STATUS_SECONDS, TimeUnit.SECONDS);
@ -60,7 +62,12 @@ public class IeRedisServiceImpl implements IeRedisService {
removeFromMatchPools(userId);
String mode = defaultText(dto.getMode(), "i");
String mood = defaultText(dto.getMood(), "quiet");
String poolKey = IeRedisKey.matchPool(mode, mood);
List<String> poolKeys = new ArrayList<>();
poolKeys.add(IeRedisKey.matchPool(mode, mood));
String regionId = defaultText(dto.getRegionId(), "");
if (!regionId.isEmpty()) {
poolKeys.add(IeRedisKey.regionMatchPool(regionId, mode, mood));
}
double score = System.currentTimeMillis();
if ("i".equals(mode)) {
score += 5000;
@ -68,16 +75,20 @@ public class IeRedisServiceImpl implements IeRedisService {
if (dto.getInterestTags() != null) {
score += dto.getInterestTags().size() * 100;
}
stringRedisTemplate.opsForZSet().add(poolKey, String.valueOf(userId), score);
stringRedisTemplate.expire(poolKey, STATUS_SECONDS, TimeUnit.SECONDS);
stringRedisTemplate.opsForValue().set(IeRedisKey.USER_MATCH_POOL + userId, poolKey, STATUS_SECONDS, TimeUnit.SECONDS);
for (String poolKey : poolKeys) {
stringRedisTemplate.opsForZSet().add(poolKey, String.valueOf(userId), score);
stringRedisTemplate.expire(poolKey, STATUS_SECONDS, TimeUnit.SECONDS);
}
stringRedisTemplate.opsForValue().set(IeRedisKey.USER_MATCH_POOL + userId, JSONUtil.toJsonStr(poolKeys), STATUS_SECONDS, TimeUnit.SECONDS);
}
@Override
public void removeFromMatchPools(Long userId) {
String poolKey = stringRedisTemplate.opsForValue().get(IeRedisKey.USER_MATCH_POOL + userId);
if (poolKey != null && poolKey.length() > 0) {
stringRedisTemplate.opsForZSet().remove(poolKey, String.valueOf(userId));
String poolKeysJson = stringRedisTemplate.opsForValue().get(IeRedisKey.USER_MATCH_POOL + userId);
if (poolKeysJson != null && poolKeysJson.length() > 0) {
for (String poolKey : parsePoolKeys(poolKeysJson)) {
stringRedisTemplate.opsForZSet().remove(poolKey, String.valueOf(userId));
}
}
stringRedisTemplate.delete(IeRedisKey.USER_MATCH_POOL + userId);
}
@ -94,17 +105,18 @@ public class IeRedisServiceImpl implements IeRedisService {
}
@Override
public List<Long> candidates(String mode, String mood, int limit) {
public List<Long> candidates(String mode, String mood, String regionId, boolean regionScope, int limit) {
List<String> keys = new ArrayList<>();
String targetMode = defaultText(mode, "i");
String targetMood = defaultText(mood, "quiet");
keys.add(IeRedisKey.matchPool(targetMode, targetMood));
String safeRegionId = defaultText(regionId, "");
keys.add(matchPoolKey(targetMode, targetMood, safeRegionId, regionScope));
if (!"quiet".equals(targetMood)) {
keys.add(IeRedisKey.matchPool(targetMode, "quiet"));
keys.add(matchPoolKey(targetMode, "quiet", safeRegionId, regionScope));
}
addPoolKey(keys, targetMode, "talk");
addPoolKey(keys, targetMode, "listen");
addPoolKey(keys, targetMode, "drift");
addPoolKey(keys, targetMode, "talk", safeRegionId, regionScope);
addPoolKey(keys, targetMode, "listen", safeRegionId, regionScope);
addPoolKey(keys, targetMode, "drift", safeRegionId, regionScope);
List<Long> result = new ArrayList<>();
for (String key : keys) {
Set<String> members = stringRedisTemplate.opsForZSet().reverseRange(key, 0, limit - 1L);
@ -121,13 +133,20 @@ public class IeRedisServiceImpl implements IeRedisService {
return result;
}
private void addPoolKey(List<String> keys, String mode, String mood) {
String key = IeRedisKey.matchPool(mode, mood);
private void addPoolKey(List<String> keys, String mode, String mood, String regionId, boolean regionScope) {
String key = matchPoolKey(mode, mood, regionId, regionScope);
if (!keys.contains(key)) {
keys.add(key);
}
}
private String matchPoolKey(String mode, String mood, String regionId, boolean regionScope) {
if (regionScope && regionId != null && !regionId.isEmpty()) {
return IeRedisKey.regionMatchPool(regionId, mode, mood);
}
return IeRedisKey.matchPool(mode, mood);
}
@Override
public boolean isRecentlyMatched(Long userId, Long targetUserId) {
Boolean result = stringRedisTemplate.opsForSet().isMember(IeRedisKey.RECENT_MATCH + userId, String.valueOf(targetUserId));
@ -257,8 +276,9 @@ public class IeRedisServiceImpl implements IeRedisService {
}
@Override
public long waitingCount(String mode, String mood) {
Long count = stringRedisTemplate.opsForZSet().zCard(IeRedisKey.matchPool(defaultText(mode, "i"), defaultText(mood, "quiet")));
public long waitingCount(String mode, String mood, String regionId, boolean regionScope) {
String key = matchPoolKey(defaultText(mode, "i"), defaultText(mood, "quiet"), defaultText(regionId, ""), regionScope);
Long count = stringRedisTemplate.opsForZSet().zCard(key);
return count == null ? 0L : count;
}
@ -276,6 +296,16 @@ public class IeRedisServiceImpl implements IeRedisService {
return IeRedisKey.DAILY_QUOTA + date + ":" + userId;
}
private List<String> parsePoolKeys(String value) {
List<String> keys = new ArrayList<>();
try {
keys.addAll(JSONUtil.toList(JSONUtil.parseArray(value), String.class));
} catch (Exception ignored) {
keys.add(value);
}
return keys;
}
private String defaultText(String value, String defaultValue) {
return value == null || value.trim().isEmpty() ? defaultValue : value.trim();
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeMatchVO.java

@ -14,6 +14,8 @@ public class IeMatchVO {
private String avatarText;
private String avatarUrl;
private String gender;
private String regionId;
private String regionName;
private String intro;
private List<String> interestTags;
private List<String> personaImages;

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeUserProfileVO.java

@ -18,6 +18,8 @@ public class IeUserProfileVO {
private String recentPreference;
private String targetModePreference;
private String targetGenderPreference;
private String regionId;
private String regionName;
private Integer defaultRoomMinutes;
private Integer dailyQuota;
private Integer usedQuota;

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java

@ -243,7 +243,7 @@ public class OrderAsyncConsumer {
return;
}
try {
ieMatchService.rewardMatchQuota(Long.valueOf(userId), 1);
ieMatchService.rewardMatchQuota(Long.valueOf(userId), 3);
} catch (NumberFormatException e) {
log.warn("配送完成异步增加i/e匹配机会失败,用户ID不是数字 userId={}", userId);
} catch (Exception e) {

27
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/controller/PlanetController.java

@ -4,6 +4,7 @@ import cc.hiver.core.common.exception.HiverException;
import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.planet.entity.PlanetDrawRecord;
import cc.hiver.mall.planet.entity.PlanetTicket;
import cc.hiver.mall.planet.pojo.*;
import cc.hiver.mall.planet.service.*;
import io.swagger.annotations.Api;
@ -335,6 +336,23 @@ public class PlanetController {
ticketService.pageLog(query.getUserId(), query.getRegionId(), query.getPageNumber(), query.getPageSize()));
}
@ApiOperation(value = "我的实时星球券数量")
@RequestMapping(value = "/ticket/count", method = RequestMethod.POST)
public Result ticketCount(@RequestBody PlanetQuery query) {
if (StringUtils.isEmpty(query.getUserId())) {
return ResultUtil.error("用户id不能为空");
}
if (StringUtils.isEmpty(query.getRegionId())) {
return ResultUtil.error(REGION_REQUIRED);
}
final PlanetTicket ticket = ticketService.getByUser(query.getUserId(), query.getRegionId());
final int ticketCount = ticket == null || ticket.getTicketCount() == null ? 0 : ticket.getTicketCount();
final Map<String, Object> data = new HashMap<>(2);
data.put("ticketCount", ticketCount);
data.put("myTicketCount", ticketCount);
return new ResultUtil<>().setData(data);
}
@ApiOperation(value = "经营玩法聚合数据")
@RequestMapping(value = "/operate/overview", method = RequestMethod.POST)
public Result operateOverview(@RequestBody PlanetQuery query) {
@ -585,6 +603,15 @@ public class PlanetController {
return new ResultUtil<>().setData(adventureService.rankTop(query.getRegionId()));
}
@ApiOperation(value = "学院助推赛昨日奖励记录")
@RequestMapping(value = "/adventure/yesterdayRewards", method = RequestMethod.POST)
public Result adventureYesterdayRewards(@RequestBody PlanetQuery query) {
if (StringUtils.isEmpty(query.getRegionId())) {
return ResultUtil.error(REGION_REQUIRED);
}
return new ResultUtil<>().setData(adventureService.yesterdayRewards(query.getRegionId()));
}
@ApiOperation(value = "学院助推赛今日最快荣耀榜")
@RequestMapping(value = "/adventure/glory", method = RequestMethod.POST)
public Result adventureGlory(@RequestBody PlanetQuery query) {

23
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetAdventureRewardRecordVo.java

@ -0,0 +1,23 @@
package cc.hiver.mall.planet.pojo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
@Data
@ApiModel(value = "白嫖星球-学院助推赛昨日奖励记录")
public class PlanetAdventureRewardRecordVo implements Serializable {
private static final long serialVersionUID = 1L;
private Integer rankNo;
private String userId;
private String nickname;
private String avatar;
private String college;
private LocalDate rewardDate;
private Integer durationSeconds;
private Integer score;
private Integer rewardTickets;
private String rewardContent;
}

4
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetAdventureService.java

@ -5,10 +5,12 @@ import cc.hiver.mall.planet.entity.PlanetAdventureEvent;
import cc.hiver.mall.planet.entity.PlanetAdventureRank;
import cc.hiver.mall.planet.entity.PlanetAdventureSession;
import cc.hiver.mall.planet.pojo.PlanetAdventureHomeVo;
import cc.hiver.mall.planet.pojo.PlanetAdventureRewardRecordVo;
import cc.hiver.mall.planet.pojo.PlanetAdventureStartVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;
public interface PlanetAdventureService {
@ -23,6 +25,8 @@ public interface PlanetAdventureService {
List<PlanetAdventureRank> rankTop(String regionId);
Map<String, List<PlanetAdventureRewardRecordVo>> yesterdayRewards(String regionId);
int settleDueRanks();
Page<PlanetAdventureEvent> pageEvents(PageVo pageVo, String regionId);

4
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdventurePkServiceImpl.java

@ -300,6 +300,10 @@ public class PlanetAdventurePkServiceImpl implements PlanetAdventurePkService {
private void settleIfReady(PlanetAdventurePkRoom room) {
List<PlanetAdventurePkPlayer> players = playerMapper.selectList(new LambdaQueryWrapper<PlanetAdventurePkPlayer>()
.eq(PlanetAdventurePkPlayer::getRoomId, room.getId()));
int maxPlayers = room.getMaxPlayers() == null ? 2 : room.getMaxPlayers();
if (maxPlayers > 2 && players.size() < maxPlayers) {
return;
}
for (PlanetAdventurePkPlayer p : players) {
if (!terminal(p)) return;
}

64
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdventureServiceImpl.java

@ -12,6 +12,7 @@ import cc.hiver.mall.planet.mapper.PlanetAdventureRankMapper;
import cc.hiver.mall.planet.mapper.PlanetAdventureSessionMapper;
import cc.hiver.mall.planet.mapper.PlanetCollegeConfigMapper;
import cc.hiver.mall.planet.pojo.PlanetAdventureHomeVo;
import cc.hiver.mall.planet.pojo.PlanetAdventureRewardRecordVo;
import cc.hiver.mall.planet.pojo.PlanetAdventureStartVo;
import cc.hiver.mall.planet.service.PlanetAdventureService;
import cc.hiver.mall.planet.service.PlanetTicketService;
@ -32,6 +33,7 @@ public class PlanetAdventureServiceImpl implements PlanetAdventureService {
private static final int GAME_SECONDS = 180;
private static final LocalTime SETTLE_TIME = LocalTime.of(21, 30);
private static final int CLEAR_BASE_PROGRESS = 100;
private static final int[] PERSONAL_REWARD_CASH = {5, 4, 3, 2, 1};
@Autowired
private PlanetAdventureSessionMapper sessionMapper;
@ -177,6 +179,15 @@ public class PlanetAdventureServiceImpl implements PlanetAdventureService {
.last("limit 20"));
}
@Override
public Map<String, List<PlanetAdventureRewardRecordVo>> yesterdayRewards(String regionId) {
LocalDate yesterday = LocalDate.now().minusDays(1);
Map<String, List<PlanetAdventureRewardRecordVo>> data = new HashMap<>(4);
data.put("personalRewardList", yesterdayPersonalRewards(regionId, yesterday));
data.put("collegeRewardList", yesterdayCollegeRewards(regionId, yesterday));
return data;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int settleDueRanks() {
@ -325,6 +336,59 @@ public class PlanetAdventureServiceImpl implements PlanetAdventureService {
return rank == null || rank.getRankNo() == null ? 0 : rank.getRankNo();
}
private List<PlanetAdventureRewardRecordVo> yesterdayPersonalRewards(String regionId, LocalDate date) {
List<PlanetAdventureSession> sessions = sessionMapper.selectList(new LambdaQueryWrapper<PlanetAdventureSession>()
.eq(PlanetAdventureSession::getRegionId, regionId)
.eq(PlanetAdventureSession::getPlayDate, date)
.eq(PlanetAdventureSession::getStatus, PlanetConstant.ADVENTURE_SESSION_SUBMITTED)
.eq(PlanetAdventureSession::getCheatFlag, "N")
.orderByAsc(PlanetAdventureSession::getDurationSeconds)
.last("limit 5"));
List<PlanetAdventureRewardRecordVo> records = new ArrayList<>();
for (int i = 0; i < sessions.size(); i++) {
PlanetAdventureSession s = sessions.get(i);
PlanetAdventureRewardRecordVo vo = new PlanetAdventureRewardRecordVo();
vo.setRankNo(i + 1);
vo.setUserId(s.getUserId());
vo.setNickname(s.getNickname());
vo.setAvatar(s.getAvatar());
vo.setCollege(s.getCollege());
vo.setRewardDate(date);
vo.setDurationSeconds(nz(s.getDurationSeconds()));
vo.setScore(nz(s.getScore()));
vo.setRewardContent("现金奖励" + PERSONAL_REWARD_CASH[i] + "元");
records.add(vo);
}
return records;
}
private List<PlanetAdventureRewardRecordVo> yesterdayCollegeRewards(String regionId, LocalDate date) {
List<PlanetAdventureRank> ranks = rankMapper.selectList(new LambdaQueryWrapper<PlanetAdventureRank>()
.eq(PlanetAdventureRank::getRegionId, regionId)
.eq(PlanetAdventureRank::getRankDate, date)
.orderByDesc(PlanetAdventureRank::getScore)
.orderByAsc(PlanetAdventureRank::getUpdateTime)
.last("limit 3"));
List<PlanetAdventureRewardRecordVo> records = new ArrayList<>();
for (int i = 0; i < ranks.size(); i++) {
PlanetAdventureRank r = ranks.get(i);
int rankNo = r.getRankNo() == null || r.getRankNo() <= 0 ? i + 1 : r.getRankNo();
int rewardTickets = Math.max(0, 4 - rankNo);
PlanetAdventureRewardRecordVo vo = new PlanetAdventureRewardRecordVo();
vo.setRankNo(rankNo);
vo.setUserId(r.getUserId());
vo.setNickname(r.getNickname());
vo.setAvatar(r.getAvatar());
vo.setCollege(r.getCollege());
vo.setRewardDate(date);
vo.setScore(nz(r.getScore()));
vo.setRewardTickets(rewardTickets);
vo.setRewardContent("学院成员每人" + rewardTickets + "张星球券");
records.add(vo);
}
return records;
}
private String validateClear(int n, int g, int d, int r, Integer clientScore, int seconds) {
if (seconds < 20 || seconds > GAME_SECONDS + 20) return "闯关时长异常";
if (n < 36) return "消除数量不足";

21
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java

@ -272,22 +272,39 @@ public class PlanetDrawServiceImpl implements PlanetDrawService {
final Set<String> cooldownUsers = recentWinnerUserIds(pool.getRegionId(), now);
boolean useCooldown = hasCandidate(poolCandidates, cooldownUsers);
int winnerCount = 0;
final List<PlanetReward> missedRewards = new ArrayList<>();
for (PlanetReward reward : rewards) {
final int quota = reward.getQuota() == null ? 0 : reward.getQuota();
for (int i = 0; i < quota; i++) {
List<Candidate> usable = usableCandidates(poolCandidates, useCooldown ? cooldownUsers : Collections.emptySet());
if (usable.isEmpty()) {
break;
missedRewards.add(reward);
continue;
}
final Candidate winner = pickWeighted(usable);
if (winner == null) {
break;
missedRewards.add(reward);
continue;
}
winner.win = true;
saveWinner(record, reward, winner.ticket, now);
winnerCount++;
}
}
// 第一轮遵循原有冷却规则;若仍有奖项未开出,则从未中奖参与者中补抽,直到奖项开完或参与者都中奖。
for (PlanetReward reward : missedRewards) {
final List<Candidate> usable = usableCandidates(poolCandidates, Collections.emptySet());
if (usable.isEmpty()) {
break;
}
final Candidate winner = pickWeighted(usable);
if (winner == null) {
break;
}
winner.win = true;
saveWinner(record, reward, winner.ticket, now);
winnerCount++;
}
record.setWinnerCount(winnerCount);
drawRecordMapper.insert(record);

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java

@ -1208,7 +1208,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
return;
}
try {
ieMatchService.rewardMatchQuota(Long.valueOf(userId), 1);
ieMatchService.rewardMatchQuota(Long.valueOf(userId), 3);
} catch (NumberFormatException e) {
log.warn("订单完成增加i/e匹配机会失败,用户ID不是数字 userId={}", userId);
} catch (Exception e) {

17
hiver-modules/hiver-mall/src/main/resources/db/ie_school_fields.sql

@ -0,0 +1,17 @@
ALTER TABLE ie_user_profile
ADD COLUMN region_id VARCHAR(64) NULL COMMENT '校区/学校ID',
ADD COLUMN region_name VARCHAR(100) NULL COMMENT '校区/学校名称';
ALTER TABLE ie_user_status
ADD COLUMN region_id VARCHAR(64) NULL COMMENT '校区/学校ID',
ADD COLUMN region_name VARCHAR(100) NULL COMMENT '校区/学校名称';
ALTER TABLE ie_match_attempt
ADD COLUMN target_region_id VARCHAR(64) NULL COMMENT '匹配对象校区/学校ID',
ADD COLUMN target_region_name VARCHAR(100) NULL COMMENT '匹配对象校区/学校名称';
ALTER TABLE ie_user_profile
ADD INDEX idx_ie_profile_region_mode (region_id, profile_completed, current_mode, is_deleted);
ALTER TABLE ie_user_status
ADD INDEX idx_ie_status_region_mode (region_id, online_status, mode, last_active_time);
Loading…
Cancel
Save