3ZVl3v7KwTX
zu{){I79RfxSWx>@prCRD5@aR*2DZV;IssZ^
zs)%Z(>$&}r?a!vpN#RtDTgl|@JEBm>G#RSN;O)7hWu$Fr`bMnaPSn*jJ5bkq8RJlvHDi
zjqGz8X?~~>bUTY%7=N;0WD8r@dAst6?%?jC=v{ShFrLFb4hGr)aE$KS%0j^tZ-J<+
z^kx7=vi?O*IKXSbkphQilF9oq=}IBva(CLGXl2qAC#D?pFd#qtt|zIpxU&bhi}A!6
zDU2SJSpkp8!DsGB5<;kApT3~%4cwkP>M!Q*s0UH{PZP)Lj%OoCyX`r>7cJ>E?ij|A
zrJ~}s1zBrx79YoV8VvN;Q5ZV}Nvh^UM{~l0nN?q0bfM^ahwaJf282iA*QGZ;}t%Xz+EC=$$}%S9*8H?V5K
zrq_*zUd&@y%cLYu!j!^}k?*%Vywv(?RiZp+emQgGDiTCl2=$xOpCUb2VHpW{6DQjy
z(B2xB@uoxz>s^4Gk`GWAW^vjCrBwc+*w!%Q-f2qGwTzNRYj`vY?WjNm#IiF(EP!yJ
z5>N!sl4AW$8C}-j4CT$qRVw$WsI#yoH{Mv+p2wS$tQ>I{t$*OBg?G5cSENH@;Mk`Zf@Y5t{IApY#HiV
z+LEn^S&QKx8Rn90YSmNIe^0C3x^A{xU(`QeRC;S|rF~Q;a%a^Cc$8;?N*=0ztL^PI
zP`WSc(2PUp4Ouz){|H2Qi}PnhkhBuLjI}f!SVqt3%=!u}W$Tts?+Q|c;i>c7Qg8oO
J@$Aoq{{z#g{1*TK
literal 8754
zcmcJVNmCm~7>4_ts{98IIVA{VW4ut60|*&ovqcDF`$B;bf*2%%BnV06uP1q*{@irW
zCdr~)Q^Sm=yT5(y=D&XjuH$apt^d2?esn*$IbGMTudhS5qW4#B(cg@54>Wq?E?m!b
zH6B*9wc=Dz+Q~N{UAbfTr>pt-HT~-72KK$a4fORdeF4i;VcB+T5AcqK<=S0pMq3bu
zdiI4e;sbV1BS2(*R}ilR6MTH{>U&LpS=(`Udfq?4ZVPVTjYMx2UUqhN4170!74QbR
zm5kPOUwD|tb)>?036f%NxJF)*(0?vEhvIm}J<$by@O`Q8ePKfrribSbTm%?A(MU(Q
zgS5&iWF}PZd8wScUxf+UF7*6aSDr%jd8Qd7(J|D@GtJ};i>Z=8s*`t;`_Qdw{ZM>#
zq!;v<>NAfK>QV?zcOv}l2N+lmHWMvkGK7w>VsH7GNaI)|-BMnHbdf9TFZ6`xIk!{_
ziTUUW%0_w%v)4rXx;t_kdbV7{9qReX&AWZQH{A!f>FRnratCflBOi2cx-I=()89rV
zCafNr;&VpThGw?}chBt#cB^uw$@7a=p6M#uc;K5S`bN`3QG*uZD)ZMXjg?6?z%bu0
zu3eU|Pg;xJwS^gtBlTxvF~JowKNa^q$pEe0ih8_!AgSU>BYguBkT3o0JnebwTlWx0
z@{o>ng+_^sJ-_y?ejf`TzL19l|C22%wez}%i)Y~@HR-Y^?$NHr*)j}ZK{q$T#Y!R|
z{58ELU`IQ$t2cT!A1nemzk9EYnn!MJFENYvKe@l%JK1SVtFhAhSBNW~I^z3*wDCr`
zPi55~WECG3y?>FsKf7hw_G|Z>wEJFBe@ii|Cn}Z%eb2oVm4{mSyL;=NOA8zBg&?eI
z&Or9{R5UcS{$BGJ<>4Q->JLGGDQdSh|DOjbcLbT3WqgOcFy~Ub8(P2TImhcZWfeQ3
z`;{<07xZvNm|w&j(o5xxE|AN;u$AW@G;l5H4|Ls0TQ}kd4WyL37gwJo$%ZIElAlCZ
zpy){NtCI1)Kr{00k;`k^kk
z(v8KY5-FEP({oOg-I6VA>&CXHlR#G-_k9*B<2=$qcEkc|vIt@V`ilA}r(i?$?8^3y
z`$*f0uZFPFqBc^DM7cBJA~Kyn0`s2N0$ze8JQcJeUOa%TLqsJ)o$6WUfzxn`6>baX
zt|CcG81sByNQPOxVqq!g-!AJ-`C6*~AmbvZDW2ISZ1}#vWDU@y!>zn5eq&hIbXh^tD%$df*%>*FxBA2t)^Tpp43cUo6
zt-(7qeb!s>`fO~y69#fg$Hy4!wo*SK$5U;`>{iC(p>UB|8a~TVgIj)Y={r2qYf;gT
zJT!8}6VDkr73lM&GmtRR&PJ_Qqa$O5^@&}th4OK1k#Tq;E2Xon7eDdep)J5PtzPI0
z9e5Rv>eZ~V-u3cp_x2@&w<|ixBG=LmPdbc_*3f~P6(D=dYV@0QUSM{wiA#2Sd}d@i&%-5<^Eibxh^`I;_|@T#gQ^S^Hx*e-FZ@a2$u=FTPA`N?K
zMG3Mu1qEHRYHvcLyg)WIR>*=%1w`#Jel6y-8XO{mnT2SazGF6ZRx6-V|-wEM->vV2{aqoT4RT
z8>uZ5KWu+&_Be+uTC&*C=T7b`VIrJFPfL=7WvP=wY`%-$R5MJc$gPUvPS15u2U$
zSVJ;y^&*tn&UITb=k=>69nEWPUpavKpUP|gLE^TPhz94i?p&j`!-+NFpUmXTwJ_V4
zlq+ZpyRhtmSjCLqi*;{UmzPwOA<$C3vLlSL<6)Hvav*zBBUuDN8g}AS+@I#8g#8@T
zYM=y&;ex8{j#}6o%g>ILY>_^oQM+=o*7Mk)Ee|2!ENYcXpInW;vQYLtx7cMFw4gOs
z!{e-2i*U;EX05Eq;dIhu)@_M=rdyr0=viq9kth1WERd?LHm}FD=F4kxI=+iKdOXZ*
zFO=239j$^qED%<1CIiP=20g-7NUi^T&{v(lhM^!6-X>UXYF2d2`J3
z_%Bs8a%a!?M3HgaSpi%VVeKT8J$w2Bb{~L1T&BCbD)9s`1q)r!Y|FU(UC`apSqZ!M
zK(QED6|*c?=4mME;al-7ijHMPE1s{Dx7V2{Rh9cve#&Xd@>o6xnr$D0Qxn^-uAa&4
z>8zKT=~X4eInRBy6!7v8t1P-l>aYcJMgEkbEnC6z(mlL|Oa+1sud4!HQ`fqYkv4lw
eU9C>7oAUy*Vmq<=qVf3u5DS`bo@)8C2>X9J1O>DJ
diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java
index 2a39369..7cec5cf 100644
--- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java
+++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package cn.novalon.gym.manage.sys.config;
+package cn.novalon.gym.manage.sys.config;
import cn.novalon.gym.manage.sys.audit.OperationLogWebFilter;
import cn.novalon.gym.manage.sys.security.JwtAuthenticationFilter;
@@ -21,7 +21,7 @@ public class SecurityConfig {
private final OperationLogWebFilter operationLogWebFilter;
private final Environment environment;
- public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
+ public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
OperationLogWebFilter operationLogWebFilter,
Environment environment) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
@@ -33,11 +33,11 @@ public class SecurityConfig {
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
String[] activeProfiles = environment.getActiveProfiles();
final boolean isDevOrTest;
-
+
isDevOrTest = java.util.Arrays.stream(activeProfiles)
.anyMatch(profile -> "dev".equals(profile) || "test".equals(profile) || "h2-test".equals(profile));
-
- logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
+
+ logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
activeProfiles.length > 0 ? String.join(",", activeProfiles) : "default", isDevOrTest);
http
--
2.52.0
From c44478a9cbfc9eb9d271ceb58086397e57742fc8 Mon Sep 17 00:00:00 2001
From: future <1360317836@qq.com>
Date: Wed, 24 Jun 2026 22:27:22 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=99=BB=E5=BD=95?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
gym-manage-api/gym-auth/pom.xml | 74 +----
.../gym/manage/auth/config/AuthConfig.java | 7 -
.../gym/manage/auth/config/SmsProperties.java | 28 +-
.../manage/auth/dto/PhoneCodeLoginDto.java | 3 -
.../gym/manage/auth/dto/PhoneLoginDto.java | 15 +-
.../gym/manage/auth/dto/SendCodeRequest.java | 22 --
.../gym/manage/auth/dto/SmsLoginDto.java | 25 --
.../manage/auth/handler/PhoneAuthHandler.java | 5 +-
.../manage/auth/service/PhoneAuthService.java | 24 +-
.../gym/manage/auth/service/SmsService.java | 35 +--
.../service/impl/PhoneAuthServiceImpl.java | 82 ++----
.../auth/service/impl/SmsServiceImpl.java | 252 +++++++++++-------
.../gym/manage/auth/vo/PhoneLoginVO.java | 3 -
...ot.autoconfigure.AutoConfiguration.imports | 4 -
gym-manage-api/gym-groupCourse/pom.xml | 17 +-
.../gym/manage/member/entity/Member.java | 16 ++
gym-manage-api/manage-app/pom.xml | 2 +-
.../gym/manage/app/config/SystemRouter.java | 3 +-
.../src/main/resources/application-dev.yml | 8 +
.../src/main/resources/member-config.yml | 4 +-
.../gym/manage/common/config/AuthConfig.java | 19 ++
.../gym/manage/common/response/Result.java | 54 ++++
.../gym/manage/common/util/JwtUtil.java | 86 ++++++
...Update_Member_User_For_One_Click_Login.sql | 21 ++
24 files changed, 416 insertions(+), 393 deletions(-)
delete mode 100644 gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
delete mode 100644 gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
delete mode 100644 gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
create mode 100644 gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java
create mode 100644 gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java
create mode 100644 gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java
create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql
diff --git a/gym-manage-api/gym-auth/pom.xml b/gym-manage-api/gym-auth/pom.xml
index 42aa5be..23e8943 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,93 +30,46 @@
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.data
- spring-data-commons
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- cn.hutool
- hutool-all
- 5.8.25
-
-
-
- com.aliyun
- aliyun-java-sdk-dypnsapi
- 1.2.12
+ spring-boot-starter-validation
+
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/SmsService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
index 914f4ff..585074e 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
@@ -1,35 +1,12 @@
package cn.novalon.gym.manage.auth.service;
-/**
- * 短信服务接口
- *
- * @author auto-generated
- * @date 2026-06-20
- */
+import reactor.core.publisher.Mono;
+
public interface SmsService {
- /**
- * 发送短信验证码
- *
- * @param phone 手机号
- * @return 发送结果
- */
- boolean sendVerificationCode(String phone);
+ Mono sendVerificationCode(String phone);
- /**
- * 验证短信验证码
- *
- * @param phone 手机号
- * @param code 验证码
- * @return 验证结果
- */
- boolean verifyCode(String phone, String code);
+ Mono verifyCode(String phone, String code);
- /**
- * 获取验证码(用于测试或特殊场景)
- *
- * @param phone 手机号
- * @return 验证码
- */
- String getVerificationCode(String phone);
-}
+ Mono getVerificationCode(String phone);
+}
\ 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 468f5a1..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,76 +45,32 @@ 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
public Mono sendSmsCode(String phone) {
log.info("发送短信验证码, phone: {}", phone);
- return Mono.fromCallable(() -> smsService.sendVerificationCode(phone));
+ return smsService.sendVerificationCode(phone);
}
@Override
public Mono codeLogin(PhoneCodeLoginDto request) {
log.info("手机号验证码登录, phone: {}", request.getPhone());
- return Mono.fromCallable(() -> smsService.verifyCode(request.getPhone(), request.getCode()))
+ return smsService.verifyCode(request.getPhone(), request.getCode())
.flatMap(verified -> {
if (!verified) {
log.warn("验证码验证失败, phone: {}", request.getPhone());
@@ -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/service/impl/SmsServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
index 2162e13..2a8b630 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
@@ -1,148 +1,198 @@
package cn.novalon.gym.manage.auth.service.impl;
-import cn.hutool.core.util.RandomUtil;
import cn.novalon.gym.manage.auth.config.SmsProperties;
import cn.novalon.gym.manage.auth.service.SmsService;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
-import com.aliyun.dysmsapi20170525.Client;
-import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
-import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
-import com.aliyun.teaopenapi.models.Config;
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
-import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
-/**
- * 短信服务实现类
- *
- * @author auto-generated
- * @date 2026-06-20
- */
@Slf4j
@Service
@RequiredArgsConstructor
public class SmsServiceImpl implements SmsService {
+ private static final long SEND_INTERVAL_SECONDS = 60;
+ private static final long CODE_EXPIRE_SECONDS = 300;
+
private final SmsProperties smsProperties;
private final RedisUtil redisUtil;
+ private final ObjectMapper objectMapper;
@Override
- public boolean sendVerificationCode(String phone) {
+ public Mono sendVerificationCode(String phone) {
log.info("发送短信验证码, phone: {}", phone);
- try {
- // 生成验证码
- String code = generateCode();
+ String rateLimitKey = RedisKeyConstants.SMS_CODE + phone + ":rate_limit";
- // 发送短信
- boolean sent = sendSms(phone, code);
+ return redisUtil.get(rateLimitKey, Long.class)
+ .defaultIfEmpty(0L)
+ .flatMap(lastSendTime -> {
+ long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
- if (sent) {
- // 将验证码存入Redis,5分钟过期
- String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- redisUtil.setWithExpire(cacheKey, code, smsProperties.getCodeExpireSeconds())
- .doOnSuccess(result -> log.info("验证码已缓存, phone: {}, code: {}, expire: {}s",
- phone, code, smsProperties.getCodeExpireSeconds()))
- .block();
+ if (currentTime - lastSendTime < SEND_INTERVAL_SECONDS) {
+ long remainingSeconds = SEND_INTERVAL_SECONDS - (currentTime - lastSendTime);
+ log.warn("发送频率限制, phone: {}, 剩余时间: {}秒", phone, remainingSeconds);
+ return Mono.just(false);
+ }
- log.info("短信验证码发送成功, phone: {}", phone);
- return true;
- }
+ return Mono.fromCallable(() -> {
+ try {
+ IAcsClient client = createClient();
- log.warn("短信验证码发送失败, phone: {}", phone);
- return false;
+ CommonRequest request = new CommonRequest();
+ request.setSysMethod(MethodType.POST);
+ request.setSysDomain("dypnsapi.aliyuncs.com");
+ request.setSysVersion("2017-05-25");
+ request.setSysAction("SendSmsVerifyCode");
+ request.putQueryParameter("PhoneNumber", phone);
+ request.putQueryParameter("SignName", smsProperties.getSignName());
+ request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode());
+ request.putQueryParameter("TemplateParam", "{\"code\":\"##code##\",\"min\":\"5\"}");
+ request.putQueryParameter("ReturnVerifyCode", "true");
- } catch (Exception e) {
- log.error("发送短信验证码异常, phone: {}", phone, e);
- return false;
- }
+ log.info("阿里云号码认证请求参数 - signName: {}, templateCode: {}, templateParam: {}",
+ smsProperties.getSignName(),
+ smsProperties.getTemplateCode(),
+ "{\"code\":\"##code##\",\"min\":\"5\"}");
+
+ CommonResponse response = client.getCommonResponse(request);
+ String responseData = response.getData();
+ log.info("阿里云号码认证原始响应: {}", responseData);
+
+ JsonNode jsonNode = objectMapper.readTree(responseData);
+
+ JsonNode codeNode = jsonNode.get("Code");
+ if (codeNode == null) {
+ log.error("阿里云响应中找不到Code字段, 原始响应: {}", responseData);
+ return false;
+ }
+
+ String code = codeNode.asText();
+
+ if ("OK".equals(code)) {
+ JsonNode requestIdNode = jsonNode.get("RequestId");
+ String requestId = requestIdNode != null ? requestIdNode.asText() : "unknown";
+ log.info("短信验证码发送成功, phone: {}, requestId: {}", phone, requestId);
+
+ // 提取验证码并存入Redis
+ JsonNode modelNode = jsonNode.get("Model");
+ if (modelNode != null) {
+ JsonNode verifyCodeNode = modelNode.get("VerifyCode");
+ if (verifyCodeNode != null) {
+ String verifyCode = verifyCodeNode.asText();
+ String smsCodeKey = RedisKeyConstants.SMS_CODE + phone;
+ redisUtil.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
+ log.info("验证码已存入Redis, phone: {}, key: {}, expire: {}秒", phone, smsCodeKey, CODE_EXPIRE_SECONDS);
+ } else {
+ log.warn("响应中未找到Model.VerifyCode字段, 原始响应: {}", responseData);
+ }
+ } else {
+ log.warn("响应中未找到Model字段, 原始响应: {}", responseData);
+ }
+
+ redisUtil.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
+ return true;
+ }
+
+ JsonNode messageNode = jsonNode.get("Message");
+ String message = messageNode != null ? messageNode.asText() : "unknown";
+ log.error("短信验证码发送失败, phone: {}, code: {}, message: {}", phone, code, message);
+ return false;
+
+ } catch (Exception e) {
+ log.error("发送短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
+ return false;
+ }
+ });
+ });
}
@Override
- public boolean verifyCode(String phone, String code) {
+ public Mono verifyCode(String phone, String code) {
log.info("验证短信验证码, phone: {}", phone);
- try {
- String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- String cachedCode = redisUtil.get(cacheKey, String.class).block();
+ return Mono.fromCallable(() -> {
+ try {
+ IAcsClient client = createClient();
- if (cachedCode == null) {
- log.warn("验证码已过期或不存在, phone: {}", phone);
+ CommonRequest request = new CommonRequest();
+ request.setSysMethod(MethodType.POST);
+ request.setSysDomain("dypnsapi.aliyuncs.com");
+ request.setSysVersion("2017-05-25");
+ request.setSysAction("CheckSmsVerifyCode");
+ request.putQueryParameter("PhoneNumber", phone);
+ request.putQueryParameter("VerifyCode", code);
+
+ log.info("阿里云号码认证核验参数 - phone: {}, code: {}", phone, code);
+
+ CommonResponse response = client.getCommonResponse(request);
+ String responseData = response.getData();
+ log.info("阿里云号码认证核验原始响应: {}", responseData);
+
+ JsonNode jsonNode = objectMapper.readTree(responseData);
+
+ JsonNode codeNode = jsonNode.get("Code");
+ if (codeNode == null) {
+ log.error("阿里云核验响应中找不到Code字段, 原始响应: {}", responseData);
+ return false;
+ }
+
+ String responseCode = codeNode.asText();
+ JsonNode modelNode = jsonNode.get("Model");
+ JsonNode verifyResultNode = modelNode != null ? modelNode.get("VerifyResult") : null;
+ boolean verifyResult = verifyResultNode != null && "PASS".equals(verifyResultNode.asText());
+
+ if ("OK".equals(responseCode) && verifyResult) {
+ log.info("验证码验证成功, phone: {}", phone);
+ return true;
+ }
+
+ JsonNode messageNode = jsonNode.get("Message");
+ String message = messageNode != null ? messageNode.asText() : "unknown";
+ log.warn("验证码验证失败, phone: {}, code: {}, message: {}, result: {}",
+ phone, responseCode, message, verifyResult);
+ return false;
+
+ } catch (Exception e) {
+ log.error("验证短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
return false;
}
-
- if (cachedCode.equals(code)) {
- // 验证成功后删除验证码
- redisUtil.delete(cacheKey).block();
- log.info("验证码验证成功, phone: {}", phone);
- return true;
- }
-
- log.warn("验证码不匹配, phone: {}, input: {}, cached: {}", phone, code, cachedCode);
- return false;
-
- } catch (Exception e) {
- log.error("验证短信验证码异常, phone: {}", phone, e);
- return false;
- }
+ });
}
@Override
- public String getVerificationCode(String phone) {
+ public Mono getVerificationCode(String phone) {
try {
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- return redisUtil.get(cacheKey, String.class).block();
+ return redisUtil.get(cacheKey, String.class);
} catch (Exception e) {
log.error("获取验证码异常, phone: {}", phone, e);
- return null;
+ return Mono.empty();
}
}
- /**
- * 生成验证码
- */
- private String generateCode() {
- return RandomUtil.randomNumbers(smsProperties.getCodeLength());
+ private IAcsClient createClient() throws ClientException {
+ IClientProfile profile = DefaultProfile.getProfile(
+ "cn-hangzhou",
+ smsProperties.getAccessKeyId(),
+ smsProperties.getAccessKeySecret());
+ DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "Dypnsapi", "dypnsapi.aliyuncs.com");
+ return new DefaultAcsClient(profile);
}
-
- /**
- * 发送短信
- */
- private boolean sendSms(String phone, String code) throws Exception {
- log.info("调用阿里云短信API发送短信, phone: {}, code: {}", phone, code);
-
- // 创建阿里云短信客户端
- Config config = new Config()
- .setAccessKeyId(smsProperties.getAccessKeyId())
- .setAccessKeySecret(smsProperties.getAccessKeySecret())
- .setEndpoint("dysmsapi.aliyuncs.com");
-
- Client client = new Client(config);
-
- // 构建发送短信请求
- SendSmsRequest request = new SendSmsRequest()
- .setPhoneNumbers(phone)
- .setSignName(smsProperties.getSignName())
- .setTemplateCode(smsProperties.getTemplateCode())
- .setTemplateParam("{\"code\":\"" + code + "\"}");
-
- // 发送短信
- SendSmsResponse response = client.sendSms(request);
-
- log.info("阿里云短信API响应, phone: {}, code: {}, message: {}",
- phone, response.getBody().getCode(), response.getBody().getMessage());
-
- // 判断发送结果
- if ("OK".equals(response.getBody().getCode())) {
- return true;
- }
-
- log.error("阿里云短信发送失败, phone: {}, code: {}, message: {}",
- phone, response.getBody().getCode(), response.getBody().getMessage());
- return false;
- }
-}
+}
\ 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/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml
index 6e95ea5..7a15e65 100644
--- a/gym-manage-api/manage-app/pom.xml
+++ b/gym-manage-api/manage-app/pom.xml
@@ -58,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 46b6a66..d14149a 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 05ce7cb..5338737 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/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-微信授权';
--
2.52.0
From 58feae78b7256fc3f7203149e12a570fefd99799 Mon Sep 17 00:00:00 2001
From: future <1360317836@qq.com>
Date: Wed, 24 Jun 2026 22:32:38 +0800
Subject: [PATCH 6/6] =?UTF-8?q?=E5=90=88=E5=B9=B6dev=E5=88=86=E6=94=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
gym-manage-api/KeyAnalyzer.java | 202 --------------------------------
1 file changed, 202 deletions(-)
delete mode 100644 gym-manage-api/KeyAnalyzer.java
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
--
2.52.0