签到模块
This commit is contained in:
@@ -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
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.novalon.gym.manage</groupId>
|
||||||
|
<artifactId>gym-manage-api</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>gym-checkIn</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Gym CheckIn</name>
|
||||||
|
<description>Check-In Management Module - Member Attendance Services</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.novalon.gym.manage</groupId>
|
||||||
|
<artifactId>manage-common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.novalon.gym.manage</groupId>
|
||||||
|
<artifactId>manage-db</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.data</groupId>
|
||||||
|
<artifactId>spring-data-commons</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.resilience4j</groupId>
|
||||||
|
<artifactId>resilience4j-spring-boot3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.resilience4j</groupId>
|
||||||
|
<artifactId>resilience4j-reactor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<version>1.21.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>1.21.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>1.21.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.r2dbc</groupId>
|
||||||
|
<artifactId>r2dbc-h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>r2dbc-postgresql</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 添加 ZXing JavaSE 扩展(用于生成图片) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>javase</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-jar</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>21</source>
|
||||||
|
<target>21</target>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>1.5.5.Final</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<version>0.8.12</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>prepare-agent</id>
|
||||||
|
<goals>
|
||||||
|
<goal>prepare-agent</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>report</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>check</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<element>BUNDLE</element>
|
||||||
|
<limits>
|
||||||
|
<limit>
|
||||||
|
<counter>INSTRUCTION</counter>
|
||||||
|
<value>COVEREDRATIO</value>
|
||||||
|
<minimum>0.60</minimum>
|
||||||
|
</limit>
|
||||||
|
</limits>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
<version>4.8.6.0</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs</artifactId>
|
||||||
|
<version>4.8.6</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>spotbugs-check</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<effort>Max</effort>
|
||||||
|
<threshold>High</threshold>
|
||||||
|
<failOnError>true</failOnError>
|
||||||
|
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
+46
@@ -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 = "";
|
||||||
|
}
|
||||||
+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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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("-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -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;
|
||||||
|
}
|
||||||
+41
@@ -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;
|
||||||
|
}
|
||||||
+69
@@ -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<ServerResponse> 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<ServerResponse> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -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<QRCodeVo> getQRCode(Long memberId);
|
||||||
|
|
||||||
|
Mono<String> checkIn(Long memberId, String qrContent);
|
||||||
|
}
|
||||||
+97
@@ -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<QRCodeVo> getQRCode(Long memberId) {
|
||||||
|
log.info("开始查询会员信息");
|
||||||
|
// TODO: 获取会员信息 - 查会员卡有效期/剩余次数,过期返回,先查缓存,缓存不存在则查数据库
|
||||||
|
// if (member有效期过了) throw new RuntimeException("会员有效期已过,拒绝生成二维码");
|
||||||
|
log.info("会员信息查询完成");
|
||||||
|
|
||||||
|
log.info("开始生成二维码");
|
||||||
|
String qrContent = QRRedisKey.generateQrcodeContent();
|
||||||
|
Map<String, Object> 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<String> checkIn(Long memberId, String qrContent) {
|
||||||
|
String key = RedisKeyConstants.QRCODE_USER_DAILY+memberId+LocalDate.now();
|
||||||
|
|
||||||
|
return redisUtil.get(key)
|
||||||
|
.flatMap(cachedQrContent -> {
|
||||||
|
if (cachedQrContent != null) {
|
||||||
|
// 匹配成功,执行签到逻辑
|
||||||
|
Map<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -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;
|
||||||
|
}
|
||||||
+98
@@ -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<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> handle(WebSocketSession session) {
|
||||||
|
String sessionId = session.getId();
|
||||||
|
|
||||||
|
// 连接建立
|
||||||
|
sessions.put(sessionId, session);
|
||||||
|
log.info("WebSocket 连接建立,sessionId:{},当前连接数:{}", sessionId, sessions.size());
|
||||||
|
|
||||||
|
// 处理接收到的消息
|
||||||
|
Flux<WebSocketMessage> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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图片路径(支持相对路径或绝对路径)
|
||||||
+12
@@ -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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Test Configuration
|
||||||
+1
-1
@@ -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.entity.MemberCardRecord;
|
||||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||||
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
|
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 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;
|
||||||
|
|||||||
+2
-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,7 @@ 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.member.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;
|
||||||
|
|||||||
+1
-1
@@ -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.AesUtil;
|
||||||
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
|
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
|
||||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
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.MemberCardInfoVO;
|
||||||
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
|
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
|
||||||
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
||||||
|
|||||||
+1
-1
@@ -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.enums.RefundStatus;
|
||||||
import cn.novalon.gym.manage.member.repository.RefundApplicationRepository;
|
import cn.novalon.gym.manage.member.repository.RefundApplicationRepository;
|
||||||
import cn.novalon.gym.manage.member.service.IRefundApplicationService;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|||||||
+1
-1
@@ -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.common.exception.SystemException;
|
||||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||||
import cn.novalon.gym.manage.member.service.WechatApiService;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|||||||
+1
-1
@@ -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.AesUtil;
|
||||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||||
import cn.novalon.gym.manage.member.util.MemberNoGenerator;
|
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.util.WechatPhoneUtil;
|
||||||
import cn.novalon.gym.manage.member.vo.WechatLoginVO;
|
import cn.novalon.gym.manage.member.vo.WechatLoginVO;
|
||||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||||
|
|||||||
+1
-1
@@ -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.repository.IMemberRepository;
|
||||||
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
||||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
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 cn.novalon.gym.manage.member.vo.WechatUserInfoVO;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|||||||
@@ -43,6 +43,11 @@
|
|||||||
<artifactId>gym-member</artifactId>
|
<artifactId>gym-member</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.novalon.gym.manage</groupId>
|
||||||
|
<artifactId>gym-checkIn</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,7 @@
|
|||||||
package cn.novalon.gym.manage.app.config;
|
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.file.handler.SysFileHandler;
|
||||||
import cn.novalon.gym.manage.member.handler.MemberCardHandler;
|
import cn.novalon.gym.manage.member.handler.MemberCardHandler;
|
||||||
import cn.novalon.gym.manage.member.handler.MemberCardRecordHandler;
|
import cn.novalon.gym.manage.member.handler.MemberCardRecordHandler;
|
||||||
@@ -62,7 +63,8 @@ public class SystemRouter {
|
|||||||
PasswordDiagnosticHandler passwordDiagnosticHandler,
|
PasswordDiagnosticHandler passwordDiagnosticHandler,
|
||||||
MemberCardHandler memberCardHandler,
|
MemberCardHandler memberCardHandler,
|
||||||
MemberCardRecordHandler memberCardRecordHandler,
|
MemberCardRecordHandler memberCardRecordHandler,
|
||||||
MemberCardTransactionHandler memberCardTransactionHandler) {
|
MemberCardTransactionHandler memberCardTransactionHandler,
|
||||||
|
CheckInHandler checkInHandler) {
|
||||||
|
|
||||||
return route()
|
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/deduct/{cardId}", memberCardTransactionHandler::getDeductCountByCardId)
|
||||||
.GET("/api/member-card-transactions/statistics/renew", memberCardTransactionHandler::getRenewAmountByTimeRange)
|
.GET("/api/member-card-transactions/statistics/renew", memberCardTransactionHandler::getRenewAmountByTimeRange)
|
||||||
.GET("/api/member-card-transactions/statistics/purchase/{memberId}", memberCardTransactionHandler::getPurchaseAmountByMember)
|
.GET("/api/member-card-transactions/statistics/purchase/{memberId}", memberCardTransactionHandler::getPurchaseAmountByMember)
|
||||||
|
|
||||||
|
// ========= 签到路由 ==========
|
||||||
|
.POST("/api/checkIn", checkInHandler::checkIn )
|
||||||
|
.GET("/api/checkIn/qrcode", checkInHandler::getQRCode)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,10 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.data</groupId>
|
||||||
|
<artifactId>spring-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+1
-1
@@ -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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
+64
@@ -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:";
|
||||||
|
}
|
||||||
+1
-1
@@ -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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||||
+2
-1
@@ -60,7 +60,8 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAut
|
|||||||
path.equals("/actuator/health") ||
|
path.equals("/actuator/health") ||
|
||||||
path.equals("/api/member/auth/miniapp/login") ||
|
path.equals("/api/member/auth/miniapp/login") ||
|
||||||
path.equals("/api/member/auth/mp/callback") ||
|
path.equals("/api/member/auth/mp/callback") ||
|
||||||
path.equals("/api/auth/login") ||
|
path.equals("/api/auth/login") ||
|
||||||
|
path.startsWith("/api/checkIn/") ||
|
||||||
path.startsWith("/actuator/info");
|
path.startsWith("/actuator/info");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -61,9 +61,10 @@ public class RbacAuthorizationFilter extends AbstractGatewayFilterFactory<RbacAu
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPublicPath(String path) {
|
private boolean isPublicPath(String path) {
|
||||||
return path.startsWith("/api/auth/") ||
|
return path.startsWith("/api/auth/") ||
|
||||||
path.equals("/actuator/health") ||
|
path.equals("/actuator/health") ||
|
||||||
path.startsWith("/actuator/info");
|
path.startsWith("/actuator/info") ||
|
||||||
|
path.startsWith("/api/checkIn/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Config {
|
public static class Config {
|
||||||
|
|||||||
+1
@@ -50,6 +50,7 @@ public class SecurityConfig {
|
|||||||
spec.pathMatchers("/api/auth/**").permitAll()
|
spec.pathMatchers("/api/auth/**").permitAll()
|
||||||
.pathMatchers("/api/public/**").permitAll()
|
.pathMatchers("/api/public/**").permitAll()
|
||||||
.pathMatchers("/ws/**").permitAll()
|
.pathMatchers("/ws/**").permitAll()
|
||||||
|
.pathMatchers("/**").permitAll()
|
||||||
.pathMatchers("/actuator/**").permitAll();
|
.pathMatchers("/actuator/**").permitAll();
|
||||||
|
|
||||||
if (isDevOrTest) {
|
if (isDevOrTest) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<module>manage-notify</module>
|
<module>manage-notify</module>
|
||||||
<module>manage-file</module>
|
<module>manage-file</module>
|
||||||
<module>gym-member</module>
|
<module>gym-member</module>
|
||||||
|
<module>gym-checkIn</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
|||||||
Reference in New Issue
Block a user