From 5adb63f612da8f70fd9f0c11043a5d181d116604 Mon Sep 17 00:00:00 2001 From: future <1360317836@qq.com> Date: Thu, 25 Jun 2026 00:53:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8Edev=E9=87=8D=E7=BB=84=E7=99=BB?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gym-manage-api/gym-auth/pom.xml | 8 +- .../gym/manage/auth/service/SmsService.java | 10 +- .../service/impl/PhoneAuthServiceImpl.java | 4 +- .../auth/service/impl/SmsServiceImpl.java | 252 +++++++++++------- gym-manage-api/gym-checkIn/pom.xml | 5 + .../gym/manage/app/config/SystemRouter.java | 17 +- .../src/main/resources/application-dev.yml | 14 +- .../src/main/resources/application-local.yml | 2 +- .../src/main/resources/application-prod.yml | 2 +- .../src/main/resources/application.yml | 22 +- 10 files changed, 192 insertions(+), 144 deletions(-) diff --git a/gym-manage-api/gym-auth/pom.xml b/gym-manage-api/gym-auth/pom.xml index 42aa5be..bff30df 100644 --- a/gym-manage-api/gym-auth/pom.xml +++ b/gym-manage-api/gym-auth/pom.xml @@ -68,7 +68,13 @@ hutool-all 5.8.25 - + + + com.aliyun + dypnsapi20170525 + 1.0.6 + + com.aliyun aliyun-java-sdk-dypnsapi 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..84ecabc 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,5 +1,7 @@ package cn.novalon.gym.manage.auth.service; +import reactor.core.publisher.Mono; + /** * 短信服务接口 * @@ -14,7 +16,7 @@ public interface SmsService { * @param phone 手机号 * @return 发送结果 */ - boolean sendVerificationCode(String phone); + Mono sendVerificationCode(String phone); /** * 验证短信验证码 @@ -23,7 +25,7 @@ public interface SmsService { * @param code 验证码 * @return 验证结果 */ - boolean verifyCode(String phone, String code); + Mono verifyCode(String phone, String code); /** * 获取验证码(用于测试或特殊场景) @@ -31,5 +33,5 @@ public interface SmsService { * @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..4bd5050 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 @@ -116,14 +116,14 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { @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()); 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..c04c196 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-checkIn/pom.xml b/gym-manage-api/gym-checkIn/pom.xml index c23489b..485be54 100644 --- a/gym-manage-api/gym-checkIn/pom.xml +++ b/gym-manage-api/gym-checkIn/pom.xml @@ -118,6 +118,11 @@ hutool-all 5.8.25 + + org.projectlombok + lombok + provided + com.google.zxing core 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 9ec7cd4..482d931 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 @@ -17,7 +17,6 @@ import cn.novalon.gym.manage.member.handler.MemberHandler; import cn.novalon.gym.manage.member.handler.WechatAuthHandler; import cn.novalon.gym.manage.notify.handler.SysNoticeHandler; import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler; -import cn.novalon.gym.manage.payment.handler.PaymentHandler; import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler; import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler; import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler; @@ -79,8 +78,7 @@ public class SystemRouter { CourseLabelHandler courseLabelHandler, CheckInHandler checkInHandler, DataStatisticsHandler dataStatisticsHandler, - PhoneAuthHandler phoneAuthHandler, - PaymentHandler paymentHandler) { + PhoneAuthHandler phoneAuthHandler) { return route() // ========== 诊断路由 ========== @@ -362,19 +360,6 @@ public class SystemRouter { .GET("/api/datacount/history", dataStatisticsHandler::queryHistoricalStatistics) .GET("/api/datacount/export", dataStatisticsHandler::exportStatistics) - // ======================================== - // ========== 支付模块路由 ================= - // ======================================== - - // ===== 支付宝App支付 ===== - .POST("/api/payment/create", paymentHandler::createPayment) - .GET("/api/payment/{orderId}", paymentHandler::getPaymentStatus) - .POST("/api/payment/{orderId}/refund", paymentHandler::refundPayment) - .POST("/api/payment/notify", paymentHandler::alipayNotify) - - // ===== 支付宝扫码支付 ===== - .POST("/api/payment/qrcode/create", paymentHandler::createQrCodePayment) - .build(); } } 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 c696a05..51a74d2 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 @@ -2,9 +2,9 @@ spring: cache: type: none r2dbc: - url: r2dbc:postgresql://localhost:55432/manage_system - username: novalon - password: novalon123 + url: r2dbc:postgresql://localhost:5432/manage_system + username: postgres + password: 123456 pool: initial-size: 5 max-size: 20 @@ -12,10 +12,10 @@ spring: max-life-time: 30m acquire-timeout: 3s flyway: - url: jdbc:postgresql://localhost:55432/manage_system - user: novalon - password: novalon123 - enabled: true + url: jdbc:postgresql://localhost:5432/manage_system + user: postgres + password: 123456 + enabled: false locations: classpath:db/migration baseline-on-migrate: true validate-on-migrate: false diff --git a/gym-manage-api/manage-app/src/main/resources/application-local.yml b/gym-manage-api/manage-app/src/main/resources/application-local.yml index f5b8e75..93be5dd 100644 --- a/gym-manage-api/manage-app/src/main/resources/application-local.yml +++ b/gym-manage-api/manage-app/src/main/resources/application-local.yml @@ -19,7 +19,7 @@ spring: password: 123456 driver-class-name: org.postgresql.Driver flyway: - enabled: true + enabled: false locations: classpath:db/migration baseline-on-migrate: true baseline-version: 0 diff --git a/gym-manage-api/manage-app/src/main/resources/application-prod.yml b/gym-manage-api/manage-app/src/main/resources/application-prod.yml index 978f29c..c3db1b1 100644 --- a/gym-manage-api/manage-app/src/main/resources/application-prod.yml +++ b/gym-manage-api/manage-app/src/main/resources/application-prod.yml @@ -4,7 +4,7 @@ spring: username: ${DB_USERNAME} password: ${DB_PASSWORD} flyway: - enabled: true + enabled: false logging: level: diff --git a/gym-manage-api/manage-app/src/main/resources/application.yml b/gym-manage-api/manage-app/src/main/resources/application.yml index 7275251..201c128 100644 --- a/gym-manage-api/manage-app/src/main/resources/application.yml +++ b/gym-manage-api/manage-app/src/main/resources/application.yml @@ -15,9 +15,9 @@ spring: exclude: - org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration r2dbc: - url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system} - username: ${DB_USERNAME:novalon} - password: ${DB_PASSWORD:novalon123} + url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system} + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:123456} pool: initial-size: 10 max-size: 50 @@ -25,12 +25,12 @@ spring: max-life-time: 1h acquire-timeout: 5s datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system} - username: ${DB_USERNAME:novalon} - password: ${DB_PASSWORD:novalon123} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system} + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:123456} driver-class-name: org.postgresql.Driver flyway: - enabled: true + enabled: false locations: classpath:db/migration baseline-on-migrate: true baseline-version: 0 @@ -98,9 +98,9 @@ springdoc: alibaba: cloud: sms: - access-key-id: ${ALIBABA_ACCESS_KEY_ID:} - access-key-secret: ${ALIBABA_ACCESS_KEY_SECRET:} - sign-name: ${ALIBABA_SMS_SIGN_NAME:} - template-code: ${ALIBABA_SMS_TEMPLATE_CODE:} + access-key-id: LTAI5t8GhorWLu5WkEx8MDZz + access-key-secret: jNDwb9IHvTIESUezLYHZRT5c5NEaCz + sign-name: 云渚科技验证平台 + template-code: 100001 code-length: 6 code-expire-seconds: 300