fix(auth): 修复邮箱登录SQL别名错误并支持邮箱找回密码

- UserMapper.selectAccountsByEmail 修正 FROM 别名 a 与 WHERE 中 u.is_super_admin 不匹配导致的 BadSqlGrammarException
- 去掉邮箱查询的管理员限定(邮箱已全局唯一,邮箱登录不再限管理员)
- 重置密码接口支持邮箱+邮箱验证码找回方式
This commit is contained in:
2026-07-24 20:28:31 +08:00
parent 14f49e43dd
commit 3ffd3255ee
3 changed files with 70 additions and 35 deletions

View File

@@ -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("密码重置失败,请稍后重试");

View File

@@ -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>
<!-- 根据手机号统计账号数量(忽略租户隔离) -->

View File

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