diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java index cf375f4e4..f3b6369a6 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java @@ -39,6 +39,7 @@ public interface ErrorCodeConstants { ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1004009001, "签到天数规则已存在"); //========== 签到配置 1004010000 ========== + ErrorCode SIGN_IN_RECORD_EXISTS = new ErrorCode(1004010000,"用户重复签到"); //========== 用户等级 1004011000 ========== diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java new file mode 100644 index 000000000..dd5acd313 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.member.controller.app.signin; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert; +import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; +import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 描述 :用户签到相关信息接口 + * Author :xiaqing + * Date :2023-09-15 09:02 + */ +@Tag(name = "签到APP - 签到") +@RestController +@RequestMapping("/member/signin") +public class AppMemberSignInController { + + @Resource + MemberSignInRecordService signInRecordService; + + /** + * 描述 :获取个人签到信息 + * Author :xiaqing + * Date :2023-09-15 12:56:47 + */ + + @Operation(summary = "个人签到信息") + @GetMapping("/get-summary") + public CommonResult getUserSummary(){ + return CommonResult.success(signInRecordService.getUserSummary(SecurityFrameworkUtils.getLoginUserId())); + } + + + /** + * 描述 :用户签到 + * Author :xiaqing + * Date :2023-09-15 09:20:58 + */ + @Operation(summary = "会员签到") + @PostMapping("/create") + public CommonResult create(){ + MemberSignInRecordDO recordDO = signInRecordService.create(SecurityFrameworkUtils.getLoginUserId()); + return CommonResult.success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO)); + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java new file mode 100644 index 000000000..e7ea26c2d --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.member.controller.app.signin.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户签到积分 Response VO") +@Data +public class AppMemberSignInRecordRespVO { + + @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer day; + + @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer point; + + @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInSummaryRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInSummaryRespVO.java new file mode 100644 index 000000000..55eaddbdd --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInSummaryRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.member.controller.app.signin.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户签到统计信息 Response VO") +@Data +public class AppMemberSignInSummaryRespVO { + + @Schema(description = "持续签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Integer continuousDay; + + @Schema(description = "总签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer totalDay; + + @Schema(description = "当天是否签到", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + private Boolean todaySignIn ; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java index c5022b854..edac5edae 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java @@ -36,4 +36,6 @@ public interface MemberSignInRecordConvert { PageResult convertPage02(PageResult pageResult); + AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO); + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java index b63b8167c..211ead33d 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java @@ -18,7 +18,7 @@ public interface MemberSignInConfigMapper extends BaseMapperX selectListByStatus(Integer status) { + default List selectListByStatus(Integer status) { return selectList(MemberSignInConfigDO::getStatus, status); } } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java index 840eeb328..84e61a761 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSi import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; import java.util.Set; /** @@ -33,4 +34,12 @@ public interface MemberSignInRecordMapper extends BaseMapperX selectListByUserId(Long userId){ + return selectList(new LambdaQueryWrapperX () + .eq(MemberSignInRecordDO::getUserId, userId) + .orderByDesc(MemberSignInRecordDO::getCreateTime)); + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java index dd263fe42..6e8096d1b 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.member.service.signin; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO; +import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInRecordRespVO; +import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO; import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO; /** @@ -29,4 +31,18 @@ public interface MemberSignInRecordService { */ PageResult getSignRecordPage(Long userId, PageParam pageParam); + + MemberSignInRecordDO create(Long userId); + + /** + * + *功能描述: 根据用户id获取个人签到信息 + * @param userId + * @return + * @author xiaqing + * @date 2023-09-15 14:21:01 + */ + AppMemberSignInSummaryRespVO getUserSummary(Long userId); + + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java index 76589d67f..02759db19 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java @@ -1,21 +1,30 @@ package cn.iocoder.yudao.module.member.service.signin; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +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.dto.MemberUserRespDTO; 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.dal.dataobject.signin.MemberSignInConfigDO; 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.enums.ErrorCodeConstants; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Set; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; /** @@ -29,16 +38,64 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService @Resource private MemberSignInRecordMapper memberSignInRecordMapper; - + @Resource + private MemberSignInRecordMapper signInRecordMapper; + @Resource + private MemberSignInConfigMapper signInConfigMapper; @Resource private MemberUserApi memberUserApi; + @Override - public PageResult getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) { - // 根据用户昵称查询出用户 ids - Set userIds = null; + public AppMemberSignInSummaryRespVO getUserSummary(Long userId) { + AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO(); + vo.setTotalDay(0); + vo.setContinuousDay(0); + vo.setTodaySignIn(false); + //获取用户签到的记录,按照天数倒序获取 + List signInRecordDOList = signInRecordMapper.selectListByUserId(userId); + if(!CollectionUtils.isEmpty(signInRecordDOList)){ + //设置总签到天数 + vo.setTotalDay(signInRecordDOList.size()); + //判断当天是否有签到复用校验方法 + try { + validSignDay(signInRecordDOList.get(0)); + vo.setTodaySignIn(false); + }catch (Exception e){ + vo.setTodaySignIn(true); + } + //如果当天签到了则说明连续签到天数有意义,否则直接用默认值0 + if(vo.getTodaySignIn()){ + //下方计算连续签到从2天开始,此处直接设置一天连续签到 + vo.setContinuousDay(1); + //判断连续签到天数 + 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; + } + } + } + + + } + return vo; + } + + private long daysBetween(LocalDate date1,LocalDate date2){ + return ChronoUnit.DAYS.between(date1, date2); + } + + @Override + public PageResult getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) { + // 根据用户昵称查询出用户ids + Set userIds = null; if (StringUtils.isNotBlank(pageReqVO.getNickname())) { - List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); + List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); // 如果查询用户结果为空直接返回无需继续查询 if (CollectionUtils.isEmpty(users)) { return PageResult.empty(); @@ -54,4 +111,58 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService return memberSignInRecordMapper.selectPage(userId, pageParam); } + @Override + public MemberSignInRecordDO create(Long userId) { + //获取当前用户签到的最大天数 + MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX () + .eq(MemberSignInRecordDO::getUserId, userId) + .orderByDesc(MemberSignInRecordDO::getDay) + .last("limit 1")); + //判断是否重复签到 + validSignDay(maxSignDay); + + /**1.查询出当前签到的天数**/ + MemberSignInRecordDO sign = new MemberSignInRecordDO(); + sign.setUserId(userId); + //设置签到初始化天数 + sign.setDay(1); + //设置签到分数默认为0 + sign.setPoint(0); + //如果不为空则修改当前签到对应的天数 + if (maxSignDay != null) { + sign.setDay(maxSignDay.getDay() + 1); + } + /**2.获取签到对应的分数**/ + //获取所有的签到规则,按照天数排序,只获取启用的 + List configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX () + .eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) + .orderByAsc(MemberSignInConfigDO::getDay)); + //如果签到的天数大于最大启用的规则天数,直接给最大签到的分数 + MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1); + if (sign.getDay() > lastConfig.getDay()) { + sign.setPoint(lastConfig.getPoint()); + } else { + configDOList.forEach(el -> { + //循环匹配对应天数,设置对应分数 + if (el.getDay() == sign.getDay()) { + sign.setPoint(el.getPoint()); + } + + }); + } + + //3.插入当前签到获取的分数 + signInRecordMapper.insert(sign); + //4.返回给用户 + return sign; + } + + void validSignDay(MemberSignInRecordDO signInRecordDO) { + if (signInRecordDO == null) + return; + LocalDate today = LocalDate.now(); + if (today.equals(signInRecordDO.getCreateTime().toLocalDate())) { + throw exception(ErrorCodeConstants.SIGN_IN_RECORD_EXISTS); + } + } } diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 5dc82c68c..e8ed8cd77 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -44,31 +44,31 @@ spring: primary: master datasource: master: - name: ruoyi-vue-pro - url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + name: mall + url: jdbc:mysql://10.211.55.5:3308/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 username: root - password: 123456 + password: 1qaz!QAZ # username: sa # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W slave: # 模拟从库,可根据自己需要修改 - name: ruoyi-vue-pro - url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + name: mall + url: jdbc:mysql://10.211.55.5:3308/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 username: root - password: 123456 + password: 1qaz!QAZ # username: sa # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: - host: 127.0.0.1 # 地址 + host: 10.211.55.5 # 地址 port: 6379 # 端口 database: 0 # 数据库索引 # password: dev # 密码,建议生产环境开启