签到模块
This commit is contained in:
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package cn.novalon.gym.manage.checkIn.config;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.HandlerMapping;
|
||||||
|
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||||
|
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||||
|
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebSocketConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MyWebSocketHandler myWebSocketHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 WebSocket 路由映射
|
||||||
|
* 路径对应前端连接的 ws://xxx/webSocket/checkIn
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public HandlerMapping webSocketMapping() {
|
||||||
|
Map<String, WebSocketHandler> map = new HashMap<>();
|
||||||
|
map.put("/webSocket/checkIn", myWebSocketHandler);
|
||||||
|
|
||||||
|
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
|
||||||
|
mapping.setUrlMap(map);
|
||||||
|
mapping.setOrder(10); // 设置优先级
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 WebSocket 处理器适配器(必须)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public WebSocketHandlerAdapter handlerAdapter() {
|
||||||
|
return new WebSocketHandlerAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -36,6 +36,7 @@ public class CheckInHandler {
|
|||||||
public Mono<ServerResponse> checkIn(ServerRequest request) {
|
public Mono<ServerResponse> checkIn(ServerRequest request) {
|
||||||
|
|
||||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||||
|
|
||||||
return request.bodyToMono(Map.class)
|
return request.bodyToMono(Map.class)
|
||||||
.flatMap(body -> {
|
.flatMap(body -> {
|
||||||
String qrContent = (String) body.get("qrContent");
|
String qrContent = (String) body.get("qrContent");
|
||||||
|
|||||||
+2
-35
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.checkIn.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.hutool.extra.qrcode.QrCodeUtil;
|
||||||
|
import cn.hutool.extra.qrcode.QrConfig;
|
||||||
import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
|
import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
|
||||||
import cn.novalon.gym.manage.checkIn.constant.QRRedisKey;
|
import cn.novalon.gym.manage.checkIn.constant.QRRedisKey;
|
||||||
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
|
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
|
||||||
@@ -19,8 +21,6 @@ import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
|||||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||||
import cn.hutool.extra.qrcode.QrCodeUtil;
|
|
||||||
import cn.hutool.extra.qrcode.QrConfig;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -84,7 +84,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
public Mono<String> checkIn(Long memberId, String qrContent) {
|
public Mono<String> checkIn(Long memberId, String qrContent) {
|
||||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
|
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
|
||||||
|
|
||||||
// 先检查当天是否已经签到(从数据库查询,作为重复签到的额外保障)
|
|
||||||
return checkTodayAlreadySignedIn(memberId)
|
return checkTodayAlreadySignedIn(memberId)
|
||||||
.flatMap(existingRecord -> {
|
.flatMap(existingRecord -> {
|
||||||
String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER);
|
String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER);
|
||||||
@@ -126,11 +125,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查会员当天是否已经签到
|
|
||||||
* @param memberId 会员ID
|
|
||||||
* @return 如果已签到返回签到记录,否则返回空
|
|
||||||
*/
|
|
||||||
private Mono<SignInRecord> checkTodayAlreadySignedIn(Long memberId) {
|
private Mono<SignInRecord> checkTodayAlreadySignedIn(Long memberId) {
|
||||||
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
|
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
|
||||||
LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX);
|
LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX);
|
||||||
@@ -138,13 +132,9 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay);
|
return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理签到逻辑
|
|
||||||
*/
|
|
||||||
private Mono<String> processCheckIn(Long memberId, Long memberCardRecordId, Map<String, Object> redisMap, String qrContent) {
|
private Mono<String> processCheckIn(Long memberId, Long memberCardRecordId, Map<String, Object> redisMap, String qrContent) {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
// 发送实时进度通知
|
|
||||||
MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡...");
|
MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡...");
|
||||||
|
|
||||||
return memberCardRecordRepository.findById(memberCardRecordId)
|
return memberCardRecordRepository.findById(memberCardRecordId)
|
||||||
@@ -163,10 +153,8 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
return Mono.error(new RuntimeException("会员卡已过期"));
|
return Mono.error(new RuntimeException("会员卡已过期"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送实时进度通知
|
|
||||||
MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息...");
|
MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息...");
|
||||||
|
|
||||||
// 检查是否有需要签到的团课预约
|
|
||||||
return validateBooking(memberId, now)
|
return validateBooking(memberId, now)
|
||||||
.then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId())
|
.then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId())
|
||||||
.switchIfEmpty(Mono.defer(() -> {
|
.switchIfEmpty(Mono.defer(() -> {
|
||||||
@@ -174,7 +162,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
return Mono.error(new RuntimeException("会员卡类型不存在"));
|
return Mono.error(new RuntimeException("会员卡类型不存在"));
|
||||||
}))
|
}))
|
||||||
.flatMap(card -> {
|
.flatMap(card -> {
|
||||||
// 发送实时进度通知
|
|
||||||
MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数...");
|
MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数...");
|
||||||
|
|
||||||
return deductCardUsage(cardRecord, card)
|
return deductCardUsage(cardRecord, card)
|
||||||
@@ -195,9 +182,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证预约信息
|
|
||||||
*/
|
|
||||||
private Mono<Void> validateBooking(Long memberId, LocalDateTime now) {
|
private Mono<Void> validateBooking(Long memberId, LocalDateTime now) {
|
||||||
return groupCourseBookingService.getBookingsByMemberId(memberId)
|
return groupCourseBookingService.getBookingsByMemberId(memberId)
|
||||||
.filter(booking -> {
|
.filter(booking -> {
|
||||||
@@ -229,9 +213,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 扣减会员卡使用次数/金额
|
|
||||||
*/
|
|
||||||
private Mono<MemberCardRecord> deductCardUsage(MemberCardRecord record, MemberCard card) {
|
private Mono<MemberCardRecord> deductCardUsage(MemberCardRecord record, MemberCard card) {
|
||||||
MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());
|
MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
@@ -267,9 +248,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存签到记录
|
|
||||||
*/
|
|
||||||
private Mono<Void> saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) {
|
private Mono<Void> saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) {
|
||||||
SignInRecord record = SignInRecord.builder()
|
SignInRecord record = SignInRecord.builder()
|
||||||
.memberId(memberId)
|
.memberId(memberId)
|
||||||
@@ -284,9 +262,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
return signInRecordRepository.save(record).then();
|
return signInRecordRepository.save(record).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建成功响应
|
|
||||||
*/
|
|
||||||
private String buildSuccessResponse(LocalDateTime dateTime) {
|
private String buildSuccessResponse(LocalDateTime dateTime) {
|
||||||
Map<String, Object> res = new HashMap<>();
|
Map<String, Object> res = new HashMap<>();
|
||||||
res.put("message", "签到成功");
|
res.put("message", "签到成功");
|
||||||
@@ -294,9 +269,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
return JSONUtil.toJsonStr(res);
|
return JSONUtil.toJsonStr(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找会员的有效会员卡(优先选择有效期最早到期的)
|
|
||||||
*/
|
|
||||||
private Mono<MemberCardRecord> findValidMemberCard(Long memberId) {
|
private Mono<MemberCardRecord> findValidMemberCard(Long memberId) {
|
||||||
return memberCardRecordRepository.findActiveCardsByMemberId(memberId)
|
return memberCardRecordRepository.findActiveCardsByMemberId(memberId)
|
||||||
.filter(record -> {
|
.filter(record -> {
|
||||||
@@ -314,8 +286,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
.next();
|
.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 签到记录管理功能 ====================
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<SignInRecordVO> getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) {
|
public Flux<SignInRecordVO> getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) {
|
||||||
LocalDateTime start = startTime.atStartOfDay();
|
LocalDateTime start = startTime.atStartOfDay();
|
||||||
@@ -413,9 +383,6 @@ public class CheckServiceImpl implements ICheckInService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换实体到VO
|
|
||||||
*/
|
|
||||||
private SignInRecordVO convertToVO(SignInRecord record) {
|
private SignInRecordVO convertToVO(SignInRecord record) {
|
||||||
SignInRecordVO vo = new SignInRecordVO();
|
SignInRecordVO vo = new SignInRecordVO();
|
||||||
vo.setId(record.getId());
|
vo.setId(record.getId());
|
||||||
|
|||||||
+1
-2
@@ -77,7 +77,7 @@ public class MyWebSocketHandler implements WebSocketHandler {
|
|||||||
.doOnError(e -> {
|
.doOnError(e -> {
|
||||||
log.error("接收消息出错,sessionId={}", sessionId, e);
|
log.error("接收消息出错,sessionId={}", sessionId, e);
|
||||||
})
|
})
|
||||||
.subscribe(); // 必须订阅,否则不会执行
|
.subscribe();
|
||||||
|
|
||||||
// 发送流给客户端
|
// 发送流给客户端
|
||||||
return session.send(sink.asFlux().map(session::textMessage))
|
return session.send(sink.asFlux().map(session::textMessage))
|
||||||
@@ -100,7 +100,6 @@ public class MyWebSocketHandler implements WebSocketHandler {
|
|||||||
* @return 是否发送成功
|
* @return 是否发送成功
|
||||||
*/
|
*/
|
||||||
public static boolean sendMessageToClient(String qrContent, String message) {
|
public static boolean sendMessageToClient(String qrContent, String message) {
|
||||||
// 先清理超时连接
|
|
||||||
cleanupTimeoutConnections();
|
cleanupTimeoutConnections();
|
||||||
|
|
||||||
Sinks.Many<String> sink = qrContentToSink.get(qrContent);
|
Sinks.Many<String> sink = qrContentToSink.get(qrContent);
|
||||||
|
|||||||
-1
@@ -25,7 +25,6 @@ import reactor.test.StepVerifier;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,6 @@
|
|||||||
package cn.novalon.gym.manage.member.service.impl;
|
package cn.novalon.gym.manage.member.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||||
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
|
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
|
||||||
@@ -15,7 +16,6 @@ import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
|||||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||||
import cn.novalon.gym.manage.member.service.IMemberCardService;
|
import cn.novalon.gym.manage.member.service.IMemberCardService;
|
||||||
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
|
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
|
||||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|||||||
+3
@@ -61,6 +61,7 @@ public final class RedisKeyConstants {
|
|||||||
* appType: miniapp(小程序), mp(公众号)
|
* appType: miniapp(小程序), mp(公众号)
|
||||||
*/
|
*/
|
||||||
public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:";
|
public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:";
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
// ==================== 认证模块 ====================
|
// ==================== 认证模块 ====================
|
||||||
|
|
||||||
@@ -70,4 +71,6 @@ public final class RedisKeyConstants {
|
|||||||
* 用途:存储登录/注册等场景的短信验证码
|
* 用途:存储登录/注册等场景的短信验证码
|
||||||
*/
|
*/
|
||||||
public static final String SMS_CODE = "sms:code:";
|
public static final String SMS_CODE = "sms:code:";
|
||||||
|
=======
|
||||||
|
>>>>>>> 08cf82a (签到模块)
|
||||||
}
|
}
|
||||||
-1
@@ -66,7 +66,6 @@ public class SecurityConfig {
|
|||||||
.pathMatchers("/api/payment/notify").permitAll()
|
.pathMatchers("/api/payment/notify").permitAll()
|
||||||
.pathMatchers("/api/payment/refund").permitAll();
|
.pathMatchers("/api/payment/refund").permitAll();
|
||||||
|
|
||||||
|
|
||||||
if (isDevOrTest) {
|
if (isDevOrTest) {
|
||||||
spec.pathMatchers("/swagger-ui.html").permitAll()
|
spec.pathMatchers("/swagger-ui.html").permitAll()
|
||||||
.pathMatchers("/swagger-ui/**").permitAll()
|
.pathMatchers("/swagger-ui/**").permitAll()
|
||||||
|
|||||||
Reference in New Issue
Block a user