From 06b8a4f12864d8b0e920dcf6a91ac578fc309222 Mon Sep 17 00:00:00 2001 From: sh Date: Thu, 9 Apr 2026 21:58:22 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=E6=89=8B=E6=9C=BA=E5=8F=B7?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/system/SysLoginController.java | 21 +++ .../src/main/resources/application.yml | 9 + .../ruoyi/common/config/SmsRequestClient.java | 85 +++++++++ .../common/core/domain/model/LoginBody.java | 56 +++++- .../ruoyi/common/core/redis/RedisUtils.java | 25 +++ .../web/service/SysLoginService.java | 168 ++++++++++++++++++ 6 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/config/SmsRequestClient.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisUtils.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 0f8ac7e..faa7cd6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -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 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 5492e5e..282fbee 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -165,6 +165,15 @@ wx: appid: wx4aa34488b965a331 secret: 558780ecc2750f87e556b0e5496773c9 +#短信服务 +sms: + #API密钥账号 + secretName: kszhjyrcjt + #API密钥 + secretKey: Dwhc9c0IiHecvC5D + #短信模板ID + templateId: 37446 + #统一门户认证 oauth: #客户端的ID diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsRequestClient.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsRequestClient.java new file mode 100644 index 0000000..f208b12 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsRequestClient.java @@ -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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java index 4d515e2..f2488e7 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -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; + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisUtils.java new file mode 100644 index 0000000..55717c5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisUtils.java @@ -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;; + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java index 38fadc2..2191385 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -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),再处理普通招聘者/求职者