Browse Source

对接拼团数据1

master
wangfukang 1 month ago
parent
commit
38426e20c1
  1. 16
      hiver-admin/test-output/test-report.html
  2. 14
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
  3. 12
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java
  4. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeMatchStartDTO.java
  5. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/dto/IeProfileDTO.java
  6. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/entity/IeUserProfile.java
  7. 5
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeMatchService.java
  8. 26
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeChatServiceImpl.java
  9. 179
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeMatchServiceImpl.java
  10. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeHomeVO.java
  11. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeMatchVO.java
  12. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeUserProfileVO.java
  13. 8
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java
  14. 35
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetServiceImpl.java
  15. 6
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTaskServiceImpl.java
  16. 6
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java
  17. 20
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/HomeReminderService.java
  18. 138
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/HomeReminderServiceImpl.java
  19. 5
      hiver-modules/hiver-mall/src/main/resources/db/ie_user_profile_companion_intent.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">六月 13, 2026 16:59:20</span></a>
<a href="#"><span class="badge badge-primary">六月 14, 2026 18:24:58</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>16:59:20 下午</span> / <span>0.022 secs</span></p>
<p class="text-sm"><span>18:24:59 下午</span> / <span>0.034 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.13.2026 16:59:20</span>
<span class='badge badge-danger'>06.13.2026 16:59:20</span>
<span class='badge badge-default'>0.022 secs</span>
<span class='badge badge-success'>06.14.2026 18:24:59</span>
<span class='badge badge-danger'>06.14.2026 18:24:59</span>
<span class='badge badge-default'>0.034 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>16:59:20</td>
<td>18:24:59</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>六月 13, 2026 16:59:20</h3>
<h3>六月 14, 2026 18:24:58</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>六月 13, 2026 16:59:20</h3>
<h3>六月 14, 2026 18:24:59</h3>
</div></div>
</div>
<div class="col-md-3">

14
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java

@ -15,6 +15,7 @@ import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery;
import cc.hiver.mall.pojo.vo.WorkerRedisVo;
import cc.hiver.mall.pojo.vo.WorkerRewardVO;
import cc.hiver.mall.quartz.WorkerRewardTask;
import cc.hiver.mall.service.HomeReminderService;
import cc.hiver.mall.service.mybatis.DealingsRecordService;
import cc.hiver.mall.service.mybatis.MallAdPositionService;
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService;
@ -76,6 +77,9 @@ public class MallDeliveryOrderController {
@Autowired
private WaitOrderCacheUtil waitOrderCacheUtil;
@Autowired
private HomeReminderService homeReminderService;
/**
* 分页查询配送单
* hallOnly=true 时查询抢单大厅未被接单的单
@ -143,9 +147,19 @@ public class MallDeliveryOrderController {
}
}
}
cacheResult.put("homeReminders", homeReminderService.homeReminders(currentUserId(), q.getRegionId()));
return new ResultUtil<Object>().setData(cacheResult);
}
private String currentUserId() {
try {
User user = securityUtil.getCurrUser();
return user == null ? null : user.getId();
} catch (Exception ignored) {
return null;
}
}
@PostMapping("/countOrderByStatus")
@ApiOperation(value = "查询当前配送员指派单和已接单数量")
public Result<List<Map<String, Object>>> countOrderByStatus(@RequestParam(value = "workerId") String workerId,

12
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java

@ -98,6 +98,18 @@ public class IeController {
return new ResultUtil<IeMatchVO>().setData(matchService.startMatch(currentUserId(), dto));
}
@RequestMapping(value = "/profiles/{targetUserId}/match", method = RequestMethod.POST)
@ApiOperation("对指定用户发起陪伴")
public Result<IeMatchVO> matchProfile(@PathVariable Long targetUserId) {
return new ResultUtil<IeMatchVO>().setData(matchService.matchWithUser(currentUserId(), targetUserId));
}
@RequestMapping(value = "/companion-intents", method = RequestMethod.GET)
@ApiOperation("查询最新找搭子内容")
public Result<List<Map<String, Object>>> companionIntents(@RequestParam(required = false, defaultValue = "50") Integer limit) {
return new ResultUtil<List<Map<String, Object>>>().setData(matchService.latestCompanionIntents(currentUserId(), limit));
}
@RequestMapping(value = "/matches/page", method = RequestMethod.GET)
@ApiOperation("分页查询我的匹配记录")
public Result<IPage<IeMatchVO>> pageMatches(@RequestParam(required = false, defaultValue = "1") Integer pageNumber,

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

@ -12,6 +12,10 @@ public class IeMatchStartDTO {
private String matchScope;
/** 搭子意图:milktea奶茶/meal吃饭/study自习/walk散步/chat只想聊聊 */
private String intent;
/** 用户自由输入的找搭子内容 */
private String companionIntent;
/** 弹层模糊搜索内容 */
private String fuzzyKeyword;
private String mood;
private Double longitude;
private Double latitude;

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

@ -11,6 +11,7 @@ public class IeProfileDTO {
private String avatarUrl;
private String gender;
private String intro;
private String companionIntent;
private List<String> interestTags;
private List<String> personaImages;
private String currentMode;

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

@ -16,6 +16,7 @@ public class IeUserProfile extends IeBaseEntity {
private String avatarUrl;
private String gender;
private String intro;
private String companionIntent;
private String interestTags;
private String personaImages;
private String currentMode;

5
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeMatchService.java

@ -8,6 +8,9 @@ import cc.hiver.mall.ie.vo.IeMatchVO;
import cc.hiver.mall.ie.vo.IeUserProfileVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
import java.util.Map;
public interface IeMatchService {
IeHomeVO home(Long userId);
@ -25,6 +28,8 @@ public interface IeMatchService {
IeMatchVO matchWithUser(Long userId, Long targetUserId);
List<Map<String, Object>> latestCompanionIntents(Long currentUserId, Integer limit);
IPage<IeMatchVO> pageMatches(Long userId, Integer pageNumber, Integer pageSize);
void rewardMatchQuota(Long userId, Integer amount);

26
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeChatServiceImpl.java

@ -13,6 +13,7 @@ import cc.hiver.mall.ie.vo.IeAliyunModerationResult;
import cc.hiver.mall.ie.vo.IeAuditResult;
import cc.hiver.mall.ie.vo.IeChatEvent;
import cc.hiver.mall.ie.vo.IeMessageAckVO;
import cc.hiver.mall.service.HomeReminderService;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -76,6 +77,9 @@ public class IeChatServiceImpl implements IeChatService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private HomeReminderService homeReminderService;
private static final String STREAK_REWARD_KEY = "ie:streak:reward:";
private static final int STREAK_LOOKBACK_DAYS = 60;
private static final int STREAK_REWARD_INTERVAL = 7;
@ -173,6 +177,7 @@ public class IeChatServiceImpl implements IeChatService {
messagingTemplate.convertAndSendToUser(String.valueOf(senderId), "/queue/ie/ack", ack);
if (message.getIsBlocked() == 0 && !needMediaAudit) {
deliverMessage(message, ack);
homeReminderService.incrementIeUnread(receiverId);
} else if (message.getIsBlocked() == 0) {
afterCommitOrNow(() -> {
chatEventProducer.sendMediaAudit(message.getId());
@ -649,6 +654,7 @@ public class IeChatServiceImpl implements IeChatService {
}
IeMessageAckVO ack = buildAck(message, null);
deliverMessage(message, ack);
homeReminderService.incrementIeUnread(message.getReceiverId());
messagingTemplate.convertAndSendToUser(String.valueOf(message.getSenderId()), "/queue/ie/ack", ack);
}
@ -765,6 +771,26 @@ public class IeChatServiceImpl implements IeChatService {
record.setLastReadTime(new Date());
record.setUpdateTime(new Date());
recordMapper.updateById(record);
homeReminderService.setIeUnreadCount(userId, totalUnreadCount(userId));
}
private int totalUnreadCount(Long userId) {
List<IeRecord> records = recordMapper.selectList(new LambdaQueryWrapper<IeRecord>()
.eq(IeRecord::getUserId, userId));
int total = 0;
for (IeRecord record : records) {
if (record.getRoomId() == null || record.getTargetUserId() == null) {
continue;
}
Long count = roomMessageMapper.selectCount(new LambdaQueryWrapper<IeRoomMessage>()
.eq(IeRoomMessage::getRoomId, record.getRoomId())
.eq(IeRoomMessage::getSenderId, record.getTargetUserId())
.eq(IeRoomMessage::getReceiverId, userId)
.eq(IeRoomMessage::getIsBlocked, 0)
.gt(record.getLastReadTime() != null, IeRoomMessage::getCreateTime, record.getLastReadTime()));
total += count == null ? 0 : count.intValue();
}
return total;
}
/**

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

@ -11,6 +11,7 @@ import cc.hiver.mall.ie.service.IeMatchService;
import cc.hiver.mall.ie.service.IeRedisService;
import cc.hiver.mall.ie.service.IeSecurityAuditService;
import cc.hiver.mall.ie.vo.*;
import cc.hiver.mall.service.HomeReminderService;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -57,6 +58,9 @@ public class IeMatchServiceImpl implements IeMatchService {
@Autowired
private IeAliyunModerationService aliyunModerationService;
@Autowired
private HomeReminderService homeReminderService;
@Override
public IeHomeVO home(Long userId) {
IeUserProfile profile = ensureProfile(userId);
@ -70,7 +74,7 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setWaitingCount(redisService.waitingCount(profile.getCurrentMode(), currentMood, profile.getRegionId(), hasText(profile.getRegionId())));
vo.setDailyQuota(effectiveDailyQuota(profile));
vo.setUsedQuota((int) redisService.todayUsedQuota(userId));
vo.setUnreadCount(totalUnreadCount(userId));
vo.setUnreadCount(cachedUnreadCount(userId));
vo.setCurrentMode(profile.getCurrentMode());
vo.setCurrentMood(currentMood);
vo.setTargetModePreference(profile.getTargetModePreference());
@ -78,12 +82,18 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setProfileCompleted(profile.getProfileCompleted() == null ? 0 : profile.getProfileCompleted());
vo.setProfile(toProfileVO(profile));
vo.setHotStatuses(redisService.hotStatuses(8));
vo.setCompanionIntents(latestCompanionIntents(userId, 50));
return vo;
}
@Override
public int unreadCount(Long userId) {
return totalUnreadCount(userId);
return cachedUnreadCount(userId);
}
private int cachedUnreadCount(Long userId) {
Integer cached = homeReminderService.getIeUnreadCount(userId);
return cached == null ? 0 : cached;
}
private int totalUnreadCount(Long userId) {
@ -141,6 +151,7 @@ public class IeMatchServiceImpl implements IeMatchService {
// 昵称/一句话介绍单独审(取过滤后的文案),头像字、兴趣标签、地区名合并成一次审核(只判拦截)
String anonymousName = auditProfileText("profile-name", userId, defaultText(dto.getAnonymousName(), profile.getAnonymousName()));
String intro = auditProfileText("profile-intro", userId, defaultText(dto.getIntro(), ""));
String companionIntent = auditCompanionIntent(userId, defaultText(dto.getCompanionIntent(), profile.getCompanionIntent()));
String avatarText = defaultText(dto.getAvatarText(), "我");
StringBuilder extraText = new StringBuilder(avatarText);
if (dto.getInterestTags() != null) {
@ -162,6 +173,7 @@ public class IeMatchServiceImpl implements IeMatchService {
}
profile.setGender(normalizeGender(dto.getGender(), "unknown"));
profile.setIntro(intro);
profile.setCompanionIntent(companionIntent);
profile.setInterestTags(tagsToJson(dto.getInterestTags()));
// 人格卡片图片:只审本次新增的,已通过的旧图不重复送审
if (dto.getPersonaImages() != null) {
@ -312,14 +324,20 @@ public class IeMatchServiceImpl implements IeMatchService {
}
statusDTO.setRegionId(regionId);
statusDTO.setRegionName(regionName);
auditAndSaveMatchCompanionIntent(userId, profile, dto == null ? null : dto.getCompanionIntent());
updateStatus(userId, statusDTO);
String mood = statusDTO.getMood() == null ? "quiet" : statusDTO.getMood();
String intent = normalizeIntent(dto == null ? null : dto.getIntent());
if (intent != null) {
redisService.addToIntentPool(intent, userId);
}
List<Long> candidates = candidateUsers(targetMode, mode, mood, regionId, regionScope);
if (intent != null) {
String fuzzyKeyword = normalizeFuzzyKeyword(dto == null ? null : dto.getFuzzyKeyword());
boolean fuzzySearch = hasText(fuzzyKeyword);
List<Long> candidates = fuzzySearch ? fuzzyCandidates(userId, fuzzyKeyword, 80) : new ArrayList<>();
if (candidates.isEmpty()) {
candidates = candidateUsers(targetMode, mode, mood, regionId, regionScope);
}
if (!fuzzySearch && intent != null) {
// 同意图的人优先进入候选(去重交给 LinkedHashSet 逻辑前置)
List<Long> intentUsers = redisService.intentCandidates(intent, 50);
intentUsers.removeAll(candidates);
@ -350,6 +368,9 @@ public class IeMatchServiceImpl implements IeMatchService {
if (intent != null && redisService.inIntentPool(intent, candidateUserId)) {
score += 200;
}
if (fuzzySearch) {
score += fuzzyFieldScore(candidateProfile, fuzzyKeyword);
}
matchedCandidates.add(new MatchCandidate(candidateUserId, score));
}
for (MatchCandidate candidate : randomizeCandidates(matchedCandidates)) {
@ -403,13 +424,7 @@ public class IeMatchServiceImpl implements IeMatchService {
}
vo.setRoomId(room.getId());
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()));
fillMatchTargetProfile(vo, targetProfile);
vo.setLastActiveText(lastActiveText(targetUserId, targetProfile, statusActiveMap.get(targetUserId)));
vo.setIntent(intent);
vo.setIntentMatched(intent != null && redisService.inIntentPool(intent, targetUserId));
@ -444,7 +459,11 @@ public class IeMatchServiceImpl implements IeMatchService {
if (hasBlockRelation(userId, targetUserId)) {
throw new RuntimeException("你们之间存在黑名单关系,无法发起陪伴");
}
IeUserProfile targetProfile = ensureProfile(targetUserId);
IeUserProfile targetProfile = findProfileByUserId(targetUserId);
if (targetProfile == null || targetProfile.getProfileCompleted() == null || targetProfile.getProfileCompleted() != 1
|| Integer.valueOf(1).equals(targetProfile.getIsDeleted())) {
throw new RuntimeException("对方资料不存在或暂不可匹配");
}
IeRoom existing = findReusableRoom(userId, targetUserId);
boolean hasExistingRelation = existing != null || hasRoomRelation(userId, targetUserId);
long maxQuota = effectiveDailyQuota(profile);
@ -481,13 +500,7 @@ public class IeMatchServiceImpl implements IeMatchService {
IeMatchVO vo = toMatchVO(match);
vo.setRoomId(room.getId());
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()));
fillMatchTargetProfile(vo, targetProfile);
return vo;
} catch (RuntimeException e) {
if (!hasExistingRelation) {
@ -557,13 +570,55 @@ public class IeMatchServiceImpl implements IeMatchService {
if (profile == null) {
return;
}
fillMatchTargetProfile(vo, profile);
}
private void fillMatchTargetProfile(IeMatchVO vo, IeUserProfile profile) {
if (vo == null || profile == null) {
return;
}
vo.setAnonymousName(defaultText(profile.getAnonymousName(), vo.getAnonymousName()));
vo.setAvatarText(defaultText(profile.getAvatarText(), vo.getAvatarText()));
vo.setAvatarUrl(profile.getAvatarUrl());
vo.setGender(profile.getGender());
vo.setRegionId(defaultText(profile.getRegionId(), vo.getRegionId()));
vo.setRegionName(defaultText(profile.getRegionName(), vo.getRegionName()));
vo.setMode(defaultText(profile.getCurrentMode(), vo.getMode()));
vo.setIntro(profile.getIntro());
vo.setQuoteText(defaultText(profile.getIntro(), vo.getQuoteText()));
vo.setCompanionIntent(profile.getCompanionIntent());
vo.setInterestTags(jsonToTags(profile.getInterestTags()));
vo.setPersonaImages(jsonToTags(profile.getPersonaImages()));
}
@Override
public List<Map<String, Object>> latestCompanionIntents(Long currentUserId, Integer limit) {
int safeLimit = limit == null ? 50 : Math.max(1, Math.min(limit, 50));
List<IeUserProfile> profiles = userProfileMapper.selectList(new LambdaQueryWrapper<IeUserProfile>()
.eq(IeUserProfile::getProfileCompleted, 1)
.eq(IeUserProfile::getIsDeleted, 0)
.isNotNull(IeUserProfile::getCompanionIntent)
.ne(IeUserProfile::getCompanionIntent, "")
.orderByDesc(IeUserProfile::getUpdateTime)
.last("limit " + safeLimit));
List<Map<String, Object>> items = new ArrayList<>();
for (IeUserProfile profile : profiles) {
if (profile == null || !hasText(profile.getCompanionIntent())) {
continue;
}
Map<String, Object> item = new HashMap<>();
// 小程序端 JS Number 会丢失 Long 精度,定向匹配必须用字符串承载真实 userId。
item.put("userId", String.valueOf(profile.getUserId()));
item.put("isSelf", Objects.equals(profile.getUserId(), currentUserId));
item.put("text", profile.getCompanionIntent());
item.put("type", normalizeMode(profile.getCurrentMode(), "i"));
item.put("mood", "talk");
item.put("anonymousName", defaultText(profile.getAnonymousName(), "半匿名漂流者"));
item.put("avatarText", defaultText(profile.getAvatarText(), "◌"));
item.put("avatarUrl", profile.getAvatarUrl());
items.add(item);
}
return items;
}
private IeRoom createRoom(IeMatchAttempt match, Long userId, Long targetUserId, String mode, String mood) {
@ -852,6 +907,31 @@ public class IeMatchServiceImpl implements IeMatchService {
return score;
}
/**
* 模糊搜索只在命中的用户集合内挑最适合的人字段优先级昵称 > 一句话签名 > 找搭子内容
*/
private int fuzzyFieldScore(IeUserProfile profile, String keyword) {
if (profile == null || !hasText(keyword)) {
return 0;
}
String key = keyword.trim();
int score = 0;
if (containsText(profile.getAnonymousName(), key)) {
score += 300;
}
if (containsText(profile.getIntro(), key)) {
score += 200;
}
if (containsText(profile.getCompanionIntent(), key)) {
score += 120;
}
return score;
}
private boolean containsText(String value, String keyword) {
return value != null && keyword != null && value.contains(keyword);
}
/**
* 随机化候选顺序先打乱再按得分稳定排序同分顺序随机
* 然后在得分最高的一批里整体随机避免每次都按固定顺序匹配到同一个人
@ -930,6 +1010,33 @@ public class IeMatchServiceImpl implements IeMatchService {
return users;
}
private List<Long> fuzzyCandidates(Long currentUserId, String keyword, int limit) {
if (!hasText(keyword)) {
return new ArrayList<>();
}
String like = "%" + keyword.trim() + "%";
LambdaQueryWrapper<IeUserProfile> query = new LambdaQueryWrapper<IeUserProfile>()
.eq(IeUserProfile::getProfileCompleted, 1)
.eq(IeUserProfile::getIsDeleted, 0)
.ne(currentUserId != null, IeUserProfile::getUserId, currentUserId)
.and(wrapper -> wrapper
.like(IeUserProfile::getAnonymousName, like)
.or()
.like(IeUserProfile::getIntro, like)
.or()
.like(IeUserProfile::getCompanionIntent, like))
.orderByDesc(IeUserProfile::getLastActiveTime)
.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) {
users.add(profile.getUserId());
}
}
return users;
}
private String auditProfileText(String bizType, Long userId, String content) {
IeAuditResult result = auditService.audit(bizType, userId, userId, content);
if (Boolean.TRUE.equals(result.getBlocked())) {
@ -938,6 +1045,31 @@ public class IeMatchServiceImpl implements IeMatchService {
return result.getFilteredContent();
}
private String auditCompanionIntent(Long userId, String content) {
if (!hasText(content)) {
return "";
}
String trimmed = content.trim();
if (trimmed.length() > 24) {
throw new RuntimeException("找搭子内容不能超过 24 个字");
}
return auditProfileText("profile-companion-intent", userId, trimmed);
}
private String auditAndSaveMatchCompanionIntent(Long userId, IeUserProfile profile, String content) {
if (!hasText(content)) {
return profile == null ? "" : defaultText(profile.getCompanionIntent(), "");
}
String audited = auditCompanionIntent(userId, content);
if (profile != null && !Objects.equals(profile.getCompanionIntent(), audited)) {
profile.setCompanionIntent(audited);
profile.setUpdateTime(new Date());
profile.setLastActiveTime(new Date());
userProfileMapper.updateById(profile);
}
return audited;
}
private void auditProfileImage(String imageUrl, String existingUrl) {
if (imageUrl == null || imageUrl.trim().isEmpty() || imageUrl.equals(existingUrl)) {
return;
@ -959,6 +1091,7 @@ public class IeMatchServiceImpl implements IeMatchService {
vo.setAvatarUrl(profile.getAvatarUrl());
vo.setGender(profile.getGender());
vo.setIntro(profile.getIntro());
vo.setCompanionIntent(profile.getCompanionIntent());
vo.setInterestTags(jsonToTags(profile.getInterestTags()));
vo.setPersonaImages(jsonToTags(profile.getPersonaImages()));
vo.setCurrentMode(profile.getCurrentMode());
@ -1031,6 +1164,14 @@ public class IeMatchServiceImpl implements IeMatchService {
return "school";
}
private String normalizeFuzzyKeyword(String value) {
if (!hasText(value)) {
return "";
}
String keyword = value.trim();
return keyword.length() > 24 ? keyword.substring(0, 24) : keyword;
}
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}

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

@ -3,6 +3,7 @@ package cc.hiver.mall.ie.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class IeHomeVO {
@ -18,4 +19,5 @@ public class IeHomeVO {
private Integer profileCompleted;
private IeUserProfileVO profile;
private List<String> hotStatuses;
private List<Map<String, Object>> companionIntents;
}

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

@ -17,6 +17,7 @@ public class IeMatchVO {
private String regionId;
private String regionName;
private String intro;
private String companionIntent;
private List<String> interestTags;
private List<String> personaImages;
private String mode;

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

@ -12,6 +12,7 @@ public class IeUserProfileVO {
private String avatarUrl;
private String gender;
private String intro;
private String companionIntent;
private List<String> interestTags;
private List<String> personaImages;
private String currentMode;

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

@ -13,6 +13,7 @@ import cc.hiver.mall.planet.service.PlanetDrawService;
import cc.hiver.mall.planet.service.PlanetNewsService;
import cc.hiver.mall.planet.service.PlanetTaskService;
import cc.hiver.mall.planet.service.PlanetTicketService;
import cc.hiver.mall.service.HomeReminderService;
import cc.hiver.mall.service.mybatis.DealingsRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
@ -80,6 +81,9 @@ public class PlanetDrawServiceImpl implements PlanetDrawService {
@Autowired
private DealingsRecordService dealingsRecordService;
@Autowired
private HomeReminderService homeReminderService;
/**
* 抽奖功能强制按商圈隔离缺少 regionId 直接拒绝杜绝跨区域开奖/查询
*/
@ -449,6 +453,7 @@ public class PlanetDrawServiceImpl implements PlanetDrawService {
winner.setIsRead(1);
winner.setReadTime(new Date());
drawWinnerMapper.updateById(winner);
homeReminderService.clearPlanetReminders(userId, regionId);
}
@Override
@ -480,6 +485,7 @@ public class PlanetDrawServiceImpl implements PlanetDrawService {
winner.setReadTime(new Date());
}
drawWinnerMapper.updateById(winner);
homeReminderService.clearPlanetReminders(userId, regionId);
DealingsRecord dealingsRecord = new DealingsRecord();
dealingsRecord.setDealingsWay(DealingsRecordConstant.TYPE_REMARK[8]);
@ -523,6 +529,8 @@ public class PlanetDrawServiceImpl implements PlanetDrawService {
winner.setIsRead(0);
winner.setCreateTime(now);
drawWinnerMapper.insert(winner);
homeReminderService.addPlanetReward(ticket.getUserId(), record.getRegionId(), "draw",
"白嫖星球中奖啦", winner.getId());
}
private List<Candidate> usableCandidates(List<Candidate> pool, Set<String> cooldownUsers) {

35
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetServiceImpl.java

@ -1,21 +1,23 @@
package cc.hiver.mall.planet.service.impl;
import cc.hiver.mall.planet.constant.PlanetConstant;
import cc.hiver.mall.planet.entity.*;
import cc.hiver.mall.planet.entity.PlanetPool;
import cc.hiver.mall.planet.entity.PlanetPoolTicket;
import cc.hiver.mall.planet.entity.PlanetReward;
import cc.hiver.mall.planet.entity.PlanetTicket;
import cc.hiver.mall.planet.mapper.PlanetPoolTicketMapper;
import cc.hiver.mall.planet.mapper.PlanetRewardMapper;
import cc.hiver.mall.planet.mapper.PlanetTicketLogMapper;
import cc.hiver.mall.planet.pojo.PlanetDailyLoopVo;
import cc.hiver.mall.planet.pojo.PlanetHomeVo;
import cc.hiver.mall.planet.pojo.PlanetQuery;
import cc.hiver.mall.planet.service.*;
import cc.hiver.mall.service.HomeReminderService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@ -55,15 +57,16 @@ public class PlanetServiceImpl implements PlanetService {
private PlanetPoolTicketMapper poolTicketMapper;
@Autowired
private PlanetTicketLogMapper ticketLogMapper;
private PlanetRewardMapper rewardMapper;
@Autowired
private PlanetRewardMapper rewardMapper;
private HomeReminderService homeReminderService;
@Override
public PlanetHomeVo home(PlanetQuery query) {
final String userId = query.getUserId();
final String regionId = query.getRegionId();
homeReminderService.clearPlanetReminders(userId, regionId);
final PlanetHomeVo vo = new PlanetHomeVo();
// 初始化/补全用户券记录
@ -93,8 +96,8 @@ public class PlanetServiceImpl implements PlanetService {
vo.setRemainHunt(huntService.remainHunt(userId));
vo.setLevel(PlanetConstant.dangerLevel(vo.getMyTicketCount()));
vo.setMyRankNo(rankService.userRankNo(regionId, userId));
vo.setRankList(rankService.rankTop(regionId, 10, userId));
vo.setDailyLoop(lightDailyLoop(userId, regionId, pool));
vo.setUnreadWinning(drawService.unreadWinning(userId, regionId));
return vo;
}
@ -130,7 +133,6 @@ public class PlanetServiceImpl implements PlanetService {
final int suggested = myTickets <= 0 ? 3 : Math.min(10, Math.max(1, total / 20));
vo.setSuggestedAddTickets(suggested);
vo.setSuggestedProbability(probability(calcWeightTickets(myTickets + suggested), weightTotal - myWeight + calcWeightTickets(myTickets + suggested)));
vo.setTodayCollectedTickets(todayCollectedTickets(userId, regionId));
return vo;
}
@ -156,25 +158,6 @@ public class PlanetServiceImpl implements PlanetService {
return total;
}
private Integer todayCollectedTickets(String userId, String regionId) {
final Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
final List<PlanetTicketLog> logs = ticketLogMapper.selectList(new LambdaQueryWrapper<PlanetTicketLog>()
.select(PlanetTicketLog::getChangeCount)
.eq(PlanetTicketLog::getUserId, userId)
.eq(PlanetTicketLog::getRegionId, regionId)
.gt(PlanetTicketLog::getChangeCount, 0)
.ge(PlanetTicketLog::getCreateTime, c.getTime()));
int total = 0;
for (PlanetTicketLog log : logs) {
total += log.getChangeCount() == null ? 0 : log.getChangeCount();
}
return total;
}
private BigDecimal probability(int mine, int total) {
if (mine <= 0 || total <= 0) {
return BigDecimal.ZERO;

6
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTaskServiceImpl.java

@ -161,9 +161,9 @@ public class PlanetTaskServiceImpl implements PlanetTaskService {
if (StringUtils.isEmpty(regionId)) {
return;
}
seedTask(regionId, PlanetConstant.TASK_WAIMAI, "完成外卖订单", "每完成一笔外卖订单 +1 星球券", 1, 0, 1);
seedTask(regionId, PlanetConstant.TASK_GROUP, "完成团购订单", "每完成一笔团购订单 +1 星球券", 1, 0, 2);
seedTask(regionId, PlanetConstant.TASK_INVITE, "邀请好友注册", "每邀请一位好友注册 +3 星球券", 3, 0, 3);
seedTask(regionId, PlanetConstant.TASK_WAIMAI, "完成外卖订单", "每完成一笔外卖订单 +6 星球券", 1, 0, 1);
seedTask(regionId, PlanetConstant.TASK_GROUP, "完成团购订单", "每完成一笔团购订单 +6 星球券", 1, 0, 2);
seedTask(regionId, PlanetConstant.TASK_INVITE, "邀请好友注册", "每邀请一位好友注册 +6 星球券", 3, 0, 3);
seedTask(regionId, PlanetConstant.TASK_SIGN, "每日签到", "每日签到领取 +1 星球券", 1, 1, 4);
}

6
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java

@ -11,6 +11,7 @@ import cc.hiver.mall.planet.mapper.PlanetAdventureSessionMapper;
import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
import cc.hiver.mall.planet.service.PlanetAdventureService;
import cc.hiver.mall.planet.service.PlanetTicketService;
import cc.hiver.mall.service.HomeReminderService;
import cc.hiver.mall.service.mybatis.DealingsRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
@ -54,6 +55,9 @@ public class PlanetAdventureSettleTask {
@Autowired
private DealingsRecordService dealingsRecordService;
@Autowired
private HomeReminderService homeReminderService;
@Scheduled(cron = "0 30 21 * * ?")
public void settleAdventureRanks() {
try {
@ -165,6 +169,8 @@ public class PlanetAdventureSettleTask {
dealingsRecord.setCreateBy(session.getUserId());
dealingsRecord.setRemark("学院助推赛荣耀榜第" + rankNo + "名现金奖励");
dealingsRecordService.save(dealingsRecord);
homeReminderService.addPlanetReward(String.valueOf(session.getUserId()), session.getRegionId(), "rankTop",
"学院助推赛第" + rankNo + "名奖励 ¥" + amount, linkId);
return true;
}
}

20
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/HomeReminderService.java

@ -0,0 +1,20 @@
package cc.hiver.mall.service;
import java.util.Map;
public interface HomeReminderService {
void incrementIeUnread(Long userId);
Integer getIeUnreadCount(Long userId);
void setIeUnreadCount(Long userId, int count);
void clearIeUnread(Long userId);
void addPlanetReward(String userId, String regionId, String type, String title, String recordId);
void clearPlanetReminders(String userId, String regionId);
Map<String, Object> homeReminders(String userId, String regionId);
}

138
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/HomeReminderServiceImpl.java

@ -0,0 +1,138 @@
package cc.hiver.mall.serviceimpl;
import cc.hiver.mall.service.HomeReminderService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class HomeReminderServiceImpl implements HomeReminderService {
private static final String KEY_PREFIX = "home:reminder:";
private static final String FIELD_IE_UNREAD = "ieUnread";
private static final String FIELD_PLANET_DRAW = "planetDraw";
private static final String FIELD_PLANET_RANK_TOP = "planetRankTop";
private static final long TTL_DAYS = 14L;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void incrementIeUnread(Long userId) {
if (userId == null) {
return;
}
String key = userKey(String.valueOf(userId));
stringRedisTemplate.opsForHash().increment(key, FIELD_IE_UNREAD, 1);
stringRedisTemplate.expire(key, TTL_DAYS, TimeUnit.DAYS);
}
@Override
public Integer getIeUnreadCount(Long userId) {
if (userId == null) {
return 0;
}
Object value = stringRedisTemplate.opsForHash().get(userKey(String.valueOf(userId)), FIELD_IE_UNREAD);
return parseInt(value);
}
@Override
public void setIeUnreadCount(Long userId, int count) {
if (userId == null) {
return;
}
String key = userKey(String.valueOf(userId));
if (count <= 0) {
stringRedisTemplate.opsForHash().delete(key, FIELD_IE_UNREAD);
return;
}
stringRedisTemplate.opsForHash().put(key, FIELD_IE_UNREAD, String.valueOf(count));
stringRedisTemplate.expire(key, TTL_DAYS, TimeUnit.DAYS);
}
@Override
public void clearIeUnread(Long userId) {
if (userId == null) {
return;
}
stringRedisTemplate.opsForHash().delete(userKey(String.valueOf(userId)), FIELD_IE_UNREAD);
}
@Override
public void addPlanetReward(String userId, String regionId, String type, String title, String recordId) {
if (StrUtil.isBlank(userId)) {
return;
}
String field = "rankTop".equals(type) ? FIELD_PLANET_RANK_TOP : FIELD_PLANET_DRAW;
Map<String, Object> payload = new HashMap<>();
payload.put("type", type);
payload.put("regionId", regionId);
payload.put("title", title);
payload.put("recordId", recordId);
payload.put("time", System.currentTimeMillis());
String key = userKey(userId);
stringRedisTemplate.opsForHash().put(key, field, JSONUtil.toJsonStr(payload));
stringRedisTemplate.expire(key, TTL_DAYS, TimeUnit.DAYS);
}
@Override
public void clearPlanetReminders(String userId, String regionId) {
if (StrUtil.isBlank(userId)) {
return;
}
stringRedisTemplate.opsForHash().delete(userKey(userId), FIELD_PLANET_DRAW, FIELD_PLANET_RANK_TOP);
}
@Override
public Map<String, Object> homeReminders(String userId, String regionId) {
Map<String, Object> result = new HashMap<>();
if (StrUtil.isBlank(userId)) {
result.put("ieUnreadCount", 0);
result.put("ie", false);
result.put("planet", false);
return result;
}
String key = userKey(userId);
Integer ieUnread = parseInt(stringRedisTemplate.opsForHash().get(key, FIELD_IE_UNREAD));
Object draw = stringRedisTemplate.opsForHash().get(key, FIELD_PLANET_DRAW);
Object rankTop = stringRedisTemplate.opsForHash().get(key, FIELD_PLANET_RANK_TOP);
result.put("ieUnreadCount", ieUnread);
result.put("ie", ieUnread > 0);
result.put("planet", draw != null || rankTop != null);
result.put("planetDraw", parseJson(draw));
result.put("planetRankTop", parseJson(rankTop));
return result;
}
private String userKey(String userId) {
return KEY_PREFIX + userId;
}
private Integer parseInt(Object value) {
if (value == null) {
return 0;
}
try {
return Math.max(0, Integer.parseInt(String.valueOf(value)));
} catch (Exception ignored) {
return 0;
}
}
private Object parseJson(Object value) {
if (value == null || StrUtil.isBlank(String.valueOf(value))) {
return null;
}
try {
return JSONUtil.parseObj(String.valueOf(value));
} catch (Exception ignored) {
return null;
}
}
}

5
hiver-modules/hiver-mall/src/main/resources/db/ie_user_profile_companion_intent.sql

@ -0,0 +1,5 @@
ALTER TABLE ie_user_profile
ADD COLUMN companion_intent VARCHAR(80) NULL COMMENT '用户自由输入的找搭子内容';
ALTER TABLE ie_user_profile
ADD INDEX idx_ie_profile_companion_latest (profile_completed, is_deleted, update_time);
Loading…
Cancel
Save