签到记录:完善 review 提到的问题

This commit is contained in:
puhui999 2023-10-21 01:01:06 +08:00
parent f18a4741a9
commit 2f7371b4ea
3 changed files with 150 additions and 99 deletions

View File

@ -1,14 +1,19 @@
package cn.iocoder.yudao.module.member.convert.signin; package cn.iocoder.yudao.module.member.convert.signin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO;
import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO; import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -32,10 +37,37 @@ public interface MemberSignInRecordConvert {
memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname()))); memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname())));
return voPageResult; return voPageResult;
} }
PageResult<MemberSignInRecordRespVO> convertPage(PageResult<MemberSignInRecordDO> pageResult); PageResult<MemberSignInRecordRespVO> convertPage(PageResult<MemberSignInRecordDO> pageResult);
PageResult<AppMemberSignInRecordRespVO> convertPage02(PageResult<MemberSignInRecordDO> pageResult); PageResult<AppMemberSignInRecordRespVO> convertPage02(PageResult<MemberSignInRecordDO> pageResult);
AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO); AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO);
default MemberSignInRecordDO convert(Long userId, MemberSignInRecordDO firstRecord, List<MemberSignInConfigDO> signInConfigs) {
// 1. 计算今天是第几天签到
long day = ChronoUnit.DAYS.between(firstRecord.getCreateTime(), LocalDateTime.now());
// 2. 初始化签到信息
MemberSignInRecordDO signInRecord = new MemberSignInRecordDO().setUserId(userId)
.setDay(Integer.parseInt(Long.toString(day))) // 设置签到天数
.setPoint(0) // 设置签到积分默认为 0
.setExperience(0); // 设置签到经验默认为 0
// 3. 获取签到对应的积分数
MemberSignInConfigDO lastConfig = signInConfigs.get(signInConfigs.size() - 1); // 最大签到天数
if (day > lastConfig.getDay()) { // 超出范围按第一天的经验计算
signInRecord.setPoint(signInConfigs.get(0).getPoint());
signInRecord.setExperience(signInConfigs.get(0).getExperience());
return signInRecord;
}
MemberSignInConfigDO signInConfig = CollUtil.findOne(signInConfigs, config -> ObjUtil.equal(config.getDay(), day));
if (signInConfig == null) {
return signInRecord;
}
signInRecord.setPoint(signInConfig.getPoint());
signInRecord.setExperience(signInConfig.getExperience());
return signInRecord;
}
} }

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@ -35,9 +36,45 @@ public interface MemberSignInRecordMapper extends BaseMapperX<MemberSignInRecord
} }
//获取用户的签到记录列表信息,根据签到时间倒序 /**
default List<MemberSignInRecordDO> selectListByUserId(Long userId){ * 获取用户最近的签到记录信息,根据签到时间倒序
return selectList(new LambdaQueryWrapperX <MemberSignInRecordDO>() *
* @param userId 用户编号
* @return 签到记录列表
*/
default MemberSignInRecordDO selectLastRecordByUserIdDesc(Long userId) {
return selectOne(new QueryWrapper<MemberSignInRecordDO>()
.eq("user_id", userId)
.orderByDesc("create_time")
.last("limit 1"));
}
/**
* 获取用户最早的签到记录信息,根据签到时间倒序
*
* @param userId 用户编号
* @return 签到记录列表
*/
default MemberSignInRecordDO selectLastRecordByUserIdAsc(Long userId) {
return selectOne(new QueryWrapper<MemberSignInRecordDO>()
.eq("user_id", userId)
.orderByAsc("create_time")
.last("limit 1"));
}
default Long selectCountByUserId(Long userId) {
return selectCount(new LambdaQueryWrapperX<MemberSignInRecordDO>()
.eq(MemberSignInRecordDO::getUserId, userId));
}
/**
* 获取用户的签到记录列表信息,根据签到时间倒序
*
* @param userId 用户编号
* @return 签到记录信息
*/
default List<MemberSignInRecordDO> selectListByUserId(Long userId) {
return selectList(new LambdaQueryWrapperX<MemberSignInRecordDO>()
.eq(MemberSignInRecordDO::getUserId, userId) .eq(MemberSignInRecordDO::getUserId, userId)
.orderByDesc(MemberSignInRecordDO::getCreateTime)); .orderByDesc(MemberSignInRecordDO::getCreateTime));
} }

View File

@ -1,19 +1,20 @@
package cn.iocoder.yudao.module.member.service.signin; package cn.iocoder.yudao.module.member.service.signin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO; import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO;
import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper; import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum; import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.level.MemberLevelService; import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
@ -21,17 +22,17 @@ import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_RECORD_TODAY_EXISTS;
/** /**
* 签到记录 Service 实现类 * 签到记录 Service 实现类
@ -45,7 +46,7 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
@Resource @Resource
private MemberSignInRecordMapper signInRecordMapper; private MemberSignInRecordMapper signInRecordMapper;
@Resource @Resource
private MemberSignInConfigMapper signInConfigMapper; private MemberSignInConfigService signInConfigService;
@Resource @Resource
private MemberPointRecordService pointRecordService; private MemberPointRecordService pointRecordService;
@Resource @Resource
@ -56,49 +57,63 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
@Override @Override
public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) { public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) {
// 1. 初始化默认返回信息
AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO(); AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
vo.setTotalDay(0); vo.setTotalDay(0);
vo.setContinuousDay(0); vo.setContinuousDay(0);
vo.setTodaySignIn(false); vo.setTodaySignIn(false);
//获取用户签到的记录按照天数倒序获取
List<MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
// TODO @xiaqingif 空的时候直接 return这样括号少逻辑更简洁
if (!CollectionUtils.isEmpty(signInRecordDOList)) {
//设置总签到天数
vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing是不是不用读取 signInRecordDOList 所有的而是 count下然后另外再读取一条最后一条
//判断当天是否有签到复用校验方法
// TODO @xiaqing不要用异常实现逻辑还是判断哈
try {
validSignDay(signInRecordDOList.get(0));
vo.setTodaySignIn(false);
} catch (Exception e) {
vo.setTodaySignIn(true);
}
//如果当天签到了则说明连续签到天数有意义否则直接用默认值0
if (vo.getTodaySignIn()) {
//下方计算连续签到从2天开始此处直接设置一天连续签到
vo.setContinuousDay(1);
//判断连续签到天数
// TODO @xiaqing这里逻辑想想怎么在简化下可读性可以在提升下哈
for (int i = 1; i < signInRecordDOList.size(); i++) {
//前一天减1等于当前天数则说明连续继续循环
LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
LocalDate pre = signInRecordDOList.get(i - 1).getCreateTime().toLocalDate();
if (1 == daysBetween(cur, pre)) {
vo.setContinuousDay(i + 1);
} else {
break;
}
}
}
// 2. 获取用户签到的记录数
Long signCount = signInRecordMapper.selectCountByUserId(userId);
if (ObjUtil.equal(signCount, 0L)) {
return vo;
} }
vo.setTotalDay(signCount.intValue()); // 设置总签到天数
// 3. 校验当天是否有签到
MemberSignInRecordDO signInRecord = signInRecordMapper.selectLastRecordByUserIdDesc(userId);
if (signInRecord == null) {
return vo;
}
vo.setTodaySignIn(DateUtils.isToday(signInRecord.getCreateTime()));
// 4. 校验今天是否签到没有签到则直接返回
if (!vo.getTodaySignIn()) {
return vo;
}
// 4.1. 判断连续签到天数
List<MemberSignInRecordDO> signInRecords = signInRecordMapper.selectListByUserId(userId);
vo.setContinuousDay(calculateConsecutiveDays(signInRecords));
return vo; return vo;
} }
private long daysBetween(LocalDate date1, LocalDate date2) { /**
return ChronoUnit.DAYS.between(date1, date2); * 计算连续签到天数
*
* @param signInRecords 签到记录列表
* @return int 连续签到天数
*/
public int calculateConsecutiveDays(List<MemberSignInRecordDO> signInRecords) {
int consecutiveDays = 1; // 初始连续天数为1
LocalDate previousDate = null;
for (MemberSignInRecordDO record : signInRecords) {
LocalDate currentDate = record.getCreateTime().toLocalDate();
if (previousDate != null) {
// 检查相邻两个日期是否连续
if (currentDate.minusDays(1).isEqual(previousDate)) {
consecutiveDays++;
} else {
// 如果日期不连续停止遍历
break;
}
}
previousDate = currentDate;
}
return consecutiveDays;
} }
@Override @Override
@ -108,7 +123,7 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
if (StringUtils.isNotBlank(pageReqVO.getNickname())) { if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
// 如果查询用户结果为空直接返回无需继续查询 // 如果查询用户结果为空直接返回无需继续查询
if (CollectionUtils.isEmpty(users)) { if (CollUtil.isEmpty(users)) {
return PageResult.empty(); return PageResult.empty();
} }
userIds = convertSet(users, MemberUserRespDTO::getId); userIds = convertSet(users, MemberUserRespDTO::getId);
@ -125,73 +140,40 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public MemberSignInRecordDO createSignRecord(Long userId) { public MemberSignInRecordDO createSignRecord(Long userId) {
// 获取当前用户签到的最大天数 // 1. 获取当前用户最近的签到
// TODO @xiaqingdb 操作dou封装到 mapper MemberSignInRecordDO lastRecord = signInRecordMapper.selectLastRecordByUserIdDesc(userId);
// TODO @xiaqingmaxSignDay是不是变量叫 lastRecord 会更容易理解哈 // 1.1. 判断是否重复签到
MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX<MemberSignInRecordDO>() validateSigned(lastRecord);
.eq(MemberSignInRecordDO::getUserId, userId)
.orderByDesc(MemberSignInRecordDO::getDay)
.last("limit 1"));
// 判断是否重复签到
validSignDay(maxSignDay);
// 1. 查询出当前签到的天数 // 2. 获取当前用户最早的一次前端记录用于计算今天是第几天签到
MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing应该使用 record 变量会更合适 MemberSignInRecordDO firstRecord = signInRecordMapper.selectLastRecordByUserIdAsc(userId);
sign.setDay(1); // 设置签到初始化天数 // 2.1. 获取所有的签到规则
sign.setPoint(0); // 设置签到积分默认为 0 List<MemberSignInConfigDO> signInConfigs = signInConfigService.getSignInConfigList(CommonStatusEnum.ENABLE.getStatus());
sign.setExperience(0); // 设置签到经验默认为 0 signInConfigs.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
// 如果不为空则修改当前签到对应的天数 // 2.2. 组合数据
// TODO @xiaqing应该是要判断连续哈就是昨天 MemberSignInRecordDO record = MemberSignInRecordConvert.INSTANCE.convert(userId, firstRecord, signInConfigs);
if (maxSignDay != null) {
sign.setDay(maxSignDay.getDay() + 1);
}
// 2. 获取签到对应的积分数
// 获取所有的签到规则按照天数排序只获取启用的 TODO @xiaqing不要使用 signInConfigMapper 直接查询而是要通过 SigninConfigService
List<MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX<MemberSignInConfigDO>()
.eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
.orderByAsc(MemberSignInConfigDO::getDay));
// 如果签到的天数大于最大启用的规则天数直接给最大签到的积分数
// TODO @xiaqing超过最大配置的天数应该直接重置到第一天哈
MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
if (sign.getDay() > lastConfig.getDay()) {
sign.setPoint(lastConfig.getPoint());
sign.setExperience(lastConfig.getExperience());
} else {
configDOList.forEach(el -> {
// 循环匹配对应天数设置对应积分数
// TODO @xiaqing使用 equals另外这种不应该去遍历比较从可读性来说应该 CollUtil.findOne()
if (el.getDay() == sign.getDay()) {
sign.setPoint(el.getPoint());
sign.setExperience(el.getExperience());
}
});
}
// 3. 插入签到记录 // 3. 插入签到记录
signInRecordMapper.insert(sign); signInRecordMapper.insert(record);
// 4. 增加积分 // 4. 增加积分
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) { if (!ObjectUtils.equalsAny(record.getPoint(), null, 0)) {
pointRecordService.createPointRecord(userId, sign.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(sign.getId())); pointRecordService.createPointRecord(userId, record.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(record.getId()));
} }
// 5. 增加经验 // 5. 增加经验
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) { if (!ObjectUtils.equalsAny(record.getExperience(), null, 0)) {
memberLevelService.addExperience(userId, sign.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(sign.getId())); memberLevelService.addExperience(userId, record.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(record.getId()));
} }
return sign; return record;
} }
// TODO @xiaqing校验使用 validate 动词哈可以改成 validateSigned private void validateSigned(MemberSignInRecordDO signInRecordDO) {
private void validSignDay(MemberSignInRecordDO signInRecordDO) { if (signInRecordDO == null) {
// TODO @xiaqing代码格式if () {} 要有括号哈
if (signInRecordDO == null)
return; return;
// TODO @xiaqing可以直接使用 DateUtils.isToday() }
LocalDate today = LocalDate.now(); if (DateUtils.isToday(signInRecordDO.getCreateTime())) {
if (today.equals(signInRecordDO.getCreateTime().toLocalDate())) { throw exception(SIGN_IN_RECORD_TODAY_EXISTS);
throw exception(ErrorCodeConstants.SIGN_IN_RECORD_TODAY_EXISTS);
} }
} }