现在提供package com.example.bc.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.bc.constant.enums.DeleteStateEnum;
import com.example.bc.constant.enums.LockFlagEnum;
import com.example.bc.constant.enums.StatusEnum;
import com.example.bc.entity.BcNews;
import com.example.bc.entity.BcNewsUser;
import com.example.bc.mapper.BcNewsUserMapper;
import com.example.bc.service.BcNewsService;
import com.example.bc.service.BcNewsUserService;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
/**
* 资讯咨询用户关联
*
* @author 3214-
* @date 2025-09-12 09:52:09
*/
@Service
@Slf4j
public class BcNewsUserServiceImpl extends ServiceImpl<BcNewsUserMapper, BcNewsUser> implements BcNewsUserService {
@Override
public void publishNews() {//按 “周期 - 周数” 的规则,为用户匹配对应的资讯,并建立全新的用户 - 资讯关联关系(避免重复关联),最后批量存入数据库
try {
LocalDateTime now = LocalDateTime.now();//获取当前时间,用于后续设置关联记录的创建时间
List<BcNewsUser> publishedList = this.list();//查询所有已存在的 BcNewsUser 记录(即已有的用户 - 资讯关联数据)。
Set<String> existingRelations = publishedList.stream()
.map(bc -> bc.getUserId() + "_" + bc.getNewsId())
.collect(Collectors.toSet());//将已存在的关联记录,按 userId + "_" + newsId 的格式生成唯一键集合,用于后续判断是否重复关联。
BcNewsService bcNewsService = SpringContextHolder.getBean(BcNewsService.class);//通过 BcNewsService 查询资讯
LambdaQueryWrapper<BcNews> newsQuery = new LambdaQueryWrapper<>();
newsQuery.eq(BcNews::getDelFlag, DeleteStateEnum.UN_DELETED.getState())//delFlag 为未删除(DeleteStateEnum.UN_DELETED)
.eq(BcNews::getStatus, StatusEnum.ENABLE.getState());//status 为启用(StatusEnum.ENABLE)
newsQuery.ne(BcNews::getPushCycle, 1);//pushCycle 不等于 1(即排除按 “周期为 1” 规则推送的资讯)
List<BcNews> newsList = bcNewsService.list(newsQuery);
// 调整资讯分组键为“第X周 星期X”
Map<String, List<BcNews>> newsGroupByCycleWeek = newsList.stream()
.filter(news -> news.getPushCycle() != null && news.getPushWeek() != null)
.collect(Collectors.groupingBy(
//bug
news -> "第"+news.getPushCycle()+"周"+news.getPushWeek()
//news -> news.getPushCycle() + "_" + news.getPushWeek()
));
//对查询到的资讯进行分组:按 pushCycle + "_" + pushWeek(周期 + 周数)的格式分组,
//得到 Map<String, List<BcNews>> newsGroupByCycleWeek,方便后续按周期 - 周数匹配用户。
List<SysUser> userList = baseMapper.listUser();//查询系统中的用户列表
Map<String, List<SysUser>> userGroupByWeekKey = userWeekMap(userList);//调用 userWeekMap 方法(推测是按用户对应的 “周期 - 周数” 规则分组),
// 得到按周数键分组的用户映射。
List<BcNewsUser> addList = new ArrayList<>();//新闻咨询用户关联集合
for (Map.Entry<String, List<SysUser>> userEntry : userGroupByWeekKey.entrySet()) {//遍历分组后的用户
String weekKey = userEntry.getKey();
List<SysUser> users = userEntry.getValue();
List<BcNews> matchedNews = newsGroupByCycleWeek.get(weekKey);//对于每个周数键(weekKey),找到对应的资讯列表(matchedNews)
if (matchedNews == null || matchedNews.isEmpty()) {
continue;
}
for (BcNews news : matchedNews) {//遍历该周数下的用户
Long newsId = news.getId();
if (newsId == null) {///遍历匹配到的资讯,若资讯 ID(newsId)为 null,跳过该资讯
continue;
}
for (SysUser user : users) {
Long userId = user.getUserId();
if (userId == null) {//若用户 ID(userId)为 null,跳过该用户
continue;
}
String relationKey = userId + "_" + newsId;//生成关联唯一键 relationKey = userId + "_" + newsId
if (existingRelations.contains(relationKey)) {
continue;////若该键已存在于 existingRelations(即已关联过),跳过该用户与资讯的关联。
}
BcNewsUser relation = new BcNewsUser();//若未关联过,创建 BcNewsUser 实例
relation.setUserId(userId);//设置用户 ID、
relation.setNewsId(newsId);//资讯 ID、
relation.setIsRead(0);//未读状态(isRead = 0)
relation.setCreateTime(now);//和创建时间,
addList.add(relation);//添加到待保存的 addList 中。
}
}
}
if (!addList.isEmpty()) {//if (!addList.isEmpty()) { this.saveBatch(addList); ... }:
// 若有待保存的关联记录,批量保存到数据库,并打印日志记录保存数量
this.saveBatch(addList);
log.info("成功添加资讯用户关联数据,数量:{}", addList.size());
}
} catch (Exception e) {
log.error("发布资讯关联数据失败", e);
}
}
@Override
public void pushFirstWeekNews(Long userId) {
try {
// 1. 查询第一周需要推送的资讯(pushCycle=1)
BcNewsService bcNewsService = SpringContextHolder.getBean(BcNewsService.class);
LambdaQueryWrapper<BcNews> newsQuery = new LambdaQueryWrapper<>();
newsQuery.eq(BcNews::getPushCycle, 1); // 筛选第一周的资讯
List<BcNews> bcNewsList = bcNewsService.list(newsQuery);
// 若没有符合条件的资讯,直接返回
if (ObjectUtil.isEmpty(bcNewsList)) {
log.info("没有需要推送的第一周资讯,用户ID:{}", userId);
return;
}
// 2. 查询用户已有的资讯关联记录
LambdaQueryWrapper<BcNewsUser> userNewsQuery = new LambdaQueryWrapper<>();
userNewsQuery.eq(BcNewsUser::getUserId, userId);
List<BcNewsUser> existingUserNews = this.list(userNewsQuery);
// 3. 提取已有的资讯ID到Set(O(1)判断效率,避免嵌套循环)
Set<Long> existingNewsIds = new HashSet<>();
if (ObjectUtil.isNotEmpty(existingUserNews)) {
for (BcNewsUser userNews : existingUserNews) {
Long newsId = userNews.getNewsId();
if (newsId != null) { // 过滤null,避免空指针
existingNewsIds.add(newsId);
}
}
}
// 4. 筛选需要新增的关联记录(不存在于已有记录中)
List<BcNewsUser> toAddList = new ArrayList<>();
LocalDateTime now = LocalDateTime.now(); // 统一创建时间,避免循环中重复生成
for (BcNews news : bcNewsList) {
Long newsId = news.getId();
// 跳过无效资讯(ID为null)
if (newsId == null) {
log.warn("发现ID为null的无效资讯,已跳过");
continue;
}
// 仅添加未关联的资讯
if (!existingNewsIds.contains(newsId)) {
BcNewsUser newUserNews = createBcNewsUser(userId, newsId, now);
toAddList.add(newUserNews);
}
}
// 5. 批量保存(仅当有数据时执行)
if (ObjectUtil.isNotEmpty(toAddList)) {
this.saveBatch(toAddList);
log.info("给用户[{}]推送第一周资讯成功,新增{}条记录", userId, toAddList.size());
} else {
log.info("用户[{}]已拥有所有第一周资讯,无需新增", userId);
}
} catch (Exception e) {
log.error("推送第一周资讯失败,用户ID:{}", userId, e);
}
}
/**
* 抽取创建BcNewsUser的逻辑,减少重复代码
*/
private BcNewsUser createBcNewsUser(Long userId, Long newsId, LocalDateTime createTime) {
BcNewsUser bcNewsUser = new BcNewsUser();
bcNewsUser.setUserId(userId);
bcNewsUser.setNewsId(newsId);
bcNewsUser.setIsRead(0); // 0表示未读
bcNewsUser.setCreateTime(createTime);
return bcNewsUser;
}
/**
* 按“第X周 星期X”分组用户,确保与资讯的pushCycle和pushWeek匹配
*/
private Map<String, List<SysUser>> userWeekMap(List<SysUser> userList) {
if (userList == null || userList.isEmpty()) {
return Collections.emptyMap();
}
LocalDateTime now = LocalDateTime.now();
// bug处理星期:将 ChronoField.DAY_OF_WEEK(1 - 7 对应周一到周日)转换为中文“星期一”等
String[] weekDays = {"", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
int currentDayOfWeek = now.get(ChronoField.DAY_OF_WEEK);
//bug处理为周一到周日
String currentWeekDay = weekDays[currentDayOfWeek];
return userList.stream()
.collect(Collectors.groupingBy(
user -> {
LocalDateTime auditTime = user.getAuditTime();
long weeksSinceCreate;
if (auditTime == null) {
weeksSinceCreate = -1;
} else if (auditTime.isAfter(now)) {
weeksSinceCreate = 0;
} else {
weeksSinceCreate = ChronoUnit.WEEKS.between(auditTime, now) + 1;
//计算用户从创建时间到现在的周数是 ChronoUnit.WEEKS.between(auditTime, now) + 1,
// 但前端展示的是 “第 X 周 星期 X”,可能周数的起始或计算方式不一致。
}
//bug生成与资讯分组一致的键:“第X周 星期X”
return "第" + weeksSinceCreate + "周 " + currentWeekDay;
//return weeksSinceCreate + "_" + currentDayOfWeek;
}
));
}
}