diff --git a/pom.xml b/pom.xml
index f1f5a7b..07ac7a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,27 @@
3.17.4
+
+
+ com.aliyun
+ dysmsapi20170525
+ 2.0.23
+
+
+
+
+ com.aliyun
+ tea-openapi
+ 0.3.9
+
+
+
+
+ com.aliyun
+ tea-util
+ 0.2.13
+
+
io.jsonwebtoken
diff --git a/src/main/java/com/example/caseData/config/DySmsConfig.java b/src/main/java/com/example/caseData/config/DySmsConfig.java
new file mode 100644
index 0000000..c8865ff
--- /dev/null
+++ b/src/main/java/com/example/caseData/config/DySmsConfig.java
@@ -0,0 +1,16 @@
+package com.example.caseData.config;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+@Getter
+public class DySmsConfig {
+ @Value("${dysms.access-key}")
+ private String accessKey;
+
+ @Value("${dysms.access-secret}")
+ private String accessSecret;
+
+}
diff --git a/src/main/java/com/example/caseData/controller/PublicController.java b/src/main/java/com/example/caseData/controller/PublicController.java
index bf06aee..923bb9c 100644
--- a/src/main/java/com/example/caseData/controller/PublicController.java
+++ b/src/main/java/com/example/caseData/controller/PublicController.java
@@ -1,23 +1,33 @@
package com.example.caseData.controller;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.example.caseData.common.Response;
import com.example.caseData.config.EnvConfig;
+import com.example.caseData.dto.T;
import com.example.caseData.dto.publicDto.GetOssSignDto;
import com.example.caseData.dto.publicDto.LoginDto;
import com.example.caseData.dto.user.UserDto;
+import com.example.caseData.extend.aliyun.DySms;
import com.example.caseData.extend.aliyun.Oss;
-import com.example.caseData.request.publicRequest.GetOssSignRequest;
-import com.example.caseData.request.publicRequest.LoginHcpRequest;
-import com.example.caseData.request.publicRequest.LoginRequest;
+import com.example.caseData.extend.weChat.WxMaServiceUtils;
+import com.example.caseData.request.publicRequest.*;
import com.example.caseData.request.UserRequest.UserRequest;
import com.example.caseData.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import java.time.Duration;
+import java.util.Map;
import java.util.Objects;
+import java.util.Random;
+@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class PublicController {
@@ -30,20 +40,49 @@ public class PublicController {
@Resource
private EnvConfig envConfig;
+ private final WxMaServiceUtils wxMaServiceUtils;
+
+ @Resource
+ private RedisTemplate redisTemplate;
+
+ @Resource
+ private DySms dySms;
+
// 登陆
@PostMapping("/login/wechat/mobile")
public Response loginWechatMobile(@Validated({LoginRequest.Login.class}) @ModelAttribute LoginRequest request) {
// 微信手机号授权登录
+ try {
// 获取手机号
+ WxMaPhoneNumberInfo phoneInfo = wxMaServiceUtils.getPhoneNumber(request.getPhone_code());
+ if (phoneInfo == null) {
+ return Response.error("微信授权失败");
+ }
+
+ if (phoneInfo.getPurePhoneNumber() == null) {
+ return Response.error("微信授权失败");
+ }
+
// 获取用户openid
+ WxMaJscode2SessionResult wxInfoData = wxMaServiceUtils.getSessionInfo(request.getWx_code());
+ if (wxInfoData == null) {
+ return Response.error("微信授权失败");
+ }
- // 临时测试使用
- String phone = "18221234167";
+ if (wxInfoData.getOpenid() == null) {
+ return Response.error("微信授权失败");
+ }
- // 用户登陆
- LoginDto g = userService.UserLoginWithMobile(phone);
+ if (wxInfoData.getSessionKey() == null) {
+ return Response.error("微信授权失败");
+ }
- return Response.success(g);
+ // 用户登陆
+ LoginDto g = userService.UserLoginWithMobile(phoneInfo.getPurePhoneNumber());
+ return Response.success(g);
+ } catch (Exception e) {
+ return Response.error(e.getMessage());
+ }
}
// 登陆
@@ -79,4 +118,96 @@ public class PublicController {
GetOssSignDto g = Oss.getOssSign(ossPath);
return Response.success(g);
}
+
+ // 获取验证码
+ @PostMapping("/code/phone")
+ public Response GetPhoneCode(@Validated() @ModelAttribute GetPhoneCodeRequest request) {
+ String templateCode = "";
+ String scene = "注册验证码";
+
+
+ // 手机号登录
+ if (request.getScene() == 1){
+ templateCode = "SMS_215344868";
+ scene = "注册验证码";
+ }
+
+ // 限流 key
+ String limitKey = "sms:limit:" + request.getPhone();
+
+ // 验证码缓存 key
+ String codeKey = "sms:code:" + request.getPhone();
+
+ // 获取当前请求次数
+ String countStr = redisTemplate.opsForValue().get(limitKey);
+ int count = countStr != null ? Integer.parseInt(countStr) : 0;
+
+ if (count > 3) {
+ return Response.error("验证码请求过于频繁,请稍后再试");
+ }
+
+ // 生成 4 位数字验证码
+ String code = String.format("%04d", new Random().nextInt(10000));
+
+ try {
+ dySms.sendSms(
+ request.getPhone(),
+ templateCode,
+ scene,
+ Map.of("code", code)
+ );
+ } catch (Exception e) {
+ return Response.error(e.getMessage());
+ }
+
+ // 缓存验证码(有效期 5 分钟)
+ redisTemplate.opsForValue().set(codeKey, code, Duration.ofMinutes(5));
+
+ // 缓存请求次数
+ if (count == 0) {
+ // 初次请求,设置有效期为 5 分钟
+ redisTemplate.opsForValue().set(limitKey, "1", Duration.ofMinutes(5));
+ } else {
+ redisTemplate.opsForValue().increment(limitKey);
+ }
+
+ return Response.success();
+ }
+
+ // 登陆
+ @PostMapping("/login/phone")
+ public Response loginPhone(@Validated() @ModelAttribute LoginPhoneRequest request) {
+ // 验证码缓存 key
+ if (!Objects.equals(envConfig.getActive(), "dev")){
+ String codeKey = "sms:code:" + request.getPhone();
+ String code = redisTemplate.opsForValue().get(codeKey);
+ if (!Objects.equals(request.getCode(), code)){
+ return Response.error("验证码错误");
+ }
+ }
+
+// try {
+ // 获取用户openid
+ WxMaJscode2SessionResult wxInfoData = wxMaServiceUtils.getSessionInfo(request.getWx_code());
+ if (wxInfoData == null) {
+ return Response.error("微信授权失败");
+ }
+
+ if (wxInfoData.getOpenid() == null) {
+ return Response.error("微信授权失败");
+ }
+
+ if (wxInfoData.getSessionKey() == null) {
+ return Response.error("微信授权失败");
+ }
+
+ // 用户登陆
+ LoginDto g = userService.UserLoginWithMobile(request.getPhone());
+ return Response.success(g);
+
+// } catch (Exception e) {
+// return Response.error(e.getMessage());
+// }
+ }
+
}
diff --git a/src/main/java/com/example/caseData/core/RedisConfiguration.java b/src/main/java/com/example/caseData/core/RedisConfiguration.java
index 951be81..1289254 100644
--- a/src/main/java/com/example/caseData/core/RedisConfiguration.java
+++ b/src/main/java/com/example/caseData/core/RedisConfiguration.java
@@ -1,26 +1,25 @@
package com.example.caseData.core;
import com.example.caseData.config.RedisConfig;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+import org.springframework.data.redis.connection.RedisPassword;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.StringRedisSerializer;
-import java.time.Duration;
-
@Configuration
+@RequiredArgsConstructor
public class RedisConfiguration {
-// private final RedisConfig redisConfig; // 自动注入你的配置类
-//
-// public RedisConfiguration(RedisConfig redisConfig) {
-// this.redisConfig = redisConfig;
-// }
-
+ private final RedisConfig redisConfig; // 自动注入你的配置类
@Bean
+ @Qualifier("redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
@@ -34,4 +33,31 @@ public class RedisConfiguration {
return template;
}
+
+ @Bean
+ @Qualifier("prodRedisTemplate")
+ public RedisTemplate prodRedisTemplate(RedisConnectionFactory connectionFactory) {
+ RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
+ configuration.setHostName(redisConfig.getProdHost());
+ configuration.setPort(redisConfig.getProdPort());
+ configuration.setDatabase(Integer.parseInt(redisConfig.getProdDatabase()));
+ if (redisConfig.getProdPassword() != null && !redisConfig.getProdPassword().isEmpty()) {
+ configuration.setPassword(RedisPassword.of(redisConfig.getProdPassword()));
+ }
+
+ LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
+ factory.afterPropertiesSet();
+
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(factory);
+
+ // 使用String序列化方式
+ StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+ template.setKeySerializer(stringRedisSerializer);
+ template.setValueSerializer(stringRedisSerializer);
+ template.setHashKeySerializer(stringRedisSerializer);
+ template.setHashValueSerializer(stringRedisSerializer);
+
+ return template;
+ }
}
diff --git a/src/main/java/com/example/caseData/extend/aliyun/DySms.java b/src/main/java/com/example/caseData/extend/aliyun/DySms.java
new file mode 100644
index 0000000..27a750a
--- /dev/null
+++ b/src/main/java/com/example/caseData/extend/aliyun/DySms.java
@@ -0,0 +1,77 @@
+package com.example.caseData.extend.aliyun;
+
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.example.caseData.config.DySmsConfig;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Slf4j
+@Component
+public class DySms {
+ @Resource
+ private DySmsConfig dySmsConfig;
+
+ private static final String endpoint = "dysmsapi.aliyuncs.com";
+ private static final String signName = "肝胆相照";
+
+ /**
+ * 创建短信客户端
+ */
+ private Client createClient() throws Exception {
+ Config config = new Config()
+ .setAccessKeyId(dySmsConfig.getAccessKey())
+ .setAccessKeySecret(dySmsConfig.getAccessSecret())
+ .setEndpoint(endpoint);
+ return new Client(config);
+ }
+
+ /**
+ * 发送短信
+ *
+ * @param phoneNumber 手机号
+ * @param templateCode 模板CODE
+ * @param sceneDesc 场景说明,用于日志或调试
+ * @param templateParam 模板参数
+ */
+ public void sendSms(String phoneNumber, String templateCode, String sceneDesc, Map templateParam) {
+ try {
+ Client client = createClient();
+ ObjectMapper objectMapper = new ObjectMapper();
+ String paramJson = objectMapper.writeValueAsString(templateParam);
+
+ SendSmsRequest request = new SendSmsRequest()
+ .setPhoneNumbers(phoneNumber)
+ .setSignName(signName)
+ .setTemplateCode(templateCode)
+ .setTemplateParam(paramJson);
+
+ // 只用标准的RuntimeOptions,不加任何自定义字段
+ RuntimeOptions runtime = new RuntimeOptions();
+
+ SendSmsResponse response = client.sendSmsWithOptions(request, runtime);
+
+ if (response.getBody() == null || !"OK".equals(response.getBody().getCode())) {
+ log.error("短信发送失败,手机号:{},场景:{},返回信息:{}", phoneNumber, sceneDesc, response.getBody() != null ? response.getBody().getMessage() : "无返回体");
+ throw new RuntimeException("短信发送失败: " + (response.getBody() != null ? response.getBody().getMessage() : "无返回体"));
+ } else {
+ log.info("短信发送成功,手机号:{},场景:{},返回信息:{}", phoneNumber, sceneDesc, response.getBody().getMessage());
+ }
+
+ } catch (TeaException e) {
+ log.error("阿里云短信发送异常,手机号:{},场景:{},错误信息:{}", phoneNumber, sceneDesc, e.getMessage(), e);
+ throw new RuntimeException("阿里云短信发送异常", e);
+ } catch (Exception e) {
+ log.error("短信发送异常,手机号:{},场景:{},错误信息:{}", phoneNumber, sceneDesc, e.getMessage(), e);
+ throw new RuntimeException("短信发送异常", e);
+ }
+ }
+}
diff --git a/src/main/java/com/example/caseData/extend/weChat/WxMaRedisConfig.java b/src/main/java/com/example/caseData/extend/weChat/WxMaRedisConfig.java
index 37410b1..c9a141a 100644
--- a/src/main/java/com/example/caseData/extend/weChat/WxMaRedisConfig.java
+++ b/src/main/java/com/example/caseData/extend/weChat/WxMaRedisConfig.java
@@ -2,35 +2,50 @@ package com.example.caseData.extend.weChat;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
@Slf4j
public class WxMaRedisConfig extends WxMaDefaultConfigImpl {
- private final StringRedisTemplate redisTemplate;
+ private final RedisTemplate redisTemplate;
+ private final RedisTemplate prodRedisTemplate;
- public WxMaRedisConfig(StringRedisTemplate redisTemplate) {
+ public WxMaRedisConfig(RedisTemplate redisTemplate, RedisTemplate prodRedisTemplate) {
this.redisTemplate = redisTemplate;
+ this.prodRedisTemplate = prodRedisTemplate;
}
@Override
public String getAccessToken() {
String redisKey = "wx:ma:access_token:" + this.getAppid();
- String token = redisTemplate.opsForValue().get(redisKey);
+ String token = (String) redisTemplate.opsForValue().get(redisKey);
+ if (token != null) {
+ return token;
+ }
+
+ // 2. 再尝试从 prodRedis 获取
+ token = (String) prodRedisTemplate.opsForValue().get(redisKey);
if (token != null) {
return token;
}
synchronized (this) {
- token = redisTemplate.opsForValue().get(redisKey);
+ token = (String) redisTemplate.opsForValue().get(redisKey);
+ if (token != null) return token;
+
+ token = (String) prodRedisTemplate.opsForValue().get(redisKey);
if (token != null) return token;
try {
String newToken = super.getAccessToken(); // 强制刷新
- redisTemplate.opsForValue().set(redisKey, newToken, this.getExpiresTime() - 200L, TimeUnit.SECONDS);
+ long expireSeconds = this.getExpiresTime() - 200L;
+
+ redisTemplate.opsForValue().set(redisKey, newToken, expireSeconds, TimeUnit.SECONDS);
+ prodRedisTemplate.opsForValue().set(redisKey, newToken, expireSeconds, TimeUnit.SECONDS);
return newToken;
} catch (Exception e) {
log.error("获取access_token失败", e);
@@ -42,6 +57,9 @@ public class WxMaRedisConfig extends WxMaDefaultConfigImpl {
@Override
public void updateAccessToken(String accessToken, int expiresInSeconds) {
String redisKey = "wx:ma:access_token:" + this.getAppid();
- redisTemplate.opsForValue().set(redisKey, accessToken, expiresInSeconds - 200L, TimeUnit.SECONDS);
+ long expireSeconds = Math.max(expiresInSeconds - 200L, 1L);
+
+ redisTemplate.opsForValue().set(redisKey, accessToken, expireSeconds, TimeUnit.SECONDS);
+ prodRedisTemplate.opsForValue().set(redisKey, accessToken, expireSeconds, TimeUnit.SECONDS);
}
}
\ No newline at end of file
diff --git a/src/main/java/com/example/caseData/extend/weChat/WxMaServiceConfig.java b/src/main/java/com/example/caseData/extend/weChat/WxMaServiceConfig.java
index c2ffc40..6efcc26 100644
--- a/src/main/java/com/example/caseData/extend/weChat/WxMaServiceConfig.java
+++ b/src/main/java/com/example/caseData/extend/weChat/WxMaServiceConfig.java
@@ -3,20 +3,42 @@ package com.example.caseData.extend.weChat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import com.example.caseData.config.WxMaConfig;
+import com.example.caseData.service.RewardPointService;
+import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class WxMaServiceConfig {
+ private final RedisTemplate devRedis;
+ private final RedisTemplate prodRedis;
+
+ @Resource
+ private WxMaConfig wxMaConfig;
+
+ public WxMaServiceConfig(
+ @Qualifier("redisTemplate") RedisTemplate devRedis,
+ @Qualifier("prodRedisTemplate") RedisTemplate prodRedis
+ ) {
+ this.devRedis = devRedis;
+ this.prodRedis = prodRedis;
+ }
+
@Bean
- public WxMaService wxMaService(WxMaConfig config, StringRedisTemplate redisTemplate) {
- WxMaRedisConfig redisConfig = new WxMaRedisConfig(redisTemplate);
- redisConfig.setAppid(config.getAppid());
- redisConfig.setSecret(config.getSecret());
+ public WxMaService wxMaService() {
+ WxMaRedisConfig redisConfig = new WxMaRedisConfig(devRedis, prodRedis);
+ redisConfig.setAppid(wxMaConfig.getAppid());
+ redisConfig.setSecret(wxMaConfig.getSecret());
WxMaServiceImpl service = new WxMaServiceImpl();
service.setWxMaConfig(redisConfig);
return service;
}
+
+
+
+
+
}
diff --git a/src/main/java/com/example/caseData/request/publicRequest/GetPhoneCodeRequest.java b/src/main/java/com/example/caseData/request/publicRequest/GetPhoneCodeRequest.java
new file mode 100644
index 0000000..24c890c
--- /dev/null
+++ b/src/main/java/com/example/caseData/request/publicRequest/GetPhoneCodeRequest.java
@@ -0,0 +1,19 @@
+package com.example.caseData.request.publicRequest;
+
+import jakarta.validation.constraints.*;
+import lombok.Data;
+
+@Data
+public class GetPhoneCodeRequest {
+ @NotNull(message = "错误请求")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
+
+ /**
+ * 场景值:1登录
+ */
+ @NotNull(message = "错误请求")
+ @Min(value = 1, message = "错误请求")
+ @Max(value = 1, message = "错误请求")
+ private Integer scene;
+}
diff --git a/src/main/java/com/example/caseData/request/publicRequest/LoginPhoneRequest.java b/src/main/java/com/example/caseData/request/publicRequest/LoginPhoneRequest.java
new file mode 100644
index 0000000..e2b6746
--- /dev/null
+++ b/src/main/java/com/example/caseData/request/publicRequest/LoginPhoneRequest.java
@@ -0,0 +1,16 @@
+package com.example.caseData.request.publicRequest;
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Data
+public class LoginPhoneRequest {
+ @NotEmpty(message = "错误请求")
+ private String phone;
+
+ @NotEmpty(message = "错误请求")
+ private String code;
+
+ @NotEmpty(message = "错误请求")
+ private String wx_code;
+}
diff --git a/src/main/java/com/example/caseData/service/UserService.java b/src/main/java/com/example/caseData/service/UserService.java
index b335e99..d014533 100644
--- a/src/main/java/com/example/caseData/service/UserService.java
+++ b/src/main/java/com/example/caseData/service/UserService.java
@@ -106,7 +106,6 @@ public class UserService {
return g;
}
-
/**
* 获取app用户数据
* @return UserModel
@@ -231,4 +230,6 @@ public class UserService {
return basicHospital;
}
+
+
}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index bfee80e..71b3cbf 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -32,7 +32,7 @@ spring:
host: '139.155.127.177'
port: 30002
password: gdxz2022&dj.
- database: 9
+ database: 11
# MyBatis-Plus 配置
@@ -50,6 +50,11 @@ oss:
endpoint: oss-cn-beijing.aliyuncs.com
custom-domain-name: https://caseplatform.oss-cn-beijing.aliyuncs.com
+# [阿里大鱼短信]
+dysms:
+ access-key: LTAI4GGygjsKhyBwvvC3CghV
+ access-secret: rcx7lO9kQxG10m8NqNPEfEtT9IS8EI
+
# jwt配置
jwt:
sign-key: 123456899 # 私钥