fix(auth): 修复邮箱登录SQL别名错误并支持邮箱找回密码
- UserMapper.selectAccountsByEmail 修正 FROM 别名 a 与 WHERE 中 u.is_super_admin 不匹配导致的 BadSqlGrammarException - 去掉邮箱查询的管理员限定(邮箱已全局唯一,邮箱登录不再限管理员) - 重置密码接口支持邮箱+邮箱验证码找回方式
This commit is contained in:
@@ -1489,17 +1489,20 @@ public class MainController extends BaseController {
|
||||
|
||||
/**
|
||||
* 重置密码(找回密码功能)
|
||||
* 支持两种验证方式:手机号+短信验证码 / 邮箱+邮箱验证码
|
||||
* 支持两种模式:
|
||||
* 1. 传 userId + tenantId:精确重置指定账号密码
|
||||
* 2. 不传 userId:通过手机号查找所有正常账号,统一重置密码(忘记密码场景)
|
||||
* 2. 不传 userId:通过手机号/邮箱查找所有正常账号,统一重置密码(忘记密码场景)
|
||||
*/
|
||||
@Operation(summary = "重置密码")
|
||||
@PostMapping("/resetPassword")
|
||||
@Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)
|
||||
public ApiResult<?> resetPassword(@RequestBody ResetPasswordParam param) {
|
||||
// 验证手机号
|
||||
if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
|
||||
return fail("请输入有效的手机号码");
|
||||
// 判断找回方式:邮箱 or 手机
|
||||
boolean byEmail = StrUtil.isNotBlank(param.getEmail());
|
||||
boolean byPhone = StrUtil.isNotBlank(param.getPhone());
|
||||
if (!byEmail && !byPhone) {
|
||||
return fail("请提供手机号或邮箱");
|
||||
}
|
||||
|
||||
// 验证两次密码是否一致
|
||||
@@ -1512,21 +1515,42 @@ public class MainController extends BaseController {
|
||||
return fail("密码必须至少8位,且包含字母和数字");
|
||||
}
|
||||
|
||||
// 验证短信验证码
|
||||
String key = "code:" + param.getPhone();
|
||||
String cachedCode = redisUtil.get(key);
|
||||
String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
|
||||
|
||||
if (!param.getSmsCode().equals(cachedCode) && !param.getSmsCode().equals(devCode)) {
|
||||
return fail("短信验证码不正确");
|
||||
// ===== 验证码校验 =====
|
||||
String verifyKey;
|
||||
String inputCode;
|
||||
if (byEmail) {
|
||||
if (!CommonUtil.isValidEmail(param.getEmail())) {
|
||||
return fail("请输入有效的邮箱地址");
|
||||
}
|
||||
if (StrUtil.isBlank(param.getEmailCode())) {
|
||||
return fail("邮箱验证码不能为空");
|
||||
}
|
||||
verifyKey = RedisConstants.EMAIL_CODE_KEY + ":" + param.getEmail();
|
||||
inputCode = param.getEmailCode();
|
||||
} else {
|
||||
if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
|
||||
return fail("请输入有效的手机号码");
|
||||
}
|
||||
if (StrUtil.isBlank(param.getSmsCode())) {
|
||||
return fail("短信验证码不能为空");
|
||||
}
|
||||
verifyKey = "code:" + param.getPhone();
|
||||
inputCode = param.getSmsCode();
|
||||
}
|
||||
|
||||
// ===== 模式判断:userId 为空时按手机号查所有用户统一重置 =====
|
||||
String cachedCode = redisUtil.get(verifyKey);
|
||||
String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
|
||||
if (!inputCode.equals(cachedCode) && !inputCode.equals(devCode)) {
|
||||
return fail(byEmail ? "邮箱验证码不正确" : "短信验证码不正确");
|
||||
}
|
||||
|
||||
// ===== 模式判断:userId 为空时按手机号/邮箱查所有用户统一重置 =====
|
||||
if (StrUtil.isBlank(param.getUserId())) {
|
||||
// 通过手机号查找所有用户(跨租户,忽略租户隔离)
|
||||
List<User> userList = userMapper.selectAccountsByPhone(param.getPhone());
|
||||
List<User> userList = byEmail
|
||||
? userMapper.selectAccountsByEmail(param.getEmail())
|
||||
: userMapper.selectAccountsByPhone(param.getPhone());
|
||||
if (userList == null || userList.isEmpty()) {
|
||||
return fail("该手机号未注册");
|
||||
return fail(byEmail ? "该邮箱未注册" : "该手机号未注册");
|
||||
}
|
||||
// 过滤掉被冻结的账号
|
||||
List<User> activeUsers = userList.stream()
|
||||
@@ -1535,8 +1559,9 @@ public class MainController extends BaseController {
|
||||
if (activeUsers.isEmpty()) {
|
||||
return fail("账号已被冻结,请联系管理员");
|
||||
}
|
||||
// 统一重置所有同手机号的正常账号密码
|
||||
// 统一重置所有正常账号密码
|
||||
String encodedPassword = userService.encodePassword(param.getNewPassword());
|
||||
String methodDesc = byEmail ? "通过邮箱" : "通过手机号";
|
||||
for (User u : activeUsers) {
|
||||
User update = new User();
|
||||
update.setUserId(u.getUserId());
|
||||
@@ -1547,23 +1572,28 @@ public class MainController extends BaseController {
|
||||
record.setUsername(u.getUsername());
|
||||
record.setNickname(u.getNickname());
|
||||
record.setLoginType(5);
|
||||
record.setComments("密码重置成功(通过手机号)");
|
||||
record.setComments("密码重置成功(" + methodDesc + ")");
|
||||
record.setTenantId(u.getTenantId());
|
||||
loginRecordService.save(record);
|
||||
}
|
||||
redisUtil.delete(key);
|
||||
redisUtil.delete(verifyKey);
|
||||
return success("密码重置成功");
|
||||
}
|
||||
|
||||
// ===== 模式1:传了 userId,精确重置 =====
|
||||
// 验证用户是否存在且手机号匹配
|
||||
User user = userService.getByUserId(param.getUserId());
|
||||
if (user == null) {
|
||||
return fail("用户不存在");
|
||||
}
|
||||
|
||||
if (!param.getPhone().equals(user.getPhone())) {
|
||||
return fail("手机号与账号不匹配");
|
||||
if (byEmail) {
|
||||
if (!param.getEmail().equals(user.getEmail())) {
|
||||
return fail("邮箱与账号不匹配");
|
||||
}
|
||||
} else {
|
||||
if (!param.getPhone().equals(user.getPhone())) {
|
||||
return fail("手机号与账号不匹配");
|
||||
}
|
||||
}
|
||||
|
||||
if (param.getTenantId() != null && !param.getTenantId().equals(user.getTenantId())) {
|
||||
@@ -1574,18 +1604,14 @@ public class MainController extends BaseController {
|
||||
boolean success = userService.resetUserPassword(param.getUserId(), user.getTenantId(), param.getNewPassword());
|
||||
|
||||
if (success) {
|
||||
// 密码重置成功后删除验证码
|
||||
redisUtil.delete(key);
|
||||
|
||||
// 记录登录日志(密码重置)
|
||||
redisUtil.delete(verifyKey);
|
||||
LoginRecord record = new LoginRecord();
|
||||
record.setUsername(user.getUsername());
|
||||
record.setNickname(user.getNickname());
|
||||
record.setLoginType(5); // 5表示密码重置(需要在LoginRecord中定义常量)
|
||||
record.setLoginType(5);
|
||||
record.setComments("密码重置成功");
|
||||
record.setTenantId(user.getTenantId());
|
||||
loginRecordService.save(record);
|
||||
|
||||
return success("密码重置成功");
|
||||
} else {
|
||||
return fail("密码重置失败,请稍后重试");
|
||||
|
||||
@@ -384,15 +384,20 @@
|
||||
|
||||
<!-- 根据邮箱查询所有账号(忽略租户隔离,用于跨租户邮箱登录) -->
|
||||
<select id="selectAccountsByEmail" resultType="com.gxwebsoft.common.system.entity.User">
|
||||
SELECT a.*,
|
||||
SELECT a.user_id,
|
||||
a.tenant_id,
|
||||
a.username,
|
||||
a.nickname,
|
||||
a.avatar,
|
||||
a.status,
|
||||
a.password,
|
||||
a.create_time,
|
||||
t.tenant_name
|
||||
FROM sys_user a
|
||||
LEFT JOIN sys_tenant t ON a.tenant_id = t.tenant_id
|
||||
WHERE a.deleted = 0
|
||||
AND a.email = #{email}
|
||||
AND (u.is_super_admin = 1 OR u.is_admin = 1)
|
||||
AND a.email = #{email}
|
||||
ORDER BY a.create_time DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 根据手机号统计账号数量(忽略租户隔离) -->
|
||||
|
||||
@@ -21,14 +21,18 @@ import java.io.Serializable;
|
||||
public class ResetPasswordParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Schema(description = "手机号码", required = true)
|
||||
@Schema(description = "手机号码(手机找回时必填)")
|
||||
private String phone;
|
||||
|
||||
@NotBlank(message = "短信验证码不能为空")
|
||||
@Schema(description = "短信验证码", required = true)
|
||||
@Schema(description = "短信验证码(手机找回时必填)")
|
||||
private String smsCode;
|
||||
|
||||
@Schema(description = "邮箱(邮箱找回时必填)")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "邮箱验证码(邮箱找回时必填)")
|
||||
private String emailCode;
|
||||
|
||||
@Schema(description = "用户ID(未登录忘记密码场景可不传,后端按手机号查用户)")
|
||||
private String userId;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user