1.添加手机号验证码登录

This commit is contained in:
sh
2026-04-09 21:58:22 +08:00
parent fd4ab146e0
commit 06b8a4f128
6 changed files with 363 additions and 1 deletions

View File

@@ -84,6 +84,27 @@ public class SysLoginController
return ajax;
}
/**
* 通过微信获取手机号和验证码
* @param loginBody
* @return
*/
@PostMapping("/app/appWxphone")
public AjaxResult appWxphone(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
ajax=loginService.getWxPhone(loginBody);
return ajax;
}
@PostMapping("/app/appLoginPhone")
public AjaxResult appLoginPhone(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
ajax=loginService.appLoginTwo(loginBody);
return ajax;
}
/**
* 一体机身份证登录
* @param loginBody

View File

@@ -165,6 +165,15 @@ wx:
appid: wx4aa34488b965a331
secret: 558780ecc2750f87e556b0e5496773c9
#短信服务
sms:
#API密钥账号
secretName: kszhjyrcjt
#API密钥
secretKey: Dwhc9c0IiHecvC5D
#短信模板ID
templateId: 37446
#统一门户认证
oauth:
#客户端的ID

View File

@@ -0,0 +1,85 @@
package com.ruoyi.common.config;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SmsRequestDTO;
import com.ruoyi.common.utils.aliyun.AliyunNlsUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class SmsRequestClient {
private static final int LIMIT = 10; // 每秒允许10次请求
private static final long REFRESH_PERIOD = 1000; // 1秒刷新
private final AtomicInteger tokenBucket = new AtomicInteger(LIMIT);
private final AtomicLong lastRefreshTime = new AtomicLong(System.currentTimeMillis());
/**
* API密钥账号
*/
@Value("${sms.secretName}")
private String secretName;
/**
* API密钥
*/
@Value("${sms.secretKey}")
private String secretKey;
/**
* 短信模板ID
*/
@Value("${sms.templateId}")
private String templateId;
/**
* 发送短信
* @param requestDTO
* @return
*/
public AjaxResult sendSms(SmsRequestDTO requestDTO){
if (!tryAcquireToken()) {
return AjaxResult.error("发送失败:当前发送人数过多,请稍后重试");
}
//参数
requestDTO.setSecretName(secretName);
requestDTO.setSecretKey(secretKey);
requestDTO.setTemplateId(templateId);
String msg="";
try {
String json= JSON.toJSONString(requestDTO);
String result= HttpUtils.snedSmsPost(AliyunNlsUtils.getSmsUrl(),json);
JSONObject responseJson = JSONObject.parseObject(result);
System.out.println("调用返回===="+responseJson);
msg = responseJson.getString("msg");
System.out.println("返回消息"+msg);
if (!"0".equals(responseJson.getString("code"))) {
msg="发送失败:" + msg;
return AjaxResult.error(msg);
}else{
msg="发送成功:" + msg;
return AjaxResult.success(msg);
}
}catch (Exception e){
return AjaxResult.error(e.getMessage());
}
}
private boolean tryAcquireToken() {
long now = System.currentTimeMillis();
// 刷新令牌桶
if (now - lastRefreshTime.get() >= REFRESH_PERIOD) {
tokenBucket.set(LIMIT);
lastRefreshTime.set(now);
}
// 尝试获取令牌
return tokenBucket.decrementAndGet() >= 0;
}
}

View File

@@ -47,7 +47,29 @@ public class LoginBody
/**
* 企业类型
*/
public String orgType;
private String orgType;
/**
* 手机号验证码
*/
private String smsCode;
/**
* 手机号
*/
private String phone;
/**
* 微信openid
*/
private String openid;
/**
* 微信unionid
*/
private String unionid;
public String getUsername()
{
@@ -128,4 +150,36 @@ public class LoginBody
public void setOrgType(String orgType) {
this.orgType = orgType;
}
public String getSmsCode() {
return smsCode;
}
public void setSmsCode(String smsCode) {
this.smsCode = smsCode;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getUnionid() {
return unionid;
}
public void setUnionid(String unionid) {
this.unionid = unionid;
}
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.common.core.redis;
public class RedisUtils {
/**
* 发送短信key
*/
public static final String SMS_CODE_KEY="login:sms:code:";
/**
* 短信lock
*/
public static final String SMS_SEND_LOCK="login:sms:lock:";
/**
* 验证码有效期 5分钟
*/
public static final Integer SMS_EXPIRE = 5;
/**
* 冷却时间
*/
public static final Integer LOCK_EXPIRE = 60;;
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.framework.web.service;
import javax.annotation.Resource;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.cms.domain.vo.WechatAuthVO;
@@ -9,12 +10,15 @@ import com.ruoyi.cms.service.IAppUserService;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.cms.util.WechatUtil;
import com.ruoyi.cms.util.encrypt.QuickValidUtils;
import com.ruoyi.common.config.SmsRequestClient;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.AppUser;
import com.ruoyi.common.core.domain.entity.SmsRequestDTO;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginSiteUser;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.redis.DistributedLockUtil;
import com.ruoyi.common.core.redis.RedisUtils;
import com.ruoyi.common.utils.*;
import com.ruoyi.framework.web.exception.ParamErrorConstants;
import org.springframework.beans.factory.annotation.Autowired;
@@ -77,6 +81,8 @@ public class SysLoginService
private IAppUserService appUserService;
@Autowired
private DistributedLockUtil distributedLockUtil;
@Autowired
private SmsRequestClient smsRequestClient;
/**
* 登录验证
@@ -321,6 +327,168 @@ public class SysLoginService
}
}
/**
* 通过微信授权登录获取手机号
* @param dto
* @return
*/
public AjaxResult getWxPhone(LoginBody dto){
AjaxResult validateResult = validateBaseParam(dto);
if (validateResult != null) {
return validateResult;
}
try {
WechatAuthVO wechatAuthVO = getWechatAuthInfo(dto.getCode());
if (wechatAuthVO == null) {
System.err.println("微信授权返回null");
return AjaxResult.error("微信授权失败");
}
String openid = wechatAuthVO.getOpenid();
String unionid = wechatAuthVO.getUnionid();
String sessionKey = wechatAuthVO.getSessionKey();
if (StringUtils.isEmpty(openid)) {
System.err.println("微信授权返回openid为空");
return AjaxResult.error("微信授权失败");
}
//解密获取手机号
String phone = decryptPhone(dto, sessionKey);
if (phone == null) {
System.err.println("手机号解密失败openid=" + openid);
return AjaxResult.error("获取手机号失败");
}
//发送短信,并缓存
AjaxResult smsResult=sendSmsAndCache(phone);
if (!smsResult.isSuccess()) {
return smsResult;
}
AjaxResult ajax = AjaxResult.success("获取手机号成功");
ajax.put("phone", phone);
ajax.put("openid",openid);
ajax.put("unionid",unionid);
return ajax;
} catch (Exception e) {
System.err.println("获取手机号异常:" + e.getMessage());
return AjaxResult.error("获取手机号异常,请稍后重试");
}
}
/**
* 发送短信并缓存
* @param phone
* @return
*/
private AjaxResult sendSmsAndCache(String phone) {
if (Boolean.TRUE.equals(redisCache.hasKey(RedisUtils.SMS_SEND_LOCK + phone))) {
return AjaxResult.error("验证码发送频繁请60秒后重试");
}
// 生成6位验证码
String code = RandomUtil.randomNumbers(6);
// 缓存验证码
redisCache.setCacheObject(RedisUtils.SMS_CODE_KEY + phone, code, RedisUtils.SMS_EXPIRE, TimeUnit.MINUTES);
// 缓存发送锁(防重)
redisCache.setCacheObject(RedisUtils.SMS_SEND_LOCK + phone, 1, RedisUtils.LOCK_EXPIRE, TimeUnit.SECONDS);
try {
SmsRequestDTO requestDTO=new SmsRequestDTO();
requestDTO.setMobile(phone);
String[] tmpValues={code,String.valueOf(RedisUtils.SMS_EXPIRE)};
requestDTO.setTemplateVars(tmpValues);
AjaxResult result=smsRequestClient.sendSms(requestDTO);
//判断是否成功
if(!result.isSuccess()){
return result;
}
System.out.println("短信发送成功 → phone=" + phone + "code=" + code);
return AjaxResult.success("验证码发送成功");
} catch (Exception e) {
// 发送失败,删除缓存
redisCache.deleteObject(RedisUtils.SMS_CODE_KEY + phone);
redisCache.deleteObject(RedisUtils.SMS_SEND_LOCK + phone);
System.err.println("短信发送失败:" + e.getMessage());
return AjaxResult.error("短信发送失败,请稍后重试");
}
}
/**
* 验证码-验证
* @param phone
* @param smsCode
* @return
*/
public AjaxResult checkSmsCode(String phone, String smsCode) {
if (StringUtils.isBlank(smsCode)) {
return AjaxResult.error("请输入验证码");
}
String cacheCode = redisCache.getCacheObject(RedisUtils.SMS_CODE_KEY + phone);
if (cacheCode == null) {
return AjaxResult.error("验证码已过期");
}
if (!cacheCode.equals(smsCode.trim())) {
return AjaxResult.error("验证码错误");
}
// 验证通过,删除
redisCache.deleteObject(RedisUtils.SMS_CODE_KEY + phone);
redisCache.deleteObject(RedisUtils.SMS_SEND_LOCK + phone);
return AjaxResult.success();
}
/**
* 小程序登录主逻辑
* 核心逻辑优先处理网格员is_company_user=2再处理普通招聘者/求职者
*/
public AjaxResult appLoginTwo(LoginBody dto) {
// 1. 验证基础参数前端userType仅0/1拦截非法参数
AjaxResult validateResult = validateBaseParam(dto);
if (validateResult != null) {
return validateResult;
}
//添加验证码验证
AjaxResult smsValid = checkSmsCode(dto.getPhone(), dto.getSmsCode());
if (!smsValid.isSuccess()) {
return smsValid;
}
try {
// 3. 第一步通过OpenID优先查询网格员无需解密手机号效率更高
AppUser openidSpecialUser = appUserService.selectByOpenid(dto.getOpenid(), StringUtil.IS_GRID_USER);
if (openidSpecialUser != null) {
System.out.printf("小程序登录-匹配到OpenID网格员openid=%s, phone=%s%n",
openidSpecialUser.getOpenid(), openidSpecialUser.getPhone());
return handleSpecialUserLogin(openidSpecialUser);
}
// 5. 第二步通过手机号查询网格员OpenID未匹配时兜底
AppUser phoneSpecialUser = appUserService.getPhoneAndUserType(dto.getPhone(), StringUtil.IS_GRID_USER);
if (phoneSpecialUser != null) {
System.out.printf("小程序登录-匹配到手机号网格员phone=%s, openid=%s%n",
phoneSpecialUser.getPhone(), phoneSpecialUser.getOpenid());
return handleSpecialUserLogin(phoneSpecialUser);
}
// 6. 非网格员:处理普通用户(招聘者/求职者)登录逻辑
String userType = dto.getUserType();
// 6.1 优先匹配「OpenID+前端传入角色」的老用户
AppUser existingUser = appUserService.selectByOpenid(dto.getOpenid(), userType);
if (existingUser != null) {
System.out.printf("小程序登录-匹配到普通老用户openid=%s, userType=%s%n", dto.getOpenid(), userType, dto.getOrgType());
return handleExistingUser(existingUser, userType,dto.getOrgType());
}
// 6.2 处理普通用户的匹配与注册(手机号绑定、新用户创建等)
return handleUserMatchAndRegister(dto.getOpenid(), dto.getUnionid(), dto.getPhone(), userType,dto.getOrgType());
} catch (Exception e) {
System.err.println("小程序登录异常:" + e.getMessage());
e.printStackTrace();
return AjaxResult.error("登录失败,请稍后重试");
}
}
/**
* 小程序登录主逻辑
* 核心逻辑优先处理网格员is_company_user=2再处理普通招聘者/求职者