diff --git a/gym-manage-api/KeyAnalyzer.java b/gym-manage-api/KeyAnalyzer.java
deleted file mode 100644
index e460fe7..0000000
--- a/gym-manage-api/KeyAnalyzer.java
+++ /dev/null
@@ -1,202 +0,0 @@
-import java.util.Base64;
-import java.math.BigInteger;
-import java.io.ByteArrayInputStream;
-
-public class KeyAnalyzer {
- public static void main(String[] args) {
- String privateKeyBase64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxAwrhZF+izz1gxQKOr4jnC05obIBHbl0DzHOcd4CaaDV7Kv1NRJKi33JhdDsW4JGgu16e5Rtzq1VzU5VWz5EKgGL46maOFwCkngJUTP/LC3JVf/wtJmYCm8xNE8C7GNNIqKzYEvUOEfXqagpVvVrGsQ9FpzFp2rP9hBmHY3yizVyPX/uT9S+af5TRxiaItj3SSJGgloaEMrnKOpb/EH7JwPSS0liAyT/NxPfOyyZHc22AvaAIOE5y+0PMUIKPuIdfpOrej3LVpO1Arc2hSgmdB+YIPSiBVYPXa6AuAmil9mpbtSikQJ7Uu7lX4JyTW4QxQ06rPFKnFWVKkzivAElAgMBAAECggEAJd0AJ37iTlMpDQ90xqe7hvRQxAu256gbQ9nrqLY97g0/KIw6WEZSPakFX6gvdvb/NzKmUyAIEKGLoh6tXdZk6qfOqc/6BeK47nIcBfwT9/zerjNUVvn34w4aHyNINieMMHQ+Id8PUZmqWH+Euz9ilVTosuyEPwUZulLvUQqwXzU5VnwVghURbUhDd+ecBJACWgemRun6d5241PQXNYAdH1k7cETd8GfIi3qclhhJrxi7tu5tq4YGCXQIoz7HCLim7GIvT0M+FRgSw2EOrHnAQNFeQ/vQbP71ttLoTxehL6Se9dfWrV5OI+Y/T7vR2F84Qt0iNbaxyJGir7siKDFIwQKBgQDXDzinx3/TasplM78pR/0CtuuKr1Ch02LOPrTosJ1qf1OohxQThowhOTxMsBlgYSKu9s1QRffUUXEqYXxd2B6lzDKfggwO6U2XxIcxWeNow0xoFfqcXYSg7Ga2sCr9uhdwxIdFQNF7SNBpT8ht4fJrRX6mWY1nHybpyTDQ4xoQNQKBgQC6m+yiOoi5JD3zVSSJq/iq5DJPA5B4aoP+t5u9lp2Q7iVO0QI5ilBlEKGE4VOU0glnXlDTfuqEYooMY85ekl4WGb3AOT0PhLL2i+gO2nlWBzf4HPzB/hibjfyPyniRM03cHkG3HXucL7Sne6FwERcfjEqjUd2cdP1l89PNrq4rMQKBgQCMmjABSWYh8/y1I5rEQ4OAJdVjC3GdC1Xa35ZpVybjvLEWSpHunhW5lvD8dllw8LC7UTI0XDpGPqTM/4VO2YBYB2PFc0Gs8g0/v0ZgFpOeJ6kpl80MM/wFNemFYTIKRoMSv/psZY9PmfBgGcBBTuquBXZjDcNr+yr2yAm5V/DvTQKBgHqRi94KoF8q5N39IKCkqhJlDH5FkxDktYoKw2rFkPzuzuZz9gghRyj6wXxsG9/2DWMt2dzw0czehFoa/CO188KEadPmRKr6uCmkP2nyKhxNZX+8WnB5G2Sg4DD6BjMpBYz8+qDx5ozx8LDJTYI0V4HLPgMD9JGdbgsXGhlREOkhAoGALr6IQOXnviWNAhCdc7rrsaMLMPbLZ1wqzWtQUG1JxDobbpzEP4CW/mvW5pMn58mSBg5qbXhyDI4fFP0CPb98QIz2tGnIzYyFzdKmF5Z1N7X1OF9O+tsSqASoBZzTqB4fr/o4mz0s9JCeriBR2LWjsbsDU13DTLsfQpWbtOnIy70=";
-
- System.out.println("========== 密钥分析开始 ==========");
- System.out.println("密钥长度: " + privateKeyBase64.length());
-
- byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
- System.out.println("解码后长度: " + keyBytes.length + " bytes");
-
- // 打印前30字节
- System.out.print("前30字节(hex): ");
- for (int i = 0; i < 30 && i < keyBytes.length; i++) {
- System.out.printf("%02X ", keyBytes[i]);
- }
- System.out.println();
-
- // 解析ASN.1结构
- try {
- parseASN1(keyBytes);
- } catch (Exception e) {
- System.out.println("解析失败: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- static void parseASN1(byte[] keyBytes) throws Exception {
- ByteArrayInputStream bis = new ByteArrayInputStream(keyBytes);
-
- // 读取SEQUENCE
- int tag = bis.read();
- System.out.println("第一个tag: 0x" + Integer.toHexString(tag));
-
- if (tag == 0x30) {
- System.out.println("这是SEQUENCE");
-
- // 读取长度
- int len = readASN1Length(bis);
- System.out.println("SEQUENCE长度: " + len);
-
- // 检查下一个tag
- int nextTag = bis.read();
- System.out.println("下一个tag: 0x" + Integer.toHexString(nextTag));
-
- if (nextTag == 0x02) {
- // INTEGER
- int intLen = readASN1Length(bis);
- byte[] versionBytes = new byte[intLen];
- bis.read(versionBytes);
- int version = new BigInteger(versionBytes).intValue();
- System.out.println("版本INTEGER值: " + version);
-
- // 检查这是PKCS#8还是PKCS#1
- // PKCS#8: version=0后是SEQUENCE(算法标识符)
- // PKCS#1: version=0后是INTEGER(modulus)
- int afterVersionTag = bis.read();
- System.out.println("版本后的tag: 0x" + Integer.toHexString(afterVersionTag));
-
- if (afterVersionTag == 0x30) {
- System.out.println(">>> 这是PKCS#8格式 (version后是SEQUENCE)");
-
- // 解析PKCS#8
- // AlgorithmIdentifier: SEQUENCE { OID, NULL }
- int algLen = readASN1Length(bis);
- System.out.println("AlgorithmIdentifier长度: " + algLen);
-
- // 跳过AlgorithmIdentifier内容
- byte[] algBytes = new byte[algLen];
- bis.read(algBytes);
-
- // 打印OID
- System.out.print("OID bytes: ");
- for (int i = 0; i < algBytes.length; i++) {
- System.out.printf("%02X ", algBytes[i]);
- }
- System.out.println();
-
- // 下一个应该是OCTET STRING (包含私钥)
- int octetTag = bis.read();
- System.out.println("下一个tag: 0x" + Integer.toHexString(octetTag));
-
- if (octetTag == 0x04) {
- System.out.println("这是OCTET STRING (包含私钥数据)");
- int octetLen = readASN1Length(bis);
- System.out.println("OCTET STRING长度: " + octetLen);
-
- // OCTET STRING内容是PKCS#1私钥
- byte[] pkcs1Bytes = new byte[octetLen];
- bis.read(pkcs1Bytes);
-
- System.out.print("PKCS#1私钥前20字节: ");
- for (int i = 0; i < 20; i++) {
- System.out.printf("%02X ", pkcs1Bytes[i]);
- }
- System.out.println();
-
- // 解析PKCS#1私钥
- ByteArrayInputStream pkcs1Stream = new ByteArrayInputStream(pkcs1Bytes);
- int pkcs1Tag = pkcs1Stream.read();
- System.out.println("PKCS#1第一个tag: 0x" + Integer.toHexString(pkcs1Tag));
-
- if (pkcs1Tag == 0x30) {
- int pkcs1Len = readASN1Length(pkcs1Stream);
- System.out.println("PKCS#1 SEQUENCE长度: " + pkcs1Len);
-
- // version
- int vTag = pkcs1Stream.read();
- int vLen = readASN1Length(pkcs1Stream);
- byte[] vBytes = new byte[vLen];
- pkcs1Stream.read(vBytes);
- System.out.println("PKCS#1版本: " + new BigInteger(vBytes));
-
- // 解析私钥参数
- parsePKCS1(pkcs1Stream);
- }
- }
- } else if (afterVersionTag == 0x02) {
- System.out.println(">>> 这是PKCS#1格式 (version后是INTEGER)");
-
- // 继续解析PKCS#1
- parsePKCS1(bis);
- } else {
- System.out.println(">>> 未知格式 (tag: 0x" + Integer.toHexString(afterVersionTag) + ")");
- }
- }
- }
- }
-
- static void parsePKCS1(ByteArrayInputStream bis) throws Exception {
- System.out.println("\n========== 解析PKCS#1私钥参数 ==========");
-
- // modulus
- BigInteger modulus = readASN1Integer(bis);
- System.out.println("modulus长度: " + modulus.bitLength() + " bits");
-
- // publicExponent
- BigInteger publicExponent = readASN1Integer(bis);
- System.out.println("publicExponent: " + publicExponent);
-
- // privateExponent
- BigInteger privateExponent = readASN1Integer(bis);
- System.out.println("privateExponent长度: " + privateExponent.bitLength() + " bits");
-
- // prime1
- BigInteger prime1 = readASN1Integer(bis);
- System.out.println("prime1长度: " + prime1.bitLength() + " bits");
-
- // prime2
- BigInteger prime2 = readASN1Integer(bis);
- System.out.println("prime2长度: " + prime2.bitLength() + " bits");
-
- // 验证 prime1 * prime2 == modulus
- BigInteger calculatedModulus = prime1.multiply(prime2);
- boolean valid = calculatedModulus.equals(modulus);
- System.out.println("\n验证 prime1 * prime2 == modulus: " + valid);
-
- if (!valid) {
- System.out.println(">>> 密钥数学关系不正确!这是无效的RSA私钥");
- System.out.println("计算得到的modulus长度: " + calculatedModulus.bitLength() + " bits");
- }
-
- // exponent1
- BigInteger exponent1 = readASN1Integer(bis);
- System.out.println("exponent1长度: " + exponent1.bitLength() + " bits");
-
- // exponent2
- BigInteger exponent2 = readASN1Integer(bis);
- System.out.println("exponent2长度: " + exponent2.bitLength() + " bits");
-
- // coefficient
- BigInteger coefficient = readASN1Integer(bis);
- System.out.println("coefficient长度: " + coefficient.bitLength() + " bits");
-
- System.out.println("\n========== 解析完成 ==========");
- }
-
- static int readASN1Length(ByteArrayInputStream bis) throws Exception {
- int firstByte = bis.read();
- if ((firstByte & 0x80) == 0) {
- return firstByte;
- }
- int numBytes = firstByte & 0x7F;
- int length = 0;
- for (int i = 0; i < numBytes; i++) {
- length = (length << 8) | (bis.read() & 0xFF);
- }
- return length;
- }
-
- static BigInteger readASN1Integer(ByteArrayInputStream bis) throws Exception {
- int tag = bis.read();
- if (tag != 0x02) throw new Exception("期望INTEGER tag: 0x02, 实际: 0x" + Integer.toHexString(tag));
- int len = readASN1Length(bis);
- byte[] data = new byte[len];
- bis.read(data);
- return new BigInteger(1, data);
- }
-}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/pom.xml b/gym-manage-api/gym-auth/pom.xml
index bff30df..fe2c32b 100644
--- a/gym-manage-api/gym-auth/pom.xml
+++ b/gym-manage-api/gym-auth/pom.xml
@@ -10,11 +10,12 @@
1.0.0
+ cn.novalon.gym.manage
gym-auth
jar
Gym Auth
- Phone Authentication Module - Phone Number Login Services
+ Authentication module for Gym Management System
@@ -29,25 +30,26 @@
cn.novalon.gym.manage
- manage-sys
+ gym-member
${project.version}
cn.novalon.gym.manage
- gym-member
+ manage-sys
${project.version}
+
org.springframework.boot
spring-boot-starter-webflux
- org.springdoc
- springdoc-openapi-starter-webflux-ui
+ org.springframework.boot
+ spring-boot-starter-security
org.springframework.boot
- spring-boot-starter-security
+ spring-boot-starter-validation
org.springframework.data
@@ -83,45 +85,25 @@
com.aliyun
aliyun-java-sdk-core
- 4.6.0
+ 4.6.3
+
- com.aliyun
- dysmsapi20170525
- 2.0.0
+ org.projectlombok
+ lombok
+ true
-
- 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.projectlombok
- lombok
- ${lombok.version}
-
-
+ ${java.version}
+ ${java.version}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
deleted file mode 100644
index d8d842d..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package cn.novalon.gym.manage.auth.config;
-
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class AuthConfig {
-}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
index fe9e35c..31551f3 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
@@ -4,44 +4,20 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
-/**
- * 阿里云短信配置属性类
- *
- * @author auto-generated
- * @date 2026-06-20
- */
@Data
@Configuration
@ConfigurationProperties(prefix = "alibaba.cloud.sms")
public class SmsProperties {
- /**
- * 访问密钥ID
- */
private String accessKeyId;
- /**
- * 访问密钥密钥
- */
private String accessKeySecret;
- /**
- * 短信签名名称
- */
private String signName;
- /**
- * 短信模板CODE
- */
private String templateCode;
- /**
- * 短信验证码长度(默认6位)
- */
- private int codeLength = 6;
+ private int codeLength = 4;
- /**
- * 短信验证码有效期(秒,默认300秒=5分钟)
- */
private long codeExpireSeconds = 300;
-}
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
index 4d21283..033da34 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
@@ -7,9 +7,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号验证码登录请求DTO
- */
@Data
@Builder
@NoArgsConstructor
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
index c3d0758..2cc0743 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
@@ -1,28 +1,23 @@
package cn.novalon.gym.manage.auth.dto;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号一键登录请求DTO
- * uniapp官方一键登录流程:前端通过云函数获取access_token和openid传给后端换取手机号
- */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PhoneLoginDto {
- @NotBlank(message = "access_token不能为空")
- private String accessToken;
-
- @NotBlank(message = "openid不能为空")
- private String openid;
+ @NotBlank(message = "手机号不能为空")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
private String nickname;
private String avatar;
-}
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
deleted file mode 100644
index c16602d..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package cn.novalon.gym.manage.auth.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Pattern;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 发送短信验证码请求DTO
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class SendCodeRequest {
-
- @NotBlank(message = "手机号不能为空")
- @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
- private String phone;
-}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
deleted file mode 100644
index d97cff5..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cn.novalon.gym.manage.auth.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Pattern;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 短信验证码登录请求DTO
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class SmsLoginDto {
-
- @NotBlank(message = "手机号不能为空")
- @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
- private String phone;
-
- @NotBlank(message = "验证码不能为空")
- private String code;
-}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java
index 562920a..af46968 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java
@@ -2,7 +2,6 @@ package cn.novalon.gym.manage.auth.handler;
import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
-import cn.novalon.gym.manage.auth.dto.SendCodeRequest;
import cn.novalon.gym.manage.auth.service.PhoneAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,8 +38,8 @@ public class PhoneAuthHandler {
public Mono sendSmsCode(ServerRequest request) {
log.info("收到发送短信验证码请求");
- return request.bodyToMono(SendCodeRequest.class)
- .flatMap(req -> phoneAuthService.sendSmsCode(req.getPhone()))
+ return request.bodyToMono(PhoneLoginDto.class)
+ .flatMap(dto -> phoneAuthService.sendSmsCode(dto.getPhone()))
.flatMap(success -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("success", success, "message", success ? "验证码发送成功" : "验证码发送失败")));
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
index 2407a9b..cd2848e 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
@@ -1,37 +1,15 @@
package cn.novalon.gym.manage.auth.service;
-import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
+import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.vo.PhoneLoginVO;
import reactor.core.publisher.Mono;
-/**
- * 手机号认证服务接口
- */
public interface PhoneAuthService {
- /**
- * 手机号一键登录(uniapp官方运营商认证)
- * 已注册则直接登录,未注册则自动注册后登录
- *
- * @param request 登录请求
- * @return 登录响应
- */
Mono oneClickLogin(PhoneLoginDto request);
- /**
- * 发送短信验证码(阿里云)
- *
- * @param phone 手机号
- * @return 是否发送成功
- */
Mono sendSmsCode(String phone);
- /**
- * 手机号验证码登录(阿里云)
- *
- * @param request 登录请求
- * @return 登录响应
- */
Mono codeLogin(PhoneCodeLoginDto request);
}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
index 4bd5050..fe96c7f 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
@@ -1,6 +1,5 @@
package cn.novalon.gym.manage.auth.service.impl;
-import cn.novalon.gym.manage.auth.config.SmsProperties;
import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.service.PhoneAuthService;
@@ -16,13 +15,6 @@ 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.sys.security.JwtTokenProvider;
-import com.aliyuncs.DefaultAcsClient;
-import com.aliyuncs.IAcsClient;
-import com.aliyuncs.dypnsapi.model.v20170525.GetMobileRequest;
-import com.aliyuncs.dypnsapi.model.v20170525.GetMobileResponse;
-import com.aliyuncs.exceptions.ClientException;
-import com.aliyuncs.exceptions.ServerException;
-import com.aliyuncs.profile.DefaultProfile;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -43,7 +35,6 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
private final EsSyncUtils esSyncUtils;
private final JwtTokenProvider jwtTokenProvider;
private final SmsService smsService;
- private final SmsProperties smsProperties;
private EsSyncUtils.EntitySyncer memberSyncer;
@@ -54,63 +45,19 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
@Override
public Mono oneClickLogin(PhoneLoginDto request) {
- log.info("手机号一键登录请求, accessToken: {}, openid: {}", request.getAccessToken(), request.getOpenid());
+ log.info("手机号一键登录, phone: {}", request.getPhone());
- return Mono.fromCallable(() -> getPhoneByToken(request.getAccessToken(), request.getOpenid()))
- .flatMap(phone -> {
- log.info("通过access_token获取手机号成功: {}", maskPhone(phone));
- String encryptedPhone = encryptPhone(phone);
+ String encryptedPhone = encryptPhone(request.getPhone());
- return memberRepository.findByPhone(encryptedPhone)
- .flatMap(existingMember -> {
- log.info("手机号已注册,直接登录, memberId: {}", existingMember.getId());
- return doLogin(existingMember, false, request);
- })
- .switchIfEmpty(Mono.defer(() -> {
- log.info("手机号未注册,创建新会员");
- return createNewMemberAndLogin(request, encryptedPhone);
- }));
+ return memberRepository.findByPhone(encryptedPhone)
+ .flatMap(existingMember -> {
+ log.info("手机号已注册,直接登录, memberId: {}", existingMember.getId());
+ return doLogin(existingMember, false, request);
})
- .onErrorResume(e -> {
- log.error("一键登录失败", e);
- return Mono.error(new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + e.getMessage()));
- });
- }
-
- /**
- * 通过access_token和openid获取手机号
- * 使用阿里云DYPNS API
- */
- private String getPhoneByToken(String accessToken, String openid) throws Exception {
- DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
- IAcsClient client = new DefaultAcsClient(profile);
- GetMobileRequest request = new GetMobileRequest();
- request.setAccessToken(accessToken);
-
- try {
- GetMobileResponse response = client.getAcsResponse(request);
- if ("OK".equals(response.getCode())) {
- return response.getGetMobileResultDTO().getMobile();
- }
- log.warn("DYPNS API返回错误: code={}, message={}", response.getCode(), response.getMessage());
- throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + response.getMessage());
- } catch (ServerException e) {
- log.error("阿里云DYPNS API服务端异常", e);
- throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败");
- } catch (ClientException e) {
- log.error("阿里云DYPNS API客户端异常", e);
- throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败");
- }
- }
-
- /**
- * 手机号脱敏显示
- */
- private String maskPhone(String phone) {
- if (phone == null || phone.length() < 11) {
- return phone;
- }
- return phone.substring(0, 3) + "****" + phone.substring(7);
+ .switchIfEmpty(Mono.defer(() -> {
+ log.info("手机号未注册,创建新会员");
+ return createNewMemberAndLogin(request, encryptedPhone);
+ }));
}
@Override
@@ -140,6 +87,7 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
.switchIfEmpty(Mono.defer(() -> {
log.info("手机号未注册,创建新会员");
PhoneLoginDto registerRequest = new PhoneLoginDto();
+ registerRequest.setPhone(request.getPhone());
return createNewMemberAndLogin(registerRequest, encryptedPhone);
}));
});
@@ -205,7 +153,7 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
vo.setNeedCompleteInfo(needCompleteInfo);
vo.setNickname(member.getNickname());
vo.setAvatar(member.getAvatar());
- vo.setPhone(member.getPhone() != null ? decryptPhone(member.getPhone()) : null);
+ vo.setPhone(decryptPhone(member.getPhone()));
return vo;
}
@@ -226,4 +174,4 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
return null;
}
}
-}
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
index f5f4651..795c103 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
@@ -5,9 +5,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号一键登录响应VO
- */
@Data
@Builder
@NoArgsConstructor
diff --git a/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 216a5f9..beb4996 100644
--- a/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,5 +1 @@
-cn.novalon.gym.manage.auth.handler.PhoneAuthHandler
-cn.novalon.gym.manage.auth.service.impl.PhoneAuthServiceImpl
-cn.novalon.gym.manage.auth.service.impl.SmsServiceImpl
-cn.novalon.gym.manage.auth.config.AuthConfig
cn.novalon.gym.manage.auth.config.SmsProperties
\ No newline at end of file
diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml
index 12e0171..bfafbbb 100644
--- a/gym-manage-api/gym-groupCourse/pom.xml
+++ b/gym-manage-api/gym-groupCourse/pom.xml
@@ -35,6 +35,11 @@
manage-common
${project.version}
+
+ cn.novalon.gym.manage
+ manage-sys
+ ${project.version}
+
cn.novalon.gym.manage
manage-db
@@ -81,8 +86,8 @@
gym-member
${project.version}
-
-
+
+
com.google.zxing
core
@@ -93,20 +98,20 @@
javase
3.5.3
+
com.aliyun.oss
aliyun-sdk-oss
- 3.17.4
+ 3.17.1
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
+ org.springframework.boot
+ spring-boot-maven-plugin
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
index 8983e20..5c96f8f 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
@@ -71,6 +71,22 @@ public class Member extends BaseEntity {
@Column("official_open_id")
private String officialOpenId;
+ // 阿里云号码认证OpenID
+ @Column("dypns_open_id")
+ private String dypnsOpenId;
+
+ // 身份证号码(AES加密存储)
+ @Column("id_card")
+ private String idCard;
+
+ // 真实姓名(AES加密存储)
+ @Column("real_name")
+ private String realName;
+
+ // 注册渠道:SMS-短信验证码,ONE_CLICK-一键登录,WECHAT-微信授权
+ @Column("register_channel")
+ private String registerChannel;
+
// 软删除
@Column("is_deleted")
private Boolean isDeleted;
diff --git a/gym-manage-api/gym-payment/pom.xml b/gym-manage-api/gym-payment/pom.xml
deleted file mode 100644
index fc273a9..0000000
--- a/gym-manage-api/gym-payment/pom.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
- 4.0.0
-
-
- cn.novalon.gym.manage
- gym-manage-api
- 1.0.0
-
-
- cn.novalon.gym.manage
- gym-payment
- jar
-
- Gym Payment
- 支付模块 - 支付宝App支付
-
-
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
-
-
- cn.novalon.gym.manage
- manage-common
- ${project.version}
-
-
-
-
- com.alipay.sdk
- alipay-sdk-java
- 4.35.120.ALL
-
-
-
-
- com.google.code.gson
- gson
- 2.10.1
-
-
-
-
- org.projectlombok
- lombok
- true
-
-
-
-
- io.swagger.core.v3
- swagger-annotations
- 2.2.19
-
-
-
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java
deleted file mode 100644
index e0b747e..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package cn.novalon.gym.manage.payment.config;
-
-import com.alipay.api.AlipayClient;
-import com.alipay.api.DefaultAlipayClient;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 支付宝客户端配置
- */
-@Configuration
-public class AlipayClientConfig {
-
- private final AlipayProperties alipayProperties;
-
- public AlipayClientConfig(AlipayProperties alipayProperties) {
- this.alipayProperties = alipayProperties;
- }
-
- @Bean
- public AlipayClient alipayClient() {
- return new DefaultAlipayClient(
- alipayProperties.getGateway(),
- alipayProperties.getAppId(),
- alipayProperties.getAppPrivateKey(),
- "json",
- alipayProperties.getCharset(),
- alipayProperties.getAlipayPublicKey(),
- alipayProperties.getSignType()
- );
- }
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java
deleted file mode 100644
index f78fd71..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package cn.novalon.gym.manage.payment.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * 支付宝配置属性
- * 沙箱环境配置
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "alipay")
-public class AlipayProperties {
-
- /**
- * 应用ID
- */
- private String appId;
-
- /**
- * 应用私钥 (PKCS#8格式)
- */
- private String appPrivateKey;
-
- /**
- * 支付宝公钥
- */
- private String alipayPublicKey;
-
- /**
- * 签名方式
- */
- private String signType = "RSA2";
-
- /**
- * 编码格式
- */
- private String charset = "UTF-8";
-
- /**
- * 网关地址
- * 沙箱环境: https://openapi-sandbox.dl.alipaydev.com/gateway.do
- * 正式环境: https://openapi.alipay.com/gateway.do
- */
- private String gateway;
-
- /**
- * 异步通知地址
- */
- private String notifyUrl;
-
- /**
- * 是否沙箱环境
- */
- private boolean sandbox = true;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java
deleted file mode 100644
index b01b20a..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package cn.novalon.gym.manage.payment.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 统一API响应
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class ApiResponse {
-
- private int code;
- private String message;
- private T data;
-
- public static ApiResponse success(T data) {
- return ApiResponse.builder()
- .code(200)
- .message("success")
- .data(data)
- .build();
- }
-
- public static ApiResponse success(String message, T data) {
- return ApiResponse.builder()
- .code(200)
- .message(message)
- .data(data)
- .build();
- }
-
- public static ApiResponse error(int code, String message) {
- return ApiResponse.builder()
- .code(code)
- .message(message)
- .data(null)
- .build();
- }
-
- public static ApiResponse error(String message) {
- return ApiResponse.builder()
- .code(500)
- .message(message)
- .data(null)
- .build();
- }
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java
deleted file mode 100644
index 5b1347e..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package cn.novalon.gym.manage.payment.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-/**
- * 创建支付请求DTO
- */
-@Data
-@Schema(description = "创建支付请求")
-public class CreatePaymentRequest {
-
- @Schema(description = "会员ID")
- private Long memberId;
-
- @Schema(description = "订单类型: MEMBER_CARD-会员卡, GROUP_COURSE-团课, GOODS-商品")
- private String orderType;
-
- @Schema(description = "商品描述")
- private String goodsDesc;
-
- @Schema(description = "交易金额(单位:分)")
- private String transAmt;
-
- @Schema(description = "交易类型: ALIPAY-支付宝, WECHAT-微信")
- private String tradeType;
-
- @Schema(description = "备注")
- private String remark;
-
- @Schema(description = "账户号")
- private String acctId;
-
- @Schema(description = "过期时间(yyyyMMddHHmmss)")
- private String timeExpire;
-
- @Schema(description = "延迟入账标识")
- private String delayAcctFlag;
-
- @Schema(description = "手续费标识")
- private Integer feeFlag;
-
- @Schema(description = "禁用支付方式")
- private String limitPayType;
-
- @Schema(description = "渠道号")
- private String channelNo;
-
- @Schema(description = "支付场景")
- private String payScene;
-
- @Schema(description = "异步通知URL")
- private String notifyUrl;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java
deleted file mode 100644
index 50fd4cb..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package cn.novalon.gym.manage.payment.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 支付响应DTO
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-@Schema(description = "支付响应")
-public class PaymentResponse {
-
- @Schema(description = "订单ID")
- private String orderId;
-
- @Schema(description = "支付状态: PENDING-待支付, SUCCESS-成功, FAIL-失败, CLOSED-关闭")
- private String status;
-
- @Schema(description = "支付链接(App支付返回 scheme)")
- private String payUrl;
-
- @Schema(description = "二维码链接 (扫码支付)")
- private String qrCode;
-
- @Schema(description = "完整支付信息")
- private String payInfo;
-
- @Schema(description = "H5支付链接")
- private String h5PayUrl;
-
- @Schema(description = "错误码")
- private String errorCode;
-
- @Schema(description = "错误消息")
- private String errorMsg;
-
- @Schema(description = "交易金额(分)")
- private String transAmt;
-
- @Schema(description = "商品描述")
- private String goodsDesc;
-
- @Schema(description = "交易类型")
- private String tradeType;
-
- @Schema(description = "支付成功时间")
- private String payTime;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java
deleted file mode 100644
index e402f0c..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package cn.novalon.gym.manage.payment.handler;
-
-import cn.novalon.gym.manage.payment.dto.ApiResponse;
-import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
-import cn.novalon.gym.manage.payment.dto.PaymentResponse;
-import cn.novalon.gym.manage.payment.service.PaymentService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.extern.slf4j.Slf4j;
-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 reactor.core.publisher.MonoSink;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 支付处理器
- * 提供支付宝App支付相关接口
- */
-@Slf4j
-@Component
-@Tag(name = "支付管理", description = "支付宝App支付接口")
-public class PaymentHandler {
-
- private final PaymentService paymentService;
-
- public PaymentHandler(PaymentService paymentService) {
- this.paymentService = paymentService;
- }
-
- @Operation(summary = "创建支付订单", description = "创建支付宝App支付订单")
- public Mono createPayment(ServerRequest request) {
- return request.bodyToMono(CreatePaymentRequest.class)
- .flatMap(createReq -> {
- log.info("[Payment] 创建支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}",
- createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt());
-
- PaymentResponse result = paymentService.alipayAppPay(createReq);
-
- if ("FAIL".equals(result.getStatus())) {
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error(result.getErrorMsg()));
- }
-
- return ServerResponse.ok()
- .bodyValue(ApiResponse.success(result));
- })
- .onErrorResume(e -> {
- log.error("[Payment] 创建支付订单异常", e);
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error("创建支付订单失败: " + e.getMessage()));
- });
- }
-
- @Operation(summary = "创建扫码支付订单", description = "创建支付宝扫码支付订单,返回二维码链接")
- public Mono createQrCodePayment(ServerRequest request) {
- return request.bodyToMono(CreatePaymentRequest.class)
- .flatMap(createReq -> {
- log.info("[Payment] 创建扫码支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}",
- createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt());
-
- PaymentResponse result = paymentService.alipayQrCodePay(createReq);
-
- if ("FAIL".equals(result.getStatus())) {
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error(result.getErrorMsg()));
- }
-
- return ServerResponse.ok()
- .bodyValue(ApiResponse.success(result));
- })
- .onErrorResume(e -> {
- log.error("[Payment] 创建扫码支付订单异常", e);
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error("创建扫码支付订单失败: " + e.getMessage()));
- });
- }
-
- @Operation(summary = "查询支付状态", description = "查询支付订单状态")
- public Mono getPaymentStatus(ServerRequest request) {
- String orderId = request.pathVariable("orderId");
- log.info("[Payment] 查询支付状态: orderId={}", orderId);
-
- PaymentResponse result = paymentService.getPaymentStatus(orderId);
- return ServerResponse.ok()
- .bodyValue(ApiResponse.success(result));
- }
-
- @Operation(summary = "支付宝异步通知", description = "接收支付宝异步回调通知")
- public Mono alipayNotify(ServerRequest request) {
- return request.bodyToMono(Map.class)
- .flatMap(params -> {
- log.info("[Payment] 收到支付宝异步通知: params={}", params);
-
- // 转换Map类型
- Map notifyParams = new HashMap<>();
- if (params instanceof Map) {
- ((Map, ?>) params).forEach((key, value) ->
- notifyParams.put(String.valueOf(key), String.valueOf(value)));
- }
-
- String result = paymentService.handleAlipayNotify(notifyParams);
- return ServerResponse.ok()
- .bodyValue(result);
- })
- .onErrorResume(e -> {
- log.error("[Payment] 异步通知处理异常", e);
- return ServerResponse.ok()
- .bodyValue("fail");
- });
- }
-
- @Operation(summary = "申请退款", description = "申请支付退款")
- public Mono refundPayment(ServerRequest request) {
- String orderId = request.pathVariable("orderId");
-
- return request.bodyToMono(Map.class)
- .flatMap(body -> {
- String refundAmt = String.valueOf(body.get("refundAmt"));
- log.info("[Payment] 申请退款: orderId={}, refundAmt={}", orderId, refundAmt);
-
- boolean success = paymentService.refund(orderId, refundAmt);
- if (success) {
- return ServerResponse.ok()
- .bodyValue(ApiResponse.success("退款申请成功"));
- } else {
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error("退款申请失败"));
- }
- })
- .onErrorResume(e -> {
- log.error("[Payment] 退款异常: orderId={}", orderId, e);
- return ServerResponse.ok()
- .bodyValue(ApiResponse.error("退款失败: " + e.getMessage()));
- });
- }
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java
deleted file mode 100644
index 7170ce5..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package cn.novalon.gym.manage.payment.service;
-
-import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
-import cn.novalon.gym.manage.payment.dto.PaymentResponse;
-
-/**
- * 支付服务接口
- */
-public interface PaymentService {
-
- /**
- * 创建支付订单
- *
- * @param request 创建支付请求
- * @return 支付响应
- */
- PaymentResponse createPayment(CreatePaymentRequest request);
-
- /**
- * 查询支付状态
- *
- * @param orderId 订单ID
- * @return 支付响应
- */
- PaymentResponse getPaymentStatus(String orderId);
-
- /**
- * 支付宝App支付
- *
- * @param request 创建支付请求
- * @return 支付响应
- */
- PaymentResponse alipayAppPay(CreatePaymentRequest request);
-
- /**
- * 支付宝扫码支付(二维码支付)
- *
- * @param request 创建支付请求
- * @return 支付响应(包含二维码链接)
- */
- PaymentResponse alipayQrCodePay(CreatePaymentRequest request);
-
- /**
- * 处理支付宝异步通知
- *
- * @param params 通知参数
- * @return 处理结果
- */
- String handleAlipayNotify(java.util.Map params);
-
- /**
- * 申请退款
- *
- * @param orderId 订单ID
- * @param refundAmt 退款金额(分)
- * @return 退款结果
- */
- boolean refund(String orderId, String refundAmt);
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java
deleted file mode 100644
index 2731d27..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java
+++ /dev/null
@@ -1,445 +0,0 @@
-package cn.novalon.gym.manage.payment.service.impl;
-
-import cn.novalon.gym.manage.common.util.RedisUtil;
-import cn.novalon.gym.manage.payment.config.AlipayProperties;
-import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
-import cn.novalon.gym.manage.payment.dto.PaymentResponse;
-import cn.novalon.gym.manage.payment.service.PaymentService;
-import com.alipay.api.AlipayApiException;
-import com.alipay.api.AlipayClient;
-import com.alipay.api.internal.util.AlipaySignature;
-import com.alipay.api.request.AlipayTradeAppPayRequest;
-import com.alipay.api.request.AlipayTradePrecreateRequest;
-import com.alipay.api.request.AlipayTradeQueryRequest;
-import com.alipay.api.request.AlipayTradeRefundRequest;
-import com.alipay.api.response.AlipayTradeAppPayResponse;
-import com.alipay.api.response.AlipayTradePrecreateResponse;
-import com.alipay.api.response.AlipayTradeQueryResponse;
-import com.alipay.api.response.AlipayTradeRefundResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 支付服务实现
- * App支付 (alipay.trade.app.pay)
- */
-@Slf4j
-@Service
-public class PaymentServiceImpl implements PaymentService {
-
- private static final String PROCESSED_TRADE_PREFIX = "alipay:processed:trade:";
- private static final String REFUND_ORDER_PREFIX = "alipay:refund:order:";
- private static final String ORDER_CACHE_PREFIX = "alipay:order:";
- private static final long IDEMPOTENT_EXPIRE_SECONDS = 86400; // 24小时过期
-
- private final AlipayClient alipayClient;
- private final AlipayProperties alipayProperties;
- private final RedisUtil redisUtil;
-
- /**
- * 订单本地缓存 (作为Redis的二级缓存)
- */
- private final Map localOrderCache = new ConcurrentHashMap<>();
-
- public PaymentServiceImpl(AlipayClient alipayClient, AlipayProperties alipayProperties, RedisUtil redisUtil) {
- this.alipayClient = alipayClient;
- this.alipayProperties = alipayProperties;
- this.redisUtil = redisUtil;
- }
-
- @Override
- public PaymentResponse createPayment(CreatePaymentRequest request) {
- // App支付使用 alipay.trade.app.pay
- return alipayAppPay(request);
- }
-
- @Override
- public PaymentResponse getPaymentStatus(String orderId) {
- // 先查本地缓存
- PaymentResponse cached = localOrderCache.get(orderId);
- if (cached != null && "SUCCESS".equals(cached.getStatus())) {
- return cached;
- }
-
- // 查Redis缓存
- PaymentResponse redisCached = getOrderFromRedis(orderId);
- if (redisCached != null && "SUCCESS".equals(redisCached.getStatus())) {
- localOrderCache.put(orderId, redisCached);
- return redisCached;
- }
-
- try {
- AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
- queryRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\"}");
-
- AlipayTradeQueryResponse response = alipayClient.execute(queryRequest);
- if (response.isSuccess()) {
- Date sendPayDate = response.getSendPayDate();
- String payTimeStr = sendPayDate != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(sendPayDate) : null;
-
- PaymentResponse result = PaymentResponse.builder()
- .orderId(orderId)
- .status(response.getTradeStatus())
- .payTime(payTimeStr)
- .transAmt(response.getTotalAmount())
- .build();
-
- if ("TRADE_SUCCESS".equals(response.getTradeStatus())) {
- result.setStatus("SUCCESS");
- // 更新缓存
- saveOrderToRedis(orderId, result);
- localOrderCache.put(orderId, result);
- }
- return result;
- }
- } catch (AlipayApiException e) {
- log.error("查询支付状态失败: orderId={}", orderId, e);
- }
-
- return PaymentResponse.builder()
- .orderId(orderId)
- .status(cached != null ? cached.getStatus() : (redisCached != null ? redisCached.getStatus() : "PENDING"))
- .build();
- }
-
- @Override
- public PaymentResponse alipayAppPay(CreatePaymentRequest request) {
- // 生成订单号
- String orderId = generateOrderId();
-
- try {
- AlipayTradeAppPayRequest payRequest = new AlipayTradeAppPayRequest();
- payRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
-
- // 构建业务参数 (测试金额:固定1分钱)
- String bizContent = "{\"out_trade_no\":\"" + orderId + "\","
- + "\"total_amount\":\"0.01\","
- + "\"subject\":\"" + request.getGoodsDesc() + "\","
- + "\"product_code\":\"QUICK_MSECURITY_PAY\","
- + "\"timeout_express\":\"30m\"}";
-
- log.info("[Alipay] App支付请求: orderId={}, bizContent={}", orderId, bizContent);
-
- payRequest.setBizContent(bizContent);
-
- AlipayTradeAppPayResponse response = alipayClient.sdkExecute(payRequest);
-
- if (response.isSuccess()) {
- String payUrl = response.getBody();
-
- // App唤起支付宝的scheme格式: alipay://...
- // 实际使用时,前端使用 plus.runtime.openURL(payUrl) 唤起支付宝
- log.info("[Alipay] App支付创建成功: orderId={}, payUrl={}", orderId, payUrl);
-
- PaymentResponse result = PaymentResponse.builder()
- .orderId(orderId)
- .status("PENDING")
- .payUrl(payUrl)
- .payInfo(response.getBody())
- .goodsDesc(request.getGoodsDesc())
- .transAmt(request.getTransAmt())
- .tradeType(request.getTradeType())
- .build();
-
- // 缓存订单到Redis和本地
- saveOrderToRedis(orderId, result);
- localOrderCache.put(orderId, result);
-
- return result;
- } else {
- log.error("[Alipay] App支付创建失败: orderId={}, error={}", orderId, response.getMsg());
- return PaymentResponse.builder()
- .orderId(orderId)
- .status("FAIL")
- .errorCode(response.getCode())
- .errorMsg(response.getMsg())
- .build();
- }
- } catch (AlipayApiException e) {
- log.error("[Alipay] App支付异常: orderId={}", orderId, e);
- String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR";
- String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e);
- return PaymentResponse.builder()
- .orderId(orderId)
- .status("FAIL")
- .errorCode(errCode)
- .errorMsg(errMsg)
- .build();
- }
- }
-
- /**
- * 支付宝扫码支付 (alipay.trade.precreate)
- * 生成付款二维码,用户使用支付宝扫码完成支付
- */
- @Override
- public PaymentResponse alipayQrCodePay(CreatePaymentRequest request) {
- String orderId = generateOrderId();
-
- try {
- AlipayTradePrecreateRequest precreateRequest = new AlipayTradePrecreateRequest();
- precreateRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
-
- // 构建业务参数
- String bizContent = "{\"out_trade_no\":\"" + orderId + "\","
- + "\"total_amount\":\"0.01\","
- + "\"subject\":\"" + request.getGoodsDesc() + "\","
- + "\"timeout_express\":\"30m\"}";
-
- log.info("[Alipay] 扫码支付请求: orderId={}, bizContent={}", orderId, bizContent);
-
- precreateRequest.setBizContent(bizContent);
- AlipayTradePrecreateResponse response = alipayClient.execute(precreateRequest);
-
- if (response.isSuccess()) {
- String qrCode = response.getQrCode(); // 二维码链接
-
- log.info("[Alipay] 扫码支付创建成功: orderId={}, qrCode={}", orderId, qrCode);
-
- PaymentResponse result = PaymentResponse.builder()
- .orderId(orderId)
- .status("PENDING")
- .qrCode(qrCode)
- .payInfo(response.getBody())
- .goodsDesc(request.getGoodsDesc())
- .transAmt(request.getTransAmt())
- .tradeType("QRCODE")
- .build();
-
- // 缓存订单到Redis和本地
- saveOrderToRedis(orderId, result);
- localOrderCache.put(orderId, result);
-
- return result;
- } else {
- log.error("[Alipay] 扫码支付创建失败: orderId={}, error={}", orderId, response.getMsg());
- return PaymentResponse.builder()
- .orderId(orderId)
- .status("FAIL")
- .errorCode(response.getCode())
- .errorMsg(response.getMsg())
- .build();
- }
- } catch (AlipayApiException e) {
- log.error("[Alipay] 扫码支付异常: orderId={}", orderId, e);
- String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR";
- String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e);
- return PaymentResponse.builder()
- .orderId(orderId)
- .status("FAIL")
- .errorCode(errCode)
- .errorMsg(errMsg)
- .build();
- }
- }
-
- @Override
- public String handleAlipayNotify(Map params) {
- String outTradeNo = params.get("out_trade_no");
-
- try {
- // 1. 幂等处理:检查是否已处理过该通知(使用Redis)
- if (isTradeProcessed(outTradeNo)) {
- log.info("[Alipay] 幂等处理:通知已处理,跳过, outTradeNo={}", outTradeNo);
- return "success";
- }
-
- // 2. 验签
- boolean signVerified = AlipaySignature.rsaCheckV1(
- params,
- alipayProperties.getAlipayPublicKey(),
- alipayProperties.getCharset(),
- alipayProperties.getSignType()
- );
-
- if (!signVerified) {
- log.warn("[Alipay] 异步通知验签失败: outTradeNo={}", outTradeNo);
- return "fail";
- }
-
- // 3. 关键信息校验
- // 校验 app_id 是否匹配
- String notifyAppId = params.get("app_id");
- if (!alipayProperties.getAppId().equals(notifyAppId)) {
- log.warn("[Alipay] app_id 不匹配: expected={}, actual={}", alipayProperties.getAppId(), notifyAppId);
- return "fail";
- }
-
- // 校验卖家ID (seller_id)
- String sellerId = params.get("seller_id");
- if (sellerId != null && !sellerId.isEmpty()) {
- // 商家ID校验逻辑根据实际情况添加
- log.debug("[Alipay] 卖家ID校验: seller_id={}", sellerId);
- }
-
- // 校验交易金额
- String notifyTotalAmount = params.get("total_amount");
- PaymentResponse cachedOrder = getOrderFromRedis(outTradeNo);
- if (cachedOrder != null && notifyTotalAmount != null) {
- // 金额校验:确保通知金额与订单金额一致(防止金额篡改)
- log.debug("[Alipay] 交易金额校验: orderId={}, amount={}", outTradeNo, notifyTotalAmount);
- }
-
- String tradeStatus = params.get("trade_status");
- log.info("[Alipay] 异步通知验签成功: outTradeNo={}, tradeStatus={}", outTradeNo, tradeStatus);
-
- // 4. 判断交易状态
- if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
- // 更新订单状态
- if (cachedOrder != null) {
- cachedOrder.setStatus("SUCCESS");
- cachedOrder.setPayTime(params.get("gmt_payment"));
- saveOrderToRedis(outTradeNo, cachedOrder);
- localOrderCache.put(outTradeNo, cachedOrder);
- }
-
- // 5. 标记为已处理(幂等,使用Redis)
- markTradeProcessed(outTradeNo);
-
- return "success";
- }
-
- return "fail";
- } catch (AlipayApiException e) {
- log.error("[Alipay] 异步通知处理异常: outTradeNo={}", outTradeNo, e);
- return "fail";
- }
- }
-
- @Override
- public boolean refund(String orderId, String refundAmt) {
- // 1. 退款幂等检查:防止重复退款(使用Redis)
- if (isOrderRefunded(orderId)) {
- log.info("[Alipay] 退款幂等:订单已退款过,跳过, orderId={}", orderId);
- return true;
- }
-
- try {
- AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
- refundRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\",\"refund_amount\":\"" + refundAmt + "\"}");
-
- AlipayTradeRefundResponse response = alipayClient.execute(refundRequest);
- if (response.isSuccess()) {
- // 2. 校验退款是否真正成功:必须判断 fund_change=Y
- String fundChange = response.getFundChange();
- if ("Y".equals(fundChange)) {
- log.info("[Alipay] 退款成功: orderId={}, refundAmt={}, fundChange={}", orderId, refundAmt, fundChange);
- // 标记为已退款(幂等,使用Redis)
- markOrderRefunded(orderId);
- return true;
- } else {
- log.warn("[Alipay] 退款返回Y但fund_change!=Y: orderId={}, fundChange={}", orderId, fundChange);
- return false;
- }
- } else {
- log.error("[Alipay] 退款失败: orderId={}, error={}", orderId, response.getMsg());
- }
- } catch (AlipayApiException e) {
- log.error("[Alipay] 退款异常: orderId={}", orderId, e);
- }
- return false;
- }
-
- // ==================== Redis操作方法 ====================
-
- /**
- * 检查交易是否已处理(幂等)
- */
- private boolean isTradeProcessed(String outTradeNo) {
- try {
- return Boolean.TRUE.equals(redisUtil.hasKey(PROCESSED_TRADE_PREFIX + outTradeNo).block());
- } catch (Exception e) {
- log.error("[Alipay] 检查交易处理状态异常: outTradeNo={}", outTradeNo, e);
- return false;
- }
- }
-
- /**
- * 标记交易已处理(幂等)
- */
- private void markTradeProcessed(String outTradeNo) {
- try {
- redisUtil.setWithExpire(PROCESSED_TRADE_PREFIX + outTradeNo, "1", IDEMPOTENT_EXPIRE_SECONDS).block();
- } catch (Exception e) {
- log.error("[Alipay] 标记交易处理状态异常: outTradeNo={}", outTradeNo, e);
- }
- }
-
- /**
- * 检查订单是否已退款(幂等)
- */
- private boolean isOrderRefunded(String orderId) {
- try {
- return Boolean.TRUE.equals(redisUtil.hasKey(REFUND_ORDER_PREFIX + orderId).block());
- } catch (Exception e) {
- log.error("[Alipay] 检查订单退款状态异常: orderId={}", orderId, e);
- return false;
- }
- }
-
- /**
- * 标记订单已退款(幂等)
- */
- private void markOrderRefunded(String orderId) {
- try {
- redisUtil.setWithExpire(REFUND_ORDER_PREFIX + orderId, "1", IDEMPOTENT_EXPIRE_SECONDS).block();
- } catch (Exception e) {
- log.error("[Alipay] 标记订单退款状态异常: orderId={}", orderId, e);
- }
- }
-
- /**
- * 从Redis获取订单
- */
- private PaymentResponse getOrderFromRedis(String orderId) {
- try {
- return redisUtil.get(ORDER_CACHE_PREFIX + orderId, PaymentResponse.class).block();
- } catch (Exception e) {
- log.error("[Alipay] 获取Redis订单异常: orderId={}", orderId, e);
- return null;
- }
- }
-
- /**
- * 保存订单到Redis
- */
- private void saveOrderToRedis(String orderId, PaymentResponse response) {
- try {
- redisUtil.setWithExpire(ORDER_CACHE_PREFIX + orderId, response, IDEMPOTENT_EXPIRE_SECONDS).block();
- } catch (Exception e) {
- log.error("[Alipay] 保存订单到Redis异常: orderId={}", orderId, e);
- }
- }
-
- /**
- * 生成订单号
- */
- private String generateOrderId() {
- String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
- String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
- return timestamp + uuid;
- }
-
- /**
- * 提取异常中的错误信息(兜底)
- */
- private String extractErrorMessage(AlipayApiException e) {
- Throwable cause = e.getCause();
- if (cause != null && cause.getMessage() != null && cause.getMessage().contains("504")) {
- return "支付宝沙箱网关连接超时(504),请检查服务器网络能否访问 openapi-sandbox.dl.alipaydev.com";
- }
- if (cause != null) {
- return "网络异常: " + cause.getClass().getSimpleName() + " - " + (cause.getMessage() != null ? cause.getMessage().substring(0, Math.min(cause.getMessage().length(), 200)) : "");
- }
- if (e.getMessage() != null) {
- return "支付宝接口异常: " + (e.getMessage().length() > 200 ? e.getMessage().substring(0, 200) : e.getMessage());
- }
- return "支付宝接口调用失败,请稍后重试";
- }
-}
diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml
index beeccbe..7a15e65 100644
--- a/gym-manage-api/manage-app/pom.xml
+++ b/gym-manage-api/manage-app/pom.xml
@@ -48,11 +48,6 @@
gym-checkIn
${project.version}
-
- cn.novalon.gym.manage
- gym-payment
- ${project.version}
-
cn.novalon.gym.manage
gym-dataCount
@@ -63,7 +58,7 @@
gym-auth
${project.version}
-
+
org.springframework.boot
spring-boot-starter-webflux
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 482d931..2a4da75 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,10 +1,11 @@
package cn.novalon.gym.manage.app.config;
+import cn.novalon.gym.manage.auth.handler.PhoneAuthHandler;
import cn.novalon.gym.manage.checkIn.handler.CheckInHandler;
import cn.novalon.gym.manage.datacount.handler.DataStatisticsHandler;
import cn.novalon.gym.manage.file.handler.SysFileHandler;
-import cn.novalon.gym.manage.auth.handler.PhoneAuthHandler;
+
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseRecommendHandler;
diff --git a/gym-manage-api/manage-app/src/main/resources/application-dev.yml b/gym-manage-api/manage-app/src/main/resources/application-dev.yml
index 51a74d2..f430e72 100644
--- a/gym-manage-api/manage-app/src/main/resources/application-dev.yml
+++ b/gym-manage-api/manage-app/src/main/resources/application-dev.yml
@@ -36,3 +36,11 @@ logging:
cn.novalon.manage: DEBUG
org.springframework.r2dbc: DEBUG
org.springframework.web: TRACE
+
+alibaba:
+ cloud:
+ sms:
+ access-key-id: LTAI5t8GhorWLu5WkEx8MDZz
+ access-key-secret: jNDwb9IHvTIESUezLYHZRT5c5NEaCz
+ sign-name: 云渚科技验证平台
+ template-code: 100001
diff --git a/gym-manage-api/manage-app/src/main/resources/member-config.yml b/gym-manage-api/manage-app/src/main/resources/member-config.yml
index 61bb6c5..178d297 100644
--- a/gym-manage-api/manage-app/src/main/resources/member-config.yml
+++ b/gym-manage-api/manage-app/src/main/resources/member-config.yml
@@ -16,8 +16,8 @@ wechat:
# 手机号加密配置
phone-encryption:
- secret-key: ${PHONE_ENCRYPTION_SECRET_KEY}
- iv: ${PHONE_ENCRYPTION_IV}
+ secret-key: ${PHONE_ENCRYPTION_SECRET_KEY:P8539ANjWJWsRbVHZKhM8Q==}
+ iv: ${PHONE_ENCRYPTION_IV:3tHp07uMRYh1xKsIXvYJMA==}
spring:
elasticsearch:
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java
new file mode 100644
index 0000000..d4995e8
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java
@@ -0,0 +1,19 @@
+package cn.novalon.gym.manage.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "auth")
+public class AuthConfig {
+
+ private String accessKeyId;
+
+ private String accessKeySecret;
+
+ private Integer tokenExpireSeconds = 86400;
+
+ private Integer refreshTokenExpireSeconds = 604800;
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java
new file mode 100644
index 0000000..7b0efbc
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java
@@ -0,0 +1,54 @@
+package cn.novalon.gym.manage.common.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Result {
+
+ private Integer code;
+
+ private String message;
+
+ private T data;
+
+ private Long timestamp;
+
+ public static Result success(T data) {
+ return Result.builder()
+ .code(200)
+ .message("success")
+ .data(data)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result success(String message) {
+ return Result.builder()
+ .code(200)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result fail(Integer code, String message) {
+ return Result.builder()
+ .code(code)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result fail(String message) {
+ return Result.builder()
+ .code(500)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java
new file mode 100644
index 0000000..bae13cc
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java
@@ -0,0 +1,86 @@
+package cn.novalon.gym.manage.common.util;
+
+import cn.novalon.gym.manage.common.config.JwtProperties;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class JwtUtil {
+
+ private final JwtProperties jwtProperties;
+
+ private SecretKey getSigningKey() {
+ String secret = jwtProperties.getSecret();
+ if (secret.length() < 32) {
+ StringBuilder sb = new StringBuilder(secret);
+ while (sb.length() < 32) {
+ sb.append(secret);
+ }
+ secret = sb.substring(0, 32);
+ }
+ return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public String generateToken(String userId, String phone) {
+ Date now = new Date();
+ Date expiryDate = new Date(now.getTime() + jwtProperties.getExpiration());
+
+ Map claims = new HashMap<>();
+ claims.put("userId", userId);
+ claims.put("phone", phone);
+
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(userId)
+ .setIssuedAt(now)
+ .setExpiration(expiryDate)
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public String generateRefreshToken(String userId) {
+ Date now = new Date();
+ Date expiryDate = new Date(now.getTime() + jwtProperties.getExpiration() * 7);
+
+ return Jwts.builder()
+ .setSubject(userId)
+ .setIssuedAt(now)
+ .setExpiration(expiryDate)
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public Claims parseToken(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(getSigningKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ public String getUserIdFromToken(String token) {
+ return parseToken(token).getSubject();
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ parseToken(token);
+ return true;
+ } catch (Exception e) {
+ log.warn("JWT token validation failed: {}", e.getMessage());
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql
new file mode 100644
index 0000000..ca01bc4
--- /dev/null
+++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql
@@ -0,0 +1,99 @@
+-- ============================================
+-- 签到记录表(sign_in_record)
+-- ============================================
+
+-- Step 1: 创建 sign_in_record 表
+CREATE TABLE IF NOT EXISTS sign_in_record (
+ -- ========== 主键和基础字段 ==========
+ id BIGSERIAL PRIMARY KEY, -- 自增主键
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 记录创建时间
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 记录更新时间
+
+ -- ========== 会员相关字段 ==========
+ member_id BIGINT NOT NULL, -- 会员ID,关联member_user表
+ member_card_id BIGINT, -- 签到时使用的会员卡ID
+
+ -- ========== 签到核心字段 ==========
+ sign_in_time TIMESTAMP NOT NULL, -- 签到入场时间
+ sign_in_type VARCHAR(20) NOT NULL, -- 签到方式:QR_CODE-扫码签到,MANUAL-手动签到,FACE-人脸识别
+ sign_in_status VARCHAR(20) NOT NULL DEFAULT 'SUCCESS', -- 签到状态:SUCCESS-成功,FAILED-失败
+
+ -- ========== 验证和错误信息 ==========
+ verification_details TEXT, -- JSONB格式,存储会员卡验证时的快照数据
+ fail_reason VARCHAR(500), -- 失败时的具体原因文案
+
+ -- ========== 操作人信息 ==========
+ operator_id BIGINT, -- 操作人ID(前台人员),自助签到时为NULL
+ operator_name VARCHAR(100), -- 操作人姓名冗余
+
+ -- ========== 设备和环境信息 ==========
+ device_info VARCHAR(200), -- 签到设备标识或型号
+ ip_address VARCHAR(50), -- 客户端IP地址
+ source VARCHAR(20) NOT NULL, -- 签到来源:MINI_PROGRAM-小程序扫码,PC_BACKEND-后台管理端
+
+ -- ========== 软删除字段 ==========
+ is_delete BOOLEAN DEFAULT FALSE -- 软删除标识:false-未删除,true-已删除
+);
+
+-- Step 2: 创建索引
+-- 会员ID索引(加速按会员查询签到记录)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_id ON sign_in_record(member_id);
+
+-- 签到时间索引(加速按时间范围查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_time ON sign_in_record(sign_in_time);
+
+-- 签到状态索引(加速按状态筛选)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_status ON sign_in_record(sign_in_status);
+
+-- 会员卡ID索引(加速按会员卡查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_card_id ON sign_in_record(member_card_id);
+
+-- 操作人ID索引(加速按操作人查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_operator_id ON sign_in_record(operator_id);
+
+-- 签到来源索引(加速按来源统计)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_source ON sign_in_record(source);
+
+-- 软删除索引(加速查询未删除的记录)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_is_delete ON sign_in_record(is_delete);
+
+-- 复合索引:会员ID + 签到时间(加速会员签到历史查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_time ON sign_in_record(member_id, sign_in_time);
+
+-- 复合索引:签到状态 + 签到时间(加速统计数据查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_status_time ON sign_in_record(sign_in_status, sign_in_time);
+
+-- 复合索引:签到日期(用于每日统计,使用表达式索引)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_date ON sign_in_record(DATE(sign_in_time));
+
+-- Step 3: 添加外键约束(可选,根据业务需求决定)
+-- 注意:如果member_user表使用了软删除,外键约束可能需要谨慎使用
+-- ALTER TABLE sign_in_record
+-- ADD CONSTRAINT fk_sign_in_record_member
+-- FOREIGN KEY (member_id) REFERENCES member_user(id) ON DELETE SET NULL;
+
+-- Step 4: 添加注释
+COMMENT ON TABLE sign_in_record IS '会员到店签到记录表';
+
+COMMENT ON COLUMN sign_in_record.id IS '自增主键';
+COMMENT ON COLUMN sign_in_record.created_at IS '记录创建时间';
+COMMENT ON COLUMN sign_in_record.updated_at IS '记录更新时间';
+
+COMMENT ON COLUMN sign_in_record.member_id IS '会员ID,关联member_user表';
+COMMENT ON COLUMN sign_in_record.member_card_id IS '签到时使用的会员卡ID';
+
+COMMENT ON COLUMN sign_in_record.sign_in_time IS '签到入场时间';
+COMMENT ON COLUMN sign_in_record.sign_in_type IS '签到方式:QR_CODE-扫码签到,MANUAL-手动签到,FACE-人脸识别';
+COMMENT ON COLUMN sign_in_record.sign_in_status IS '签到状态:SUCCESS-成功,FAILED-失败';
+
+COMMENT ON COLUMN sign_in_record.verification_details IS 'JSON格式,存储会员卡验证时的快照数据(包含卡类型、剩余次数/金额、有效期等)';
+COMMENT ON COLUMN sign_in_record.fail_reason IS '失败时的具体原因文案';
+
+COMMENT ON COLUMN sign_in_record.operator_id IS '操作人ID(前台人员),自助签到时为NULL';
+COMMENT ON COLUMN sign_in_record.operator_name IS '操作人姓名冗余(避免关联查询)';
+
+COMMENT ON COLUMN sign_in_record.device_info IS '签到设备标识或型号';
+COMMENT ON COLUMN sign_in_record.ip_address IS '客户端IP地址';
+COMMENT ON COLUMN sign_in_record.source IS '签到来源:MINI_PROGRAM-小程序扫码,PC_BACKEND-后台管理端';
+
+COMMENT ON COLUMN sign_in_record.is_delete IS '软删除标识:false-未删除,true-已删除';
diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql
new file mode 100644
index 0000000..05765d1
--- /dev/null
+++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql
@@ -0,0 +1,21 @@
+-- ============================================
+-- V20: 更新 member_user 表 - 添加阿里云号码认证字段
+-- 支持一键登录功能
+-- ============================================
+
+-- 添加阿里云号码认证相关字段
+ALTER TABLE IF EXISTS member_user
+ ADD COLUMN IF NOT EXISTS dypns_open_id VARCHAR(100),
+ ADD COLUMN IF NOT EXISTS id_card VARCHAR(50),
+ ADD COLUMN IF NOT EXISTS real_name VARCHAR(50),
+ ADD COLUMN IF NOT EXISTS register_channel VARCHAR(50) DEFAULT 'SMS';
+
+-- 创建索引
+CREATE INDEX IF NOT EXISTS idx_member_user_dypns_open_id ON member_user(dypns_open_id);
+CREATE INDEX IF NOT EXISTS idx_member_user_register_channel ON member_user(register_channel);
+
+-- 添加字段注释
+COMMENT ON COLUMN member_user.dypns_open_id IS '阿里云号码认证OpenID(一键登录用户唯一标识)';
+COMMENT ON COLUMN member_user.id_card IS '身份证号码(AES加密存储)';
+COMMENT ON COLUMN member_user.real_name IS '真实姓名(AES加密存储)';
+COMMENT ON COLUMN member_user.register_channel IS '注册渠道:SMS-短信验证码,ONE_CLICK-一键登录,WECHAT-微信授权';
diff --git a/gym-manage-api/pom.xml b/gym-manage-api/pom.xml
index 69441c7..8746a4a 100644
--- a/gym-manage-api/pom.xml
+++ b/gym-manage-api/pom.xml
@@ -47,7 +47,6 @@
gym-checkIn
gym-dataCount
gym-auth
- gym-payment