From 08cf82ac83b0f1e15fad3cd40d985f425b5d64c9 Mon Sep 17 00:00:00 2001 From: future <1360317836@qq.com> Date: Tue, 2 Jun 2026 09:56:37 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=BE=E5=88=B0=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gym-manage-api/gym-checkIn/.gitignore | 48 ++++ gym-manage-api/gym-checkIn/pom.xml | 242 ++++++++++++++++++ .../manage/checkIn/config/QRCodeConfig.java | 46 ++++ .../checkIn/config/WebSocketConfig.java | 43 ++++ .../manage/checkIn/constant/QRRedisKey.java | 43 ++++ .../gym/manage/checkIn/dto/QRCodeDto.java | 13 + .../manage/checkIn/entity/SignInRecord.java | 41 +++ .../checkIn/handler/CheckInHandler.java | 69 +++++ .../checkIn/service/ICheckInService.java | 11 + .../service/impl/CheckServiceImpl.java | 97 +++++++ .../gym/manage/checkIn/vo/QRCodeVo.java | 17 ++ .../checkIn/websocket/MyWebSocketHandler.java | 98 +++++++ ...ot.autoconfigure.AutoConfiguration.imports | 0 .../src/main/resources/checkIn-config.yml | 10 + .../gym/manage/checkin/CheckInModuleTest.java | 12 + .../src/test/resources/application-test.yml | 1 + .../impl/MemberCardRecordServiceImpl.java | 2 +- .../service/impl/MemberCardServiceImpl.java | 3 +- .../service/impl/MemberServiceImpl.java | 2 +- .../impl/RefundApplicationServiceImpl.java | 2 +- .../service/impl/WechatApiServiceImpl.java | 2 +- .../service/impl/WechatAuthServiceImpl.java | 2 +- .../impl/WechatOfficialServiceImpl.java | 2 +- gym-manage-api/manage-app/pom.xml | 5 + .../gym/manage/app/config/SystemRouter.java | 9 +- gym-manage-api/manage-common/pom.xml | 4 + .../manage/common}/config/RedisConfig.java | 2 +- .../common/constant/RedisKeyConstants.java | 64 +++++ .../gym/manage/common}/util/RedisUtil.java | 2 +- .../filter/JwtAuthenticationFilter.java | 3 +- .../filter/RbacAuthorizationFilter.java | 5 +- .../gym/manage/sys/config/SecurityConfig.java | 1 + gym-manage-api/pom.xml | 1 + 33 files changed, 888 insertions(+), 14 deletions(-) create mode 100644 gym-manage-api/gym-checkIn/.gitignore create mode 100644 gym-manage-api/gym-checkIn/pom.xml create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/QRCodeConfig.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/constant/QRRedisKey.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/dto/QRCodeDto.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/vo/QRCodeVo.java create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java create mode 100644 gym-manage-api/gym-checkIn/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 gym-manage-api/gym-checkIn/src/main/resources/checkIn-config.yml create mode 100644 gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java create mode 100644 gym-manage-api/gym-checkIn/src/test/resources/application-test.yml rename gym-manage-api/{gym-member/src/main/java/cn/novalon/gym/manage/member => manage-common/src/main/java/cn/novalon/gym/manage/common}/config/RedisConfig.java (96%) create mode 100644 gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java rename gym-manage-api/{gym-member/src/main/java/cn/novalon/gym/manage/member => manage-common/src/main/java/cn/novalon/gym/manage/common}/util/RedisUtil.java (97%) diff --git a/gym-manage-api/gym-checkIn/.gitignore b/gym-manage-api/gym-checkIn/.gitignore new file mode 100644 index 0000000..8220137 --- /dev/null +++ b/gym-manage-api/gym-checkIn/.gitignore @@ -0,0 +1,48 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Virtual machine crash logs +hs_err_pid* +replay_pid* + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# IDE +.idea/ +*.iml +.vscode/ +.settings/ +.classpath +.project + +# OS +.DS_Store +Thumbs.db diff --git a/gym-manage-api/gym-checkIn/pom.xml b/gym-manage-api/gym-checkIn/pom.xml new file mode 100644 index 0000000..8e29b7e --- /dev/null +++ b/gym-manage-api/gym-checkIn/pom.xml @@ -0,0 +1,242 @@ + + + 4.0.0 + + + cn.novalon.gym.manage + gym-manage-api + 1.0.0 + + + gym-checkIn + jar + + Gym CheckIn + Check-In Management Module - Member Attendance Services + + + + cn.novalon.gym.manage + manage-common + ${project.version} + + + cn.novalon.gym.manage + manage-db + ${project.version} + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-aop + + + org.springdoc + springdoc-openapi-starter-webflux-ui + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.data + spring-data-commons + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + io.projectreactor + reactor-test + test + + + io.github.resilience4j + resilience4j-spring-boot3 + + + io.github.resilience4j + resilience4j-reactor + + + org.testcontainers + testcontainers + 1.21.4 + test + + + org.testcontainers + postgresql + 1.21.4 + test + + + org.testcontainers + junit-jupiter + 1.21.4 + test + + + com.h2database + h2 + test + + + io.r2dbc + r2dbc-h2 + test + + + org.postgresql + r2dbc-postgresql + test + + + cn.hutool + hutool-all + 5.8.25 + + + com.google.zxing + core + 3.5.1 + + + + + com.google.zxing + javase + 3.5.1 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + default-jar + package + + jar + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + prepare-agent + + prepare-agent + + + + report + verify + + report + + + + check + verify + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.60 + + + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.0 + + + com.github.spotbugs + spotbugs + 4.8.6 + + + + + spotbugs-check + verify + + check + + + + + Max + High + true + spotbugs-exclude.xml + + + + + diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/QRCodeConfig.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/QRCodeConfig.java new file mode 100644 index 0000000..dd44c33 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/QRCodeConfig.java @@ -0,0 +1,46 @@ +package cn.novalon.gym.manage.checkIn.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "qr.config") +public class QRCodeConfig { + + /** + * 二维码宽度(像素) + */ + private Integer width = 300; + + /** + * 二维码高度(像素) + */ + private Integer height = 300; + + /** + * 白边宽度 + */ + private Integer margin = 1; + + /** + * 容错率:L, M, Q, H + */ + private String errorCorrection = "M"; + + /** + * 图片格式:png, jpg + */ + private String format = "png"; + + /** + * 是否启用Logo + */ + private Boolean logoEnabled = false; + + /** + * Logo路径 + */ + private String logoPath = ""; +} \ No newline at end of file diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java new file mode 100644 index 0000000..e4d6ea1 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/constant/QRRedisKey.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/constant/QRRedisKey.java new file mode 100644 index 0000000..8baa890 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/constant/QRRedisKey.java @@ -0,0 +1,43 @@ +package cn.novalon.gym.manage.checkIn.constant; + +import java.time.LocalDate; +import java.util.UUID; + +/** + * 打卡模块 Redis 键常量 + * + * @author 付嘉 + * @date 2026-05-30 + */ +public final class QRRedisKey { + + private static final String SEPARATOR = ":"; + private static final String QRCODE_USER_DAILY = "qrcode:user:daily"; + private static final String QRCODE_CONTENT = "QR_"; + private QRRedisKey() { + // 私有构造,防止实例化 + } + + /** + * 用户当日二维码 + * 格式:qrcode:user:daily:{userId}:{date} + * 示例:qrcode:user:daily:1001:2026-05-30 + */ + public static String qrcodeUserDaily(Long userId, LocalDate date) { + return QRCODE_USER_DAILY + SEPARATOR + userId + SEPARATOR + date; + } + + /** + * 用户当日二维码(今天) + */ + public static String qrcodeUserToday(Long userId) { + return qrcodeUserDaily(userId, LocalDate.now()); + } + + /** + * 生成二维码内容(每个用户每次调用都不同) + */ + public static String generateQrcodeContent() { + return QRCODE_CONTENT + UUID.randomUUID().toString().replace("-", ""); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/dto/QRCodeDto.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/dto/QRCodeDto.java new file mode 100644 index 0000000..4ae9b80 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/dto/QRCodeDto.java @@ -0,0 +1,13 @@ +package cn.novalon.gym.manage.checkIn.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class QRCodeDto { + + private String qrContent; + + private boolean isUsed; +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java new file mode 100644 index 0000000..db15196 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java @@ -0,0 +1,41 @@ +package cn.novalon.gym.manage.checkIn.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("sign_in_record") +public class SignInRecord { + + @Id + private Long id; + + // 会员ID + @Column("member_id") + private Long memberId; + + // 签到日期 + @Column("sign_in_date") + private LocalDate signInDate; + + // 签到时间 + @Column("sign_in_time") + private LocalDateTime signInTime; + + // 创建时间 + @CreatedDate + @Column("created_at") + private LocalDateTime createdAt; +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java new file mode 100644 index 0000000..324a9b9 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java @@ -0,0 +1,69 @@ +package cn.novalon.gym.manage.checkIn.handler; + +import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl; +import cn.novalon.gym.manage.sys.util.AuthUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CheckInHandler { + + private final AuthUtil authUtil; + private final CheckServiceImpl checkService; + + /** + * 签到 + * + * POST /api/checkIn + * + */ + public Mono checkIn(ServerRequest request) { + + Long memberId = 1L; +// authUtil.getMemberIdOrThrow(request); + return request.bodyToMono(Map.class) + .flatMap(body -> { + String qrContent = (String) body.get("qrContent"); + log.info("收到签到请求, memberId: {}, qrContent: {}", memberId, qrContent); + + return checkService.checkIn(memberId, qrContent) + .flatMap(result -> ServerResponse.ok() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Map.of("code", 200, "message", "签到成功"))); + }) + .onErrorResume(e -> { + log.error("签到失败", e); + return ServerResponse.status(HttpStatus.BAD_REQUEST) + .bodyValue(Map.of("code", 400, "message", e.getMessage())); + }); + } + + /** + * 获取二维码 + * + * GET /api/checkin/qrcode + * + */ + public Mono getQRCode(ServerRequest request) { + + Long memberId = 1L; +// authUtil.getMemberIdOrThrow(request); + + log.info("收到用户{}获取二维码请求", memberId); + + return checkService.getQRCode(memberId) + .flatMap(qrCodeVo -> ServerResponse.ok() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(qrCodeVo)); + } +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java new file mode 100644 index 0000000..9b37980 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java @@ -0,0 +1,11 @@ +package cn.novalon.gym.manage.checkIn.service; + +import cn.novalon.gym.manage.checkIn.vo.QRCodeVo; +import reactor.core.publisher.Mono; + +public interface ICheckInService { + + Mono getQRCode(Long memberId); + + Mono checkIn(Long memberId, String qrContent); +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java new file mode 100644 index 0000000..fbdd4f0 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java @@ -0,0 +1,97 @@ +package cn.novalon.gym.manage.checkIn.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +import cn.hutool.json.JSONUtil; +import cn.novalon.gym.manage.checkIn.config.QRCodeConfig; +import cn.novalon.gym.manage.checkIn.constant.QRRedisKey; +import cn.novalon.gym.manage.checkIn.service.ICheckInService; +import cn.novalon.gym.manage.checkIn.vo.QRCodeVo; +import cn.novalon.gym.manage.common.constant.RedisKeyConstants; +import cn.novalon.gym.manage.common.util.RedisUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CheckServiceImpl implements ICheckInService { + + @Autowired + private final QRCodeConfig qrCodeConfig; + + private final RedisUtil redisUtil; + + @Override + public Mono getQRCode(Long memberId) { + log.info("开始查询会员信息"); + // TODO: 获取会员信息 - 查会员卡有效期/剩余次数,过期返回,先查缓存,缓存不存在则查数据库 + // if (member有效期过了) throw new RuntimeException("会员有效期已过,拒绝生成二维码"); + log.info("会员信息查询完成"); + + log.info("开始生成二维码"); + String qrContent = QRRedisKey.generateQrcodeContent(); + Map redisMap = new HashMap<>(); + redisMap.put("qrContent", qrContent); + redisMap.put("isUsed", false); + + return redisUtil.setWithExpire( + RedisKeyConstants.QRCODE_USER_DAILY+memberId+LocalDate.now(), + redisMap, + getSecondsUntilEndOfDay() + ) + .then(Mono.fromSupplier(() -> { + String qrCodeBase64 = QrCodeUtil.generateAsBase64(qrContent, + BeanUtil.copyProperties(qrCodeConfig, QrConfig.class), "png"); + return new QRCodeVo(qrCodeBase64,false,qrCodeConfig.getWidth(),qrCodeConfig.getHeight()); + })); + } + + @Override + public Mono checkIn(Long memberId, String qrContent) { + String key = RedisKeyConstants.QRCODE_USER_DAILY+memberId+LocalDate.now(); + + return redisUtil.get(key) + .flatMap(cachedQrContent -> { + if (cachedQrContent != null) { + // 匹配成功,执行签到逻辑 + Map map = JSONUtil.parseObj(cachedQrContent); + if(map.get("qrContent").equals(qrContent)){ + if((boolean)map.get("isUsed")){ + log.error("重复签到"); + throw new RuntimeException("您已经在"+map.get("checkInTime")+"完成签到,请勿重复签到"); + } + log.info("二维码匹配成功,memberId: {}", memberId); + // TODO查会员卡缓存,按照卡有效期进行扣减次数,没有缓存查数据库 + map.put("isUsed", true); + map.put("checkInTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + + return redisUtil.set(key,map). + then(Mono.just("签到成功")); + } + } + throw new RuntimeException("二维码无效"); + }) + .switchIfEmpty(Mono.error(new RuntimeException("二维码已过期或不存在"))); + } + + private long getSecondsUntilEndOfDay() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59); + + if (now.isAfter(endOfDay)) return 1; + + return ChronoUnit.SECONDS.between(now, endOfDay); + } +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/vo/QRCodeVo.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/vo/QRCodeVo.java new file mode 100644 index 0000000..13e329e --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/vo/QRCodeVo.java @@ -0,0 +1,17 @@ +package cn.novalon.gym.manage.checkIn.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class QRCodeVo { + + private String qrCodeBase64; + + private boolean isUsed; + + private Integer width; + + private Integer height; +} diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java new file mode 100644 index 0000000..c786050 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java @@ -0,0 +1,98 @@ +package cn.novalon.gym.manage.checkIn.websocket; + +import cn.hutool.json.JSONUtil; +import cn.novalon.gym.manage.checkIn.dto.QRCodeDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.WebSocketMessage; +import org.springframework.web.reactive.socket.WebSocketSession; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class MyWebSocketHandler implements WebSocketHandler { + + // 存储所有连接 + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public Mono handle(WebSocketSession session) { + String sessionId = session.getId(); + + // 连接建立 + sessions.put(sessionId, session); + log.info("WebSocket 连接建立,sessionId:{},当前连接数:{}", sessionId, sessions.size()); + + // 处理接收到的消息 + Flux output = session.receive() + .doOnNext(message -> { + String payload = message.getPayloadAsText(); + log.info("收到消息:{}", payload); + }) + .map(message -> { + String payload = message.getPayloadAsText(); + String response = processMessage(payload, sessionId); + return session.textMessage(response); + }); + + // 连接关闭时清理 + return session.send(output) + .doFinally(signalType -> { + sessions.remove(sessionId); + log.info("WebSocket 连接关闭,sessionId:{},剩余连接数:{}", sessionId, sessions.size()); + }); + } + + /** + * 处理消息逻辑 + */ + private String processMessage(String message, String sessionId) { + try { + // 解析 QRCodeDto + QRCodeDto qrCodeDto = JSONUtil.toBean(message, QRCodeDto.class); + + String response; + + // 判断二维码是否有效 + if (qrCodeDto.getQrContent() != null + && !qrCodeDto.getQrContent().isEmpty() + && !qrCodeDto.isUsed()) { + // 有效:qrContent 有值且 isUsed 为 false + response = "正在进行签到"; + + // 可选:将二维码标记为已使用(需要调用后端服务) + // checkInService.handleCheckIn(qrCodeDto.getQrContent()); + + log.info("二维码有效,sessionId:{},qrContent:{}", sessionId, qrCodeDto.getQrContent()); + } else { + // 无效:qrContent 为空 或 isUsed 为 true + String reason = ""; + if (qrCodeDto.getQrContent() == null || qrCodeDto.getQrContent().isEmpty()) { + reason = "二维码内容为空"; + } else if (qrCodeDto.isUsed()) { + reason = "二维码已被使用"; + } + response = "二维码无效:" + reason; + log.warn("二维码无效,sessionId:{},原因:{}", sessionId, reason); + } + + return response; + + } catch (Exception e) { + log.error("解析消息失败,sessionId:{}", sessionId, e); + return "消息格式错误"; + } + } + + /** + * 获取当前在线连接数 + */ + public static int getOnlineCount() { + return sessions.size(); + } +} \ No newline at end of file diff --git a/gym-manage-api/gym-checkIn/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/gym-checkIn/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..e69de29 diff --git a/gym-manage-api/gym-checkIn/src/main/resources/checkIn-config.yml b/gym-manage-api/gym-checkIn/src/main/resources/checkIn-config.yml new file mode 100644 index 0000000..b1eb6d9 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/main/resources/checkIn-config.yml @@ -0,0 +1,10 @@ +# 二维码配置 +qr: + config: + width: 300 # 二维码宽度(像素) + height: 300 # 二维码高度(像素) + margin: 1 # 白边宽度(像素) + format: png # 图片格式:png / jpg + error-correction: L #容错率:L, M, Q, H,如果启用Logo(logo-enabled: true),必须设置为 H + logo-enabled: false # 是否启用Logo(启用时error-correction必须为H) +# logo-path: static/logo.png # Logo图片路径(支持相对路径或绝对路径) \ No newline at end of file diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java new file mode 100644 index 0000000..bed8bb0 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java @@ -0,0 +1,12 @@ +package cn.novalon.gym.manage.checkin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class CheckInModuleTest { + + @Test + public void contextLoads() { + } +} diff --git a/gym-manage-api/gym-checkIn/src/test/resources/application-test.yml b/gym-manage-api/gym-checkIn/src/test/resources/application-test.yml new file mode 100644 index 0000000..ffee846 --- /dev/null +++ b/gym-manage-api/gym-checkIn/src/test/resources/application-test.yml @@ -0,0 +1 @@ +# Test Configuration diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java index 0cbbd76..ae20bd4 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java @@ -3,7 +3,7 @@ package cn.novalon.gym.manage.member.service.impl; import cn.novalon.gym.manage.member.entity.MemberCardRecord; import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository; import cn.novalon.gym.manage.member.service.IMemberCardRecordService; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java index 87e345b..8b50822 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java @@ -1,5 +1,6 @@ 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.MemberCardRecord; import cn.novalon.gym.manage.member.entity.MemberCardTransaction; @@ -15,7 +16,7 @@ import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository; import cn.novalon.gym.manage.member.repository.MemberCardRepository; import cn.novalon.gym.manage.member.service.IMemberCardService; import cn.novalon.gym.manage.member.service.IMemberCardTransactionService; -import cn.novalon.gym.manage.member.util.RedisUtil; + import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java index cecdaed..86f6110 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java @@ -17,7 +17,7 @@ import cn.novalon.gym.manage.member.service.MemberService; import cn.novalon.gym.manage.member.util.AesUtil; import cn.novalon.gym.manage.member.util.BeanConvertUtil; import cn.novalon.gym.manage.member.util.EsSyncUtils; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import cn.novalon.gym.manage.member.vo.MemberCardInfoVO; import cn.novalon.gym.manage.member.vo.MemberDetailVO; import cn.novalon.gym.manage.member.vo.MemberInfoVO; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java index 0e9da72..e505ef7 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java @@ -5,7 +5,7 @@ import cn.novalon.gym.manage.member.entity.RefundApplication; import cn.novalon.gym.manage.member.enums.RefundStatus; import cn.novalon.gym.manage.member.repository.RefundApplicationRepository; import cn.novalon.gym.manage.member.service.IRefundApplicationService; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java index aac7753..99238ec 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java @@ -4,7 +4,7 @@ import cn.novalon.gym.manage.common.exception.ErrorCode; import cn.novalon.gym.manage.common.exception.SystemException; import cn.novalon.gym.manage.member.config.WechatProperties; import cn.novalon.gym.manage.member.service.WechatApiService; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java index a462a14..b1eec0b 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java @@ -16,7 +16,7 @@ import cn.novalon.gym.manage.member.service.WechatAuthService; import cn.novalon.gym.manage.member.util.AesUtil; import cn.novalon.gym.manage.member.util.EsSyncUtils; import cn.novalon.gym.manage.member.util.MemberNoGenerator; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import cn.novalon.gym.manage.member.util.WechatPhoneUtil; import cn.novalon.gym.manage.member.vo.WechatLoginVO; import cn.novalon.gym.manage.sys.security.JwtTokenProvider; diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java index 22a2d56..41aca11 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java @@ -8,7 +8,7 @@ import cn.novalon.gym.manage.member.es.repository.MemberESRepository; import cn.novalon.gym.manage.member.repository.IMemberRepository; import cn.novalon.gym.manage.member.service.WechatOfficialService; import cn.novalon.gym.manage.member.util.EsSyncUtils; -import cn.novalon.gym.manage.member.util.RedisUtil; +import cn.novalon.gym.manage.common.util.RedisUtil; import cn.novalon.gym.manage.member.vo.WechatUserInfoVO; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml index 2644308..7d83bcb 100644 --- a/gym-manage-api/manage-app/pom.xml +++ b/gym-manage-api/manage-app/pom.xml @@ -43,6 +43,11 @@ gym-member ${project.version} + + cn.novalon.gym.manage + gym-checkIn + ${project.version} + org.springframework.boot diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java index 996c1d1..8a7d9a2 100644 --- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java +++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java @@ -1,6 +1,7 @@ package cn.novalon.gym.manage.app.config; +import cn.novalon.gym.manage.checkIn.handler.CheckInHandler; import cn.novalon.gym.manage.file.handler.SysFileHandler; import cn.novalon.gym.manage.member.handler.MemberCardHandler; import cn.novalon.gym.manage.member.handler.MemberCardRecordHandler; @@ -62,7 +63,8 @@ public class SystemRouter { PasswordDiagnosticHandler passwordDiagnosticHandler, MemberCardHandler memberCardHandler, MemberCardRecordHandler memberCardRecordHandler, - MemberCardTransactionHandler memberCardTransactionHandler) { + MemberCardTransactionHandler memberCardTransactionHandler, + CheckInHandler checkInHandler) { return route() // ========== 诊断路由 ========== @@ -249,7 +251,10 @@ public class SystemRouter { .GET("/api/member-card-transactions/statistics/deduct/{cardId}", memberCardTransactionHandler::getDeductCountByCardId) .GET("/api/member-card-transactions/statistics/renew", memberCardTransactionHandler::getRenewAmountByTimeRange) .GET("/api/member-card-transactions/statistics/purchase/{memberId}", memberCardTransactionHandler::getPurchaseAmountByMember) - + + // ========= 签到路由 ========== + .POST("/api/checkIn", checkInHandler::checkIn ) + .GET("/api/checkIn/qrcode", checkInHandler::getQRCode) .build(); } } diff --git a/gym-manage-api/manage-common/pom.xml b/gym-manage-api/manage-common/pom.xml index 7f51635..e5a81c2 100644 --- a/gym-manage-api/manage-common/pom.xml +++ b/gym-manage-api/manage-common/pom.xml @@ -56,6 +56,10 @@ spring-boot-starter-test test + + org.springframework.data + spring-data-redis + diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/RedisConfig.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/RedisConfig.java similarity index 96% rename from gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/RedisConfig.java rename to gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/RedisConfig.java index 7321879..6b9f23c 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/RedisConfig.java +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/RedisConfig.java @@ -1,4 +1,4 @@ -package cn.novalon.gym.manage.member.config; +package cn.novalon.gym.manage.common.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java new file mode 100644 index 0000000..053180d --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java @@ -0,0 +1,64 @@ +package cn.novalon.gym.manage.common.constant; + +/** + * Redis 缓存 Key 常量类 + * 统一管理项目中所有 Redis 缓存的 key 前缀 + * + * @author auto-generated + * @date 2026-05-30 + */ +public final class RedisKeyConstants { + + private RedisKeyConstants() { + } + + // ==================== 会员模块 ==================== + + /** + * 会员信息缓存 + * 格式:member:info:{memberId} + */ + public static final String MEMBER_INFO = "member:info:"; + + /** + * 会员详情缓存 + * 格式:member:detail:{memberId} + */ + public static final String MEMBER_DETAIL = "member:detail:"; + + /** + * 会员卡类型缓存 + * 格式:member:card:{memberCardId} + */ + public static final String MEMBER_CARD = "member:card:"; + + /** + * 会员卡记录缓存(包含剩余次数/金额) + * 格式:member:card:record:{recordId} + */ + public static final String MEMBER_CARD_RECORD = "member:card:record:"; + + /** + * 会员退款申请缓存 + * 格式:member:refund:{recordId} + */ + public static final String MEMBER_REFUND = "member:refund:"; + + // ==================== 签到模块 ==================== + + /** + * 用户当日二维码缓存 + * 格式:qrcode:user:daily:{userId}:{date} + * 示例:qrcode:user:daily:1:2026-05-30 + */ + public static final String QRCODE_USER_DAILY = "qrcode:user:daily:"; + + // ==================== 微信模块 ==================== + + /** + * 微信 access_token 缓存 + * 格式:wechat:access_token:{appType} + * appType: miniapp(小程序), mp(公众号) + */ + public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:"; +} \ No newline at end of file diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/RedisUtil.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java similarity index 97% rename from gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/RedisUtil.java rename to gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java index ea49f50..0482306 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/RedisUtil.java +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/RedisUtil.java @@ -1,4 +1,4 @@ -package cn.novalon.gym.manage.member.util; +package cn.novalon.gym.manage.common.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.ReactiveRedisTemplate; diff --git a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java index ea4b245..54c4a4e 100644 --- a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java +++ b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java @@ -60,7 +60,8 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactorymanage-notify manage-file gym-member + gym-checkIn