From 4ad2a85850131d3050907152e3bd8628f626a780 Mon Sep 17 00:00:00 2001 From: sh Date: Wed, 24 Dec 2025 12:51:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=99=BB=E5=BD=95-=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=85=88=E6=8E=92=E9=99=A4=E7=BD=91=E6=A0=BC=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/cms/util/StringUtil.java | 54 ++++++++++--- .../core/redis/DistributedLockUtil.java | 40 +++++++++ .../web/service/OauthLoginHlwService.java | 5 +- .../web/service/SysLoginService.java | 81 ++++++++++++++++--- 4 files changed, 154 insertions(+), 26 deletions(-) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java index 21f08ca..c519617 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/StringUtil.java @@ -1,5 +1,7 @@ package com.ruoyi.cms.util; +import com.ruoyi.common.utils.StringUtils; + import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Arrays; @@ -138,18 +140,6 @@ public class StringUtil { .collect(Collectors.toList()); // 收集为List } - /** - * 脱敏逻辑:前4位 + ***+ 后4位 - * @param idCard - * @return - */ - public static String desensitizeIdCard(String idCard) { - if (idCard == null || idCard.length() != 18) { - return idCard; // 非标准身份证号不脱敏(或按规则处理) - } - return idCard.substring(0, 4) + "***" + idCard.substring(14); - } - /** * 获取附件地址 * @return @@ -183,4 +173,44 @@ public class StringUtil { } return request.getHeader("X-Proxy-Server"); } + + /** + * 手机号脱敏 + * @param phone + * @return + */ + public static String desensitizePhone(String phone) { + if (StringUtils.isEmpty(phone) || phone.length() != 11) { + return phone; + } + return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); + } + + /** + * 脱敏逻辑:前4位 + ***+ 后4位 + * @param idCard + * @return + */ + public static String desensitizeIdCard(String idCard) { + if (StringUtils.isEmpty(idCard)) { + return null; + } + // 处理18位身份证(支持末尾X/x) + if (idCard.matches("\\d{17}[\\dXx]")) { + return idCard.replaceAll("(\\d{6})\\d{8}([\\dXx]{4})", "$1********$2"); + } + // 处理15位身份证 + else if (idCard.matches("\\d{15}")) { + return idCard.replaceAll("(\\d{6})\\d{6}(\\d{3})", "$1******$2"); + } + // 非标准格式(如16位、含特殊字符):返回部分脱敏(前4位+****+后2位),避免明文暴露 + else { + int len = idCard.length(); + if (len >= 6) { + return idCard.substring(0, 4) + "****" + idCard.substring(len - 2); + } + // 长度过短(<6位):直接返回***,避免明文 + return "***"; + } + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/DistributedLockUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/DistributedLockUtil.java index e91d647..a4c8200 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/DistributedLockUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/DistributedLockUtil.java @@ -208,4 +208,44 @@ public class DistributedLockUtil { String identifier = acquireLockWithRenewal(lockKey); return new AutoReleaseLock(this, lockKey, identifier); } + + /** + * 锁+超时时间 + * @param lockKey + * @param timeout + * @param unit + * @return + */ + public AutoReleaseLock tryLock(String lockKey, long timeout, TimeUnit unit) { + // 1. 校验参数合法性(防御性编程) + if (lockKey == null || lockKey.trim().isEmpty()) { + throw new IllegalArgumentException("锁key不能为空"); + } + if (timeout < 0) { + throw new IllegalArgumentException("超时时间不能为负数"); + } + if (unit == null) { + throw new IllegalArgumentException("时间单位不能为空"); + } + + // 2. 带超时获取锁(核心逻辑,需实现 acquireLockWithTimeout 方法) + String identifier = acquireLockWithTimeout(lockKey, timeout, unit); + + // 3. 返回自动释放锁(复用原有 AutoReleaseLock,无需修改) + return new AutoReleaseLock(this, lockKey, identifier); + } + + /** + * 带超时时间获取锁(支持自定义超时单位) + * @param lockKey 锁键 + * @param timeout 获取锁的超时时间 + * @param unit 时间单位 + * @return 锁标识(获取失败返回null) + */ + public String acquireLockWithTimeout(String lockKey, long timeout, TimeUnit unit) { + // 转换超时时间为秒(向上取整避免精度丢失) + long acquireTimeoutSeconds = (long) Math.ceil((double) unit.toMillis(timeout) / 1000); + // 使用默认的锁过期时间(30秒),也可根据需要改为让调用方传入 + return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, acquireTimeoutSeconds); + } } \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java index f913d2d..263cfd3 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java @@ -285,6 +285,7 @@ public class OauthLoginHlwService { appUserParm.setPhone(wwTyInfo.getPhone()); appUserParm.setIdCard(wwTyInfo.getIdno()); appUserParm.setName(wwTyInfo.getName()); + appUserParm.setIsCompanyUser(StringUtil.IS_JOB_REQUEST_USER); code=wwTyInfo.getIdno(); break; default: @@ -294,9 +295,11 @@ public class OauthLoginHlwService { //企业联系人->现根据社会信用代码查询企业信息 updateCompanyContact(wwTyInfo); //移动端 - appUserParm.setPhone(wwTyInfo.getPhone()); + String phone = StringUtils.isNotBlank(wwTyInfo.getPhone()) ? wwTyInfo.getPhone() : wwTyInfo.getContactphone(); + appUserParm.setPhone(phone); appUserParm.setIdCard(wwTyInfo.getEnterprisecode()); appUserParm.setName(wwTyInfo.getEnterprisename()); + appUserParm.setIsCompanyUser(StringUtil.IS_COMPANY_USER); code=wwTyInfo.getEnterprisecode(); } String localUsername=StringUtil.USER_KEY+code; 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 c8eb630..1d8b68f 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 @@ -43,6 +43,7 @@ import com.ruoyi.system.service.ISysUserService; import org.springframework.transaction.annotation.Transactional; import java.util.Date; +import java.util.concurrent.TimeUnit; /** * 登录校验方法 @@ -321,41 +322,68 @@ public class SysLoginService /** * 小程序登录主逻辑 + * 核心逻辑:优先处理网格员(is_company_user=2),再处理普通招聘者/求职者 */ public AjaxResult appLoginNew(LoginBody dto) { - //1.验证基础参数 + // 1. 验证基础参数(前端userType仅0/1,拦截非法参数) AjaxResult validateResult = validateBaseParam(dto); if (validateResult != null) { return validateResult; } try { - //2. 微信授权获取OpenID/UnionID/SessionKey + // 2. 微信授权获取OpenID/UnionID/SessionKey(网格员查询依赖OpenID) 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(); - - String userType = dto.getUserType(); - //3. 优先匹配「OpenID+角色」的老用户 - AppUser existingUser = appUserService.selectByOpenid(openid,userType); - if (existingUser != null) { - return handleExistingUser(existingUser, userType); + if (StringUtils.isEmpty(openid)) { + System.err.println("小程序登录失败:微信授权返回openid为空"); + return AjaxResult.error("微信授权失败"); } - // 4. 解密获取手机号(含二次校验) + // 3. 第一步:通过OpenID优先查询网格员(无需解密手机号,效率更高) + AppUser openidSpecialUser = appUserService.selectByOpenid(openid, StringUtil.IS_GRID_USER); + if (openidSpecialUser != null) { + System.out.printf("小程序登录-匹配到OpenID网格员:openid=%s, phone=%s%n", + openidSpecialUser.getOpenid(), openidSpecialUser.getPhone()); + return handleSpecialUserLogin(openidSpecialUser); + } + + // 4. 解密获取手机号(含二次校验)- 网格员兜底查询/普通用户登录依赖 String phone = decryptPhone(dto, sessionKey); if (phone == null) { + System.err.println("小程序登录失败:手机号解密失败,openid=" + openid); return AjaxResult.error("获取手机号失败"); } - // 5. 处理用户匹配与注册(核心逻辑拆分到独立方法) + // 5. 第二步:通过手机号查询网格员(OpenID未匹配时兜底) + AppUser phoneSpecialUser = appUserService.getPhoneAndUserType(phone, 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(openid, userType); + if (existingUser != null) { + System.out.printf("小程序登录-匹配到普通老用户:openid=%s, userType=%s%n", openid, userType); + return handleExistingUser(existingUser, userType); + } + + // 6.2 处理普通用户的匹配与注册(手机号绑定、新用户创建等) return handleUserMatchAndRegister(openid, unionid, phone, userType); + } catch (Exception e) { - System.err.println("登录失败:" + e.getMessage()); + System.err.println("小程序登录异常:" + e.getMessage()); + e.printStackTrace(); return AjaxResult.error("登录失败,请稍后重试"); } } @@ -470,7 +498,7 @@ public class SysLoginService */ private AjaxResult handleNoRoleUserBinding(String openid, String unionid, String phone, String userType, AppUser noRoleUser) { String lockKey = "login_no_role_bind_" + phone + "_" + userType; - try (DistributedLockUtil.AutoReleaseLock lock = distributedLockUtil.tryLock(lockKey)) { + try (DistributedLockUtil.AutoReleaseLock lock = distributedLockUtil.tryLock(lockKey, 3, TimeUnit.SECONDS)) { if (!lock.isLocked()) { return AjaxResult.error("登录请求过于频繁,请稍后重试"); } @@ -493,7 +521,7 @@ public class SysLoginService */ private AjaxResult handleNewUserRegistration(String openid, String unionid, String phone, String userType) { String createLockKey = "login_create_" + phone + "_" + userType; - try (DistributedLockUtil.AutoReleaseLock lock = distributedLockUtil.tryLock(createLockKey)) { + try (DistributedLockUtil.AutoReleaseLock lock = distributedLockUtil.tryLock(createLockKey, 3, TimeUnit.SECONDS)) { if (!lock.isLocked()) { return AjaxResult.error("登录请求过于频繁,请稍后重试"); } @@ -506,6 +534,33 @@ public class SysLoginService } } + /** + * 8-特殊处理-网格员登录 + * @param specialUser + * @return + */ + private AjaxResult handleSpecialUserLogin(AppUser specialUser) { + String lockKey = "login_grid_user_" + specialUser.getUserId(); // 按用户ID加锁 + try (DistributedLockUtil.AutoReleaseLock lock = distributedLockUtil.tryLock(lockKey, 2, TimeUnit.SECONDS)) { + if (!lock.isLocked()) { + System.err.println("网格员登录请求过于频繁,userId=" + specialUser.getUserId()); + return AjaxResult.error("登录请求过于频繁,请稍后重试"); + } + // 原有逻辑(更新登录时间、生成token) + AjaxResult ajax = AjaxResult.success(); + updateAppUserCommon(specialUser, null, null, null); + String token = loginUserIdApp(specialUser); + ajax.put(Constants.TOKEN, token); + ajax.put("isNewUser", false); + ajax.put("idCard", StringUtil.desensitizeIdCard(specialUser.getIdCard())); + ajax.put("isCompanyUser", specialUser.getIsCompanyUser()); + System.out.printf("特殊角色用户登录成功,openid:%s, phone:%s, 角色:%s%n", + specialUser.getOpenid(), StringUtil.desensitizePhone(specialUser.getPhone()), specialUser.getIsCompanyUser()); + System.out.println(ParamErrorConstants.LOG_AJAX_RETURN + JSON.toJSONString(ajax)); + return ajax; + } + } + /** * 处理老用户登录(日志用println) */