feat(auth): 新增超级管理员手机号短信验证码登录功能

- 在登录接口中增加对superAdminLogin场景的手机号校验
- 提供超级管理员短信验证码登录接口,支持验证码校验和超级管理员身份验证
- 超级管理员登录成功后生成访问token并缓存
- 在UserMapper中添加根据手机号查询最近登录超级管理员账号的方法
- 在UserService及其实现中增加对应的查询接口
- 更新SecurityConfig,放行超级管理员短信登录接口的权限验证
- 增强登录记录保存逻辑,包含错误和成功的登录记录保存
This commit is contained in:
2026-05-31 10:52:33 +08:00
parent e2c84b94b9
commit ec3bededa4
7 changed files with 172 additions and 0 deletions

View File

@@ -54,6 +54,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
"/hxz/v1/**",
"/api/sendSmsCaptcha",
"/api/loginBySms",
"/api/loginBySuperAdminSms",
"/api/system/user/regByPhone",
"/api/parseToken/*",
"/api/login-alipay/*",

View File

@@ -463,6 +463,12 @@ public class MainController extends BaseController {
return fail("该手机号码未注册!");
}
}
if (param.getScene() != null && param.getScene().equals("superAdminLogin")) {
final User superAdmin = userService.getLastLoginSuperAdminByPhone(param.getPhone());
if (ObjectUtil.isEmpty(superAdmin)) {
return fail("该手机号码未注册超级管理员账号!");
}
}
Integer tenantId = getTenantId();
@@ -693,6 +699,65 @@ public class MainController extends BaseController {
return success("登录成功", new LoginResult(access_token, user));
}
@Operation(summary = "超级管理员短信验证码登录")
@PostMapping("/loginBySuperAdminSms")
public ApiResult<LoginResult> loginBySuperAdminSms(@RequestBody LoginParam param, HttpServletRequest request) {
if (param == null) {
return fail("参数不能为空", null);
}
final String phone = param.getPhone();
if (!CommonUtil.isValidPhoneNumber(phone)) {
return fail("请输入有效的手机号码", null);
}
String code = param.getCode();
if (StrUtil.isBlank(code)) {
code = param.getSmsCode();
}
if (StrUtil.isBlank(code)) {
return fail("验证码不能为空", null);
}
String smsCode = redisUtil.get("code:" + phone);
String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
if (!StrUtil.equals(code, smsCode) && !StrUtil.equals(code, devSmsCode)) {
String message = "验证码不正确";
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
return fail(message, null);
}
User user = userService.getLastLoginSuperAdminByPhone(phone);
if (user == null) {
String message = "用户不存在";
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
return fail(message, null);
}
if (!Boolean.TRUE.equals(user.getIsSuperAdmin())) {
String message = "非超级管理员账号不允许登录";
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
return fail(message, null);
}
if (!user.getStatus().equals(0)) {
String message = "账号被冻结";
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
return fail(message, null);
}
Long tokenExpireTime = configProperties.getTokenExpireTime();
final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
if (register != null) {
final String ExpireTime = register.getString("tokenExpireTime");
if (ExpireTime != null) {
tokenExpireTime = Long.valueOf(ExpireTime);
}
}
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
tokenExpireTime, configProperties.getTokenKey());
redisUtil.set("access_token:" + user.getUserId(), access_token, tokenExpireTime, TimeUnit.SECONDS);
return success("登录成功", new LoginResult(access_token, user));
}
@Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE)
@Operation(summary = "账号注册")
@PostMapping("/register")

View File

@@ -90,4 +90,13 @@ public interface UserMapper extends BaseMapper<User> {
@InterceptorIgnore(tenantLine = "true")
Integer countByPhone(@Param("phone") String phone);
/**
* 根据手机号查询最近登录的超级管理员账号(忽略租户隔离)
*
* @param phone 手机号
* @return User
*/
@InterceptorIgnore(tenantLine = "true")
User selectLastLoginSuperAdminByPhone(@Param("phone") String phone);
}

View File

@@ -379,4 +379,28 @@
AND phone = #{phone}
</select>
<!-- 根据手机号查询最近登录的超级管理员账号(忽略租户隔离) -->
<select id="selectLastLoginSuperAdminByPhone" resultType="com.gxwebsoft.common.system.entity.User">
SELECT u.*
FROM sys_user u
LEFT JOIN (
SELECT tenant_id,
username,
MAX(create_time) AS last_login_time
FROM sys_login_record
WHERE login_type = 0
GROUP BY tenant_id, username
) lr ON lr.tenant_id = u.tenant_id
AND (lr.username = u.username OR lr.username = u.phone)
WHERE u.deleted = 0
AND u.status = 0
AND u.phone = #{phone}
AND u.is_super_admin = 1
ORDER BY lr.last_login_time DESC,
u.update_time DESC,
u.create_time DESC,
u.user_id DESC
LIMIT 1
</select>
</mapper>

View File

@@ -126,6 +126,14 @@ public interface UserService extends IService<User>, UserDetailsService {
List<User> getAdminsByPhone(LoginParam param);
/**
* 根据手机号查询最近登录的超级管理员账号(忽略租户隔离)
*
* @param phone 手机号
* @return 用户信息
*/
User getLastLoginSuperAdminByPhone(String phone);
List<User> pageAll(UserParam param);
User getByUserId(String userId);

View File

@@ -370,6 +370,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return baseMapper.selectListAllRel(userParam);
}
@Override
public User getLastLoginSuperAdminByPhone(String phone) {
return baseMapper.selectLastLoginSuperAdminByPhone(phone);
}
@Override
public List<User> pageAll(UserParam param) {
return baseMapper.pageRelAll(param);