处理了微信登录

This commit is contained in:
wucongxing8150 2025-06-17 11:26:54 +08:00
parent b516ddf5df
commit 6eab966eab
11 changed files with 383 additions and 31 deletions

21
pom.xml
View File

@ -98,6 +98,27 @@
<version>3.17.4</version>
</dependency>
<!-- 阿里云短信服务 SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
<!-- 阿里云 OpenAPI 基础依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.3.9</version>
</dependency>
<!-- 阿里云 Tea 工具库 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-util</artifactId>
<version>0.2.13</version>
</dependency>
<!-- JWT 解析库 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>

View File

@ -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;
}

View File

@ -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<String, String> redisTemplate;
@Resource
private DySms dySms;
// 登陆
@PostMapping("/login/wechat/mobile")
public Response<LoginDto> loginWechatMobile(@Validated({LoginRequest.Login.class}) @ModelAttribute LoginRequest request) {
// 微信手机号授权登录
try {
// 获取手机号
// 获取用户openid
WxMaPhoneNumberInfo phoneInfo = wxMaServiceUtils.getPhoneNumber(request.getPhone_code());
if (phoneInfo == null) {
return Response.error("微信授权失败");
}
// 临时测试使用
String phone = "18221234167";
if (phoneInfo.getPurePhoneNumber() == null) {
return Response.error("微信授权失败");
}
// 获取用户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(phone);
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<T> 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<LoginDto> 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());
// }
}
}

View File

@ -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<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
@ -34,4 +33,31 @@ public class RedisConfiguration {
return template;
}
@Bean
@Qualifier("prodRedisTemplate")
public RedisTemplate<String, Object> 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<String, Object> 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;
}
}

View File

@ -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<String, Object> 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);
}
}
}

View File

@ -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<String, Object> redisTemplate;
private final RedisTemplate<String, Object> prodRedisTemplate;
public WxMaRedisConfig(StringRedisTemplate redisTemplate) {
public WxMaRedisConfig(RedisTemplate<String, Object> redisTemplate, RedisTemplate<String, Object> 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);
}
}

View File

@ -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<String, Object> devRedis;
private final RedisTemplate<String, Object> prodRedis;
@Resource
private WxMaConfig wxMaConfig;
public WxMaServiceConfig(
@Qualifier("redisTemplate") RedisTemplate<String, Object> devRedis,
@Qualifier("prodRedisTemplate") RedisTemplate<String, Object> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -106,7 +106,6 @@ public class UserService {
return g;
}
/**
* 获取app用户数据
* @return UserModel
@ -231,4 +230,6 @@ public class UserService {
return basicHospital;
}
}

View File

@ -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 # 私钥