新增用户安全相关的

This commit is contained in:
2026-02-03 22:21:55 +08:00
parent 3a0f192599
commit db762a0f03
19 changed files with 1726 additions and 10 deletions

View File

@@ -0,0 +1,106 @@
package com.vetti.web.controller.hotake;
import com.vetti.common.core.domain.AjaxResult;
import com.vetti.common.core.domain.R;
import com.vetti.common.utils.MessageUtils;
import com.vetti.hotake.domain.HotakeSecuritySettings;
import com.vetti.hotake.domain.dto.SecurityChangePasswordDto;
import com.vetti.hotake.domain.vo.SecuritySessionVo;
import com.vetti.hotake.service.IHotakeSecurityService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 安全设置控制器
*
* @author vetti
* @date 2026-02-02
*/
@Api(tags = "安全设置模块")
@RestController
@RequestMapping("/security")
public class HotakeSecurityController
{
@Autowired
private IHotakeSecurityService securityService;
/**
* 获取当前用户的安全设置
*/
@ApiOperation("获取当前用户的安全设置")
@GetMapping("/settings")
public R<HotakeSecuritySettings> getSecuritySettings()
{
HotakeSecuritySettings settings = securityService.getCurrentUserSecuritySettings();
// 脱敏处理,不返回敏感信息
settings.setTwoFactorSecret(null);
settings.setBackupCodes(null);
settings.setPasswordResetToken(null);
return R.ok(settings, "");
}
/**
* 更新两步验证设置
*/
@ApiOperation("更新两步验证设置")
@PutMapping("/two-factor")
public AjaxResult updateTwoFactorEnabled(
@ApiParam(value = "是否启用", required = true)
@RequestParam Boolean enabled)
{
securityService.updateTwoFactorEnabled(enabled);
return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10001"));
}
/**
* 修改密码
*/
@ApiOperation("修改密码")
@PostMapping("/change-password")
public AjaxResult changePassword(@Validated @RequestBody SecurityChangePasswordDto dto)
{
securityService.changePassword(dto);
return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10002"));
}
/**
* 获取活跃会话列表
*/
@ApiOperation("获取活跃会话列表")
@GetMapping("/sessions")
public R<List<SecuritySessionVo>> getActiveSessions()
{
List<SecuritySessionVo> sessions = securityService.getActiveSessions();
return R.ok(sessions, "");
}
/**
* 终止指定会话
*/
@ApiOperation("终止指定会话")
@DeleteMapping("/sessions/{sessionId}")
public AjaxResult terminateSession(
@ApiParam(value = "会话ID", required = true)
@PathVariable Long sessionId)
{
securityService.terminateSession(sessionId);
return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10003"));
}
/**
* 终止所有其他会话
*/
@ApiOperation("终止所有其他会话")
@DeleteMapping("/sessions/terminate-all")
public AjaxResult terminateAllOtherSessions()
{
securityService.terminateAllOtherSessions();
return AjaxResult.success(MessageUtils.messageCustomize("HotakeSecurityController10004"));
}
}

View File

@@ -71,6 +71,28 @@ public class SysLoginController
LoginDto loginDto = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
// TODO: 安全功能集成 - 记录登录会话到安全表
// 在用户成功登录后记录会话信息到hotake_security_login_sessions表
// 用于支持"Login Sessions"功能,显示用户的所有活跃会话
try {
String ipAddress = com.vetti.common.utils.ip.IpUtils.getIpAddr();
String userAgent = com.vetti.common.utils.ServletUtils.getRequest().getHeader("User-Agent");
// 使用Spring的ApplicationContext来获取bean避免循环依赖
try {
Object securityService = com.vetti.common.utils.spring.SpringUtils.getBean("hotakeSecurityServiceImpl");
if (securityService != null) {
// 使用反射调用方法
java.lang.reflect.Method method = securityService.getClass().getMethod(
"recordLoginSession", Long.class, String.class, String.class, String.class);
method.invoke(securityService, loginDto.getUserId(), loginDto.getToken(), ipAddress, userAgent);
}
} catch (Exception e) {
// 安全服务不存在或调用失败,不影响登录流程
}
} catch (Exception e) {
// 记录会话失败不影响登录流程
}
// 如果是候选者,查询是否有简历
if (loginDto.getUser() != null && "candidate".equals(loginDto.getUser().getSysUserType())) {
HotakeCvInfo query = new HotakeCvInfo();
@@ -180,11 +202,7 @@ public class SysLoginController
public AjaxResult logout()
{
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null)
{
// 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken());
}
loginService.logout(loginUser);
return AjaxResult.success("退出成功");
}
}

View File

@@ -2,7 +2,7 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
port: 8686
servlet:
# 应用的访问路径
context-path: /

View File

@@ -65,4 +65,24 @@ VerificationEmailTiTle = Your verification code
VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes.
HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position
HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position
# Security settings related messages
HotakeSecurityServiceImpl10001 = New password and confirm password do not match
HotakeSecurityServiceImpl10002 = Current password is incorrect
HotakeSecurityServiceImpl10003 = New password cannot be the same as the last 5 passwords used
HotakeSecurityServiceImpl10004 = Session does not exist or no permission to operate
HotakeSecurityServiceImpl10005 = Failed to change password: current password is incorrect
HotakeSecurityServiceImpl10006 = Two-factor authentication enabled
HotakeSecurityServiceImpl10007 = Two-factor authentication disabled
HotakeSecurityServiceImpl10008 = Password changed successfully
HotakeSecurityServiceImpl10009 = Terminate session
HotakeSecurityServiceImpl10010 = Terminate all other sessions
HotakeSecurityController10001 = Two-factor authentication settings updated
HotakeSecurityController10002 = Password changed successfully
HotakeSecurityController10003 = Session terminated
HotakeSecurityController10004 = All other sessions terminated
# Logout related
HotakeSecurityServiceImpl10011 = User logged out

View File

@@ -62,4 +62,24 @@ HotakeRolesInfoServiceImpl10001 = 岗位信息异常,请稍后再试
VerificationEmailTiTle = 你的验证码
VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。
HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位
HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位
# 安全设置相关消息
HotakeSecurityServiceImpl10001 = 新密码和确认密码不一致
HotakeSecurityServiceImpl10002 = 当前密码错误
HotakeSecurityServiceImpl10003 = 新密码不能与最近5次使用过的密码相同
HotakeSecurityServiceImpl10004 = 会话不存在或无权操作
HotakeSecurityServiceImpl10005 = 修改密码失败:当前密码错误
HotakeSecurityServiceImpl10006 = 启用两步验证
HotakeSecurityServiceImpl10007 = 禁用两步验证
HotakeSecurityServiceImpl10008 = 修改密码成功
HotakeSecurityServiceImpl10009 = 终止会话
HotakeSecurityServiceImpl10010 = 终止所有其他会话
HotakeSecurityController10001 = 两步验证设置已更新
HotakeSecurityController10002 = 密码修改成功
HotakeSecurityController10003 = 会话已终止
HotakeSecurityController10004 = 所有其他会话已终止
# 退出登录相关
HotakeSecurityServiceImpl10011 = 用户退出登录

View File

@@ -65,4 +65,24 @@ VerificationEmailTiTle = Your verification code
VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes.
HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position
HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position
# Security settings related messages
HotakeSecurityServiceImpl10001 = New password and confirm password do not match
HotakeSecurityServiceImpl10002 = Current password is incorrect
HotakeSecurityServiceImpl10003 = New password cannot be the same as the last 5 passwords used
HotakeSecurityServiceImpl10004 = Session does not exist or no permission to operate
HotakeSecurityServiceImpl10005 = Failed to change password: current password is incorrect
HotakeSecurityServiceImpl10006 = Two-factor authentication enabled
HotakeSecurityServiceImpl10007 = Two-factor authentication disabled
HotakeSecurityServiceImpl10008 = Password changed successfully
HotakeSecurityServiceImpl10009 = Terminate session
HotakeSecurityServiceImpl10010 = Terminate all other sessions
HotakeSecurityController10001 = Two-factor authentication settings updated
HotakeSecurityController10002 = Password changed successfully
HotakeSecurityController10003 = Session terminated
HotakeSecurityController10004 = All other sessions terminated
# Logout related
HotakeSecurityServiceImpl10011 = User logged out

View File

@@ -62,4 +62,24 @@ HotakeRolesInfoServiceImpl10001 = 岗位信息异常,请稍后再试
VerificationEmailTiTle = 你的验证码
VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。
HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位
HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位
# 安全设置相关消息
HotakeSecurityServiceImpl10001 = 新密码和确认密码不一致
HotakeSecurityServiceImpl10002 = 当前密码错误
HotakeSecurityServiceImpl10003 = 新密码不能与最近5次使用过的密码相同
HotakeSecurityServiceImpl10004 = 会话不存在或无权操作
HotakeSecurityServiceImpl10005 = 修改密码失败:当前密码错误
HotakeSecurityServiceImpl10006 = 启用两步验证
HotakeSecurityServiceImpl10007 = 禁用两步验证
HotakeSecurityServiceImpl10008 = 修改密码成功
HotakeSecurityServiceImpl10009 = 终止会话
HotakeSecurityServiceImpl10010 = 终止所有其他会话
HotakeSecurityController10001 = 两步验证设置已更新
HotakeSecurityController10002 = 密码修改成功
HotakeSecurityController10003 = 会话已终止
HotakeSecurityController10004 = 所有其他会话已终止
# 退出登录相关
HotakeSecurityServiceImpl10011 = 用户退出登录

View File

@@ -233,4 +233,35 @@ public class SysLoginService
userService.resetUserPwd(sysUser.getUserId(), SecurityUtils.encryptPassword(password));
}
/**
* 退出登录
*
* @param loginUser 登录用户信息
*/
public void logout(LoginUser loginUser)
{
if (loginUser != null)
{
String token = loginUser.getToken();
// TODO: 安全功能集成 - 更新会话状态为已登出
// 在用户退出登录时更新hotake_security_login_sessions表中的会话状态
// 记录登出时间并将is_active设置为0
try {
Object securityService = com.vetti.common.utils.spring.SpringUtils.getBean("hotakeSecurityServiceImpl");
if (securityService != null) {
// 使用反射调用更新会话状态的方法
java.lang.reflect.Method method = securityService.getClass().getMethod(
"updateSessionLogout", String.class);
method.invoke(securityService, token);
}
} catch (Exception e) {
// 更新会话状态失败不影响退出流程
}
// 删除用户缓存记录
tokenService.delLoginUser(token);
}
}
}

View File

@@ -0,0 +1,115 @@
package com.vetti.hotake.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vetti.common.annotation.Excel;
import com.vetti.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 用户登录会话对象 hotake_security_login_sessions
*
* @author vetti
* @date 2026-02-02
*/
@Data
@Accessors(chain = true)
@ApiModel("用户登录会话")
public class HotakeSecurityLoginSessions extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@ApiModelProperty("主键ID")
private Long id;
/** 用户ID */
@ApiModelProperty("用户ID")
@Excel(name = "用户ID")
private Long userId;
/** 会话ID */
@ApiModelProperty("会话ID")
@Excel(name = "会话ID")
private String sessionId;
/** 会话令牌 */
@ApiModelProperty("会话令牌")
private String sessionToken;
/** 设备类型 Desktop/Mobile/Tablet */
@ApiModelProperty("设备类型")
@Excel(name = "设备类型")
private String deviceType;
/** 设备名称 */
@ApiModelProperty("设备名称")
@Excel(name = "设备名称")
private String deviceName;
/** 浏览器 */
@ApiModelProperty("浏览器")
@Excel(name = "浏览器")
private String browser;
/** 浏览器版本 */
@ApiModelProperty("浏览器版本")
private String browserVersion;
/** 操作系统 */
@ApiModelProperty("操作系统")
@Excel(name = "操作系统")
private String os;
/** 操作系统版本 */
@ApiModelProperty("操作系统版本")
private String osVersion;
/** IP地址 */
@ApiModelProperty("IP地址")
@Excel(name = "IP地址")
private String ipAddress;
/** 国家 */
@ApiModelProperty("国家")
@Excel(name = "国家")
private String locationCountry;
/** 城市 */
@ApiModelProperty("城市")
@Excel(name = "城市")
private String locationCity;
/** 是否活跃 0-否 1-是 */
@ApiModelProperty("是否活跃")
@Excel(name = "是否活跃", readConverterExp = "0=否,1=是")
private Integer isActive;
/** 最后活动时间 */
@ApiModelProperty("最后活动时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后活动时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date lastActivityTime;
/** 登录时间 */
@ApiModelProperty("登录时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date loginTime;
/** 登出时间 */
@ApiModelProperty("登出时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "登出时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date logoutTime;
/** 会话过期时间 */
@ApiModelProperty("会话过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "会话过期时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date expiresAt;
}

View File

@@ -0,0 +1,54 @@
package com.vetti.hotake.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vetti.common.annotation.Excel;
import com.vetti.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 密码历史记录对象 hotake_security_password_history
*
* @author vetti
* @date 2026-02-02
*/
@Data
@Accessors(chain = true)
@ApiModel("密码历史记录")
public class HotakeSecurityPasswordHistory extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@ApiModelProperty("主键ID")
private Long id;
/** 用户ID */
@ApiModelProperty("用户ID")
@Excel(name = "用户ID")
private Long userId;
/** 密码哈希值 */
@ApiModelProperty("密码哈希值")
private String passwordHash;
/** 修改时间 */
@ApiModelProperty("修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "修改时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date changedAt;
/** 修改方式 USER/ADMIN/RESET */
@ApiModelProperty("修改方式")
@Excel(name = "修改方式")
private String changedBy;
/** IP地址 */
@ApiModelProperty("IP地址")
@Excel(name = "IP地址")
private String ipAddress;
}

View File

@@ -0,0 +1,67 @@
package com.vetti.hotake.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vetti.common.annotation.Excel;
import com.vetti.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 安全日志对象 hotake_security_security_logs
*
* @author vetti
* @date 2026-02-02
*/
@Data
@Accessors(chain = true)
@ApiModel("安全日志")
public class HotakeSecuritySecurityLogs extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@ApiModelProperty("主键ID")
private Long id;
/** 用户ID */
@ApiModelProperty("用户ID")
@Excel(name = "用户ID")
private Long userId;
/** 事件类型 */
@ApiModelProperty("事件类型")
@Excel(name = "事件类型")
private String eventType;
/** 事件描述 */
@ApiModelProperty("事件描述")
@Excel(name = "事件描述")
private String eventDescription;
/** IP地址 */
@ApiModelProperty("IP地址")
@Excel(name = "IP地址")
private String ipAddress;
/** 用户代理 */
@ApiModelProperty("用户代理")
private String userAgent;
/** 地理位置 */
@ApiModelProperty("地理位置")
@Excel(name = "地理位置")
private String location;
/** 状态 SUCCESS/FAILED/BLOCKED */
@ApiModelProperty("状态")
@Excel(name = "状态")
private String status;
/** 元数据(JSON格式) */
@ApiModelProperty("元数据")
private String metadata;
}

View File

@@ -0,0 +1,78 @@
package com.vetti.hotake.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vetti.common.annotation.Excel;
import com.vetti.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 用户安全设置对象 hotake_security_settings
*
* @author vetti
* @date 2026-02-02
*/
@Data
@Accessors(chain = true)
@ApiModel("用户安全设置")
public class HotakeSecuritySettings extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@ApiModelProperty("主键ID")
private Long id;
/** 用户ID */
@ApiModelProperty("用户ID")
@Excel(name = "用户ID")
private Long userId;
/** 是否启用两步验证 0-否 1-是 */
@ApiModelProperty("是否启用两步验证 0-否 1-是")
@Excel(name = "是否启用两步验证", readConverterExp = "0=否,1=是")
private Integer twoFactorEnabled;
/** 两步验证密钥 */
@ApiModelProperty("两步验证密钥")
private String twoFactorSecret;
/** 两步验证类型 TOTP/SMS/EMAIL */
@ApiModelProperty("两步验证类型")
@Excel(name = "两步验证类型")
private String twoFactorType;
/** 备份验证码(JSON格式) */
@ApiModelProperty("备份验证码")
private String backupCodes;
/** 密码最后修改时间 */
@ApiModelProperty("密码最后修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "密码最后修改时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date passwordChangedAt;
/** 密码重置令牌 */
@ApiModelProperty("密码重置令牌")
private String passwordResetToken;
/** 密码重置令牌过期时间 */
@ApiModelProperty("密码重置令牌过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date passwordResetExpires;
/** 失败登录尝试次数 */
@ApiModelProperty("失败登录尝试次数")
@Excel(name = "失败登录尝试次数")
private Integer failedLoginAttempts;
/** 账户锁定截止时间 */
@ApiModelProperty("账户锁定截止时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "账户锁定截止时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date accountLockedUntil;
}

View File

@@ -0,0 +1,80 @@
package com.vetti.hotake.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vetti.common.annotation.Excel;
import com.vetti.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 可信设备对象 hotake_security_trusted_devices
*
* @author vetti
* @date 2026-02-02
*/
@Data
@Accessors(chain = true)
@ApiModel("可信设备")
public class HotakeSecurityTrustedDevices extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@ApiModelProperty("主键ID")
private Long id;
/** 用户ID */
@ApiModelProperty("用户ID")
@Excel(name = "用户ID")
private Long userId;
/** 设备唯一标识 */
@ApiModelProperty("设备唯一标识")
@Excel(name = "设备唯一标识")
private String deviceId;
/** 设备名称 */
@ApiModelProperty("设备名称")
@Excel(name = "设备名称")
private String deviceName;
/** 设备类型 */
@ApiModelProperty("设备类型")
@Excel(name = "设备类型")
private String deviceType;
/** 浏览器 */
@ApiModelProperty("浏览器")
@Excel(name = "浏览器")
private String browser;
/** 操作系统 */
@ApiModelProperty("操作系统")
@Excel(name = "操作系统")
private String os;
/** IP地址 */
@ApiModelProperty("IP地址")
@Excel(name = "IP地址")
private String ipAddress;
/** 地理位置 */
@ApiModelProperty("地理位置")
@Excel(name = "地理位置")
private String location;
/** 是否可信 0-否 1-是 */
@ApiModelProperty("是否可信")
@Excel(name = "是否可信", readConverterExp = "0=否,1=是")
private Integer isTrusted;
/** 信任过期时间 */
@ApiModelProperty("信任过期时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "信任过期时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date trustExpiresAt;
}

View File

@@ -0,0 +1,100 @@
package com.vetti.hotake.service;
import com.vetti.hotake.domain.HotakeSecuritySettings;
import com.vetti.hotake.domain.dto.SecurityChangePasswordDto;
import com.vetti.hotake.domain.vo.SecuritySessionVo;
import java.util.List;
/**
* 安全设置Service接口
*
* @author vetti
* @date 2026-02-02
*/
public interface IHotakeSecurityService
{
/**
* 获取当前用户的安全设置
* 如果不存在则自动创建默认设置
*
* @return 安全设置
*/
HotakeSecuritySettings getCurrentUserSecuritySettings();
/**
* 更新两步验证设置
*
* @param enabled 是否启用
* @return 结果
*/
int updateTwoFactorEnabled(boolean enabled);
/**
* 修改密码
*
* @param dto 修改密码DTO
* @return 结果
*/
void changePassword(SecurityChangePasswordDto dto);
/**
* 获取当前用户的活跃会话列表
*
* @return 会话列表
*/
List<SecuritySessionVo> getActiveSessions();
/**
* 终止指定会话
*
* @param sessionId 会话ID
* @return 结果
*/
int terminateSession(Long sessionId);
/**
* 终止所有其他会话(除了当前会话)
*
* @return 结果
*/
int terminateAllOtherSessions();
/**
* 记录登录会话
*
* @param userId 用户ID
* @param sessionToken 会话令牌
* @param ipAddress IP地址
* @param userAgent 用户代理
* @return 结果
*/
int recordLoginSession(Long userId, String sessionToken, String ipAddress, String userAgent);
/**
* 更新会话活动时间
*
* @param sessionToken 会话令牌
* @return 结果
*/
int updateSessionActivity(String sessionToken);
/**
* 记录安全日志
*
* @param userId 用户ID
* @param eventType 事件类型
* @param eventDescription 事件描述
* @param status 状态
* @return 结果
*/
int recordSecurityLog(Long userId, String eventType, String eventDescription, String status);
/**
* 更新会话为已登出状态
*
* @param sessionToken 会话令牌
* @return 结果
*/
int updateSessionLogout(String sessionToken);
}

View File

@@ -0,0 +1,454 @@
package com.vetti.hotake.service.impl;
import com.vetti.common.core.domain.model.LoginUser;
import com.vetti.common.core.service.BaseServiceImpl;
import com.vetti.common.enums.FillTypeEnum;
import com.vetti.common.exception.ServiceException;
import com.vetti.common.utils.MessageUtils;
import com.vetti.common.utils.SecurityUtils;
import com.vetti.common.utils.ServletUtils;
import com.vetti.common.utils.StringUtils;
import com.vetti.common.utils.ip.AddressUtils;
import com.vetti.common.utils.ip.IpUtils;
import com.vetti.common.constant.CacheConstants;
import com.vetti.common.core.redis.RedisCache;
import com.vetti.hotake.domain.HotakeSecurityLoginSessions;
import com.vetti.hotake.domain.HotakeSecurityPasswordHistory;
import com.vetti.hotake.domain.HotakeSecuritySecurityLogs;
import com.vetti.hotake.domain.HotakeSecuritySettings;
import com.vetti.hotake.domain.dto.SecurityChangePasswordDto;
import com.vetti.hotake.domain.vo.SecuritySessionVo;
import com.vetti.hotake.mapper.HotakeSecurityLoginSessionsMapper;
import com.vetti.hotake.mapper.HotakeSecurityPasswordHistoryMapper;
import com.vetti.hotake.mapper.HotakeSecuritySecurityLogsMapper;
import com.vetti.hotake.mapper.HotakeSecuritySettingsMapper;
import com.vetti.hotake.service.IHotakeSecurityService;
import com.vetti.system.service.ISysUserService;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 安全设置Service业务层处理
*
* @author vetti
* @date 2026-02-02
*/
@Service
public class HotakeSecurityServiceImpl extends BaseServiceImpl implements IHotakeSecurityService
{
@Autowired
private HotakeSecuritySettingsMapper securitySettingsMapper;
@Autowired
private HotakeSecurityLoginSessionsMapper loginSessionsMapper;
@Autowired
private HotakeSecurityPasswordHistoryMapper passwordHistoryMapper;
@Autowired
private HotakeSecuritySecurityLogsMapper securityLogsMapper;
@Autowired
private ISysUserService userService;
@Autowired
private RedisCache redisCache;
@Value("${token.expireTime}")
private int expireTime;
/**
* 获取当前用户的安全设置
* 如果不存在则自动创建默认设置
*
* @return 安全设置
*/
@Transactional(rollbackFor = Exception.class)
@Override
public HotakeSecuritySettings getCurrentUserSecuritySettings()
{
Long userId = SecurityUtils.getUserId();
// 查询用户的安全设置
HotakeSecuritySettings settings = securitySettingsMapper.selectHotakeSecuritySettingsByUserId(userId);
// 如果不存在,创建默认设置
if (settings == null)
{
settings = new HotakeSecuritySettings();
settings.setUserId(userId);
settings.setTwoFactorEnabled(0);
settings.setTwoFactorType("TOTP");
settings.setFailedLoginAttempts(0);
fill(FillTypeEnum.INSERT.getCode(), settings);
securitySettingsMapper.insertHotakeSecuritySettings(settings);
}
return settings;
}
/**
* 更新两步验证设置
*
* @param enabled 是否启用
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int updateTwoFactorEnabled(boolean enabled)
{
Long userId = SecurityUtils.getUserId();
HotakeSecuritySettings settings = getCurrentUserSecuritySettings();
settings.setTwoFactorEnabled(enabled ? 1 : 0);
fill(FillTypeEnum.UPDATE.getCode(), settings);
// 记录安全日志
String eventType = enabled ? "2FA_ENABLED" : "2FA_DISABLED";
String eventDesc = enabled ?
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10006") :
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10007");
recordSecurityLog(userId, eventType, eventDesc, "SUCCESS");
return securitySettingsMapper.updateHotakeSecuritySettings(settings);
}
/**
* 修改密码
*
* @param dto 修改密码DTO
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void changePassword(SecurityChangePasswordDto dto)
{
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
// 验证新密码和确认密码是否一致
if (!dto.getNewPassword().equals(dto.getConfirmPassword()))
{
throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10001"));
}
// 验证当前密码是否正确
if (!SecurityUtils.matchesPassword(dto.getCurrentPassword(), SecurityUtils.getLoginUser().getPassword()))
{
// 记录失败日志
recordSecurityLog(userId, "PASSWORD_CHANGE",
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10005"), "FAILED");
throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10002"));
}
// TODO: 安全功能集成 - 检查新密码是否与历史密码重复
// 查询最近5次的密码历史
List<HotakeSecurityPasswordHistory> historyList = passwordHistoryMapper.selectRecentPasswordHistory(userId, 5);
String newPasswordHash = SecurityUtils.encryptPassword(dto.getNewPassword());
for (HotakeSecurityPasswordHistory history : historyList)
{
if (SecurityUtils.matchesPassword(dto.getNewPassword(), history.getPasswordHash()))
{
throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10003"));
}
}
// 修改密码
userService.resetUserPwd(userId, newPasswordHash);
// 记录密码历史
HotakeSecurityPasswordHistory history = new HotakeSecurityPasswordHistory();
history.setUserId(userId);
history.setPasswordHash(newPasswordHash);
history.setChangedAt(new Date());
history.setChangedBy("USER");
history.setIpAddress(IpUtils.getIpAddr());
fill(FillTypeEnum.INSERT.getCode(), history);
passwordHistoryMapper.insertHotakeSecurityPasswordHistory(history);
// 更新安全设置中的密码修改时间
HotakeSecuritySettings settings = getCurrentUserSecuritySettings();
settings.setPasswordChangedAt(new Date());
fill(FillTypeEnum.UPDATE.getCode(), settings);
securitySettingsMapper.updateHotakeSecuritySettings(settings);
// 记录安全日志
recordSecurityLog(userId, "PASSWORD_CHANGE",
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10008"), "SUCCESS");
}
/**
* 获取当前用户的活跃会话列表
*
* @return 会话列表
*/
@Transactional(readOnly = true)
@Override
public List<SecuritySessionVo> getActiveSessions()
{
Long userId = SecurityUtils.getUserId();
LoginUser loginUser = SecurityUtils.getLoginUser();
String currentToken = loginUser.getToken();
// 查询用户的活跃会话
List<HotakeSecurityLoginSessions> sessionsList = loginSessionsMapper.selectActiveSessionsByUserId(userId);
// 转换为VO
List<SecuritySessionVo> voList = new ArrayList<>();
for (HotakeSecurityLoginSessions session : sessionsList)
{
SecuritySessionVo vo = new SecuritySessionVo();
BeanUtils.copyProperties(session, vo);
// 组合设备名称
String deviceName = "";
if (StringUtils.isNotEmpty(session.getBrowser()))
{
deviceName = session.getBrowser();
}
if (StringUtils.isNotEmpty(session.getOs()))
{
deviceName += (StringUtils.isNotEmpty(deviceName) ? " / " : "") + session.getOs();
}
vo.setDeviceName(deviceName);
// 组合地理位置
String location = "";
if (StringUtils.isNotEmpty(session.getLocationCity()))
{
location = session.getLocationCity();
}
if (StringUtils.isNotEmpty(session.getLocationCountry()))
{
location += (StringUtils.isNotEmpty(location) ? ", " : "") + session.getLocationCountry();
}
vo.setLocation(location);
// 判断是否为当前会话
vo.setIsCurrent(session.getSessionToken().equals(currentToken));
vo.setIsActive(session.getIsActive() == 1);
voList.add(vo);
}
return voList;
}
/**
* 终止指定会话
*
* @param sessionId 会话ID
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int terminateSession(Long sessionId)
{
Long userId = SecurityUtils.getUserId();
// 查询会话信息
HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsById(sessionId);
if (session == null || !session.getUserId().equals(userId))
{
throw new ServiceException(MessageUtils.messageCustomize("HotakeSecurityServiceImpl10004"));
}
// TODO: 安全功能集成 - 从Redis中删除对应的token缓存
// 这样可以立即使该会话失效
// 使用RedisCache直接删除避免依赖TokenService
String userKey = CacheConstants.LOGIN_TOKEN_KEY + session.getSessionToken();
redisCache.deleteObject(userKey);
// 更新会话状态
int result = loginSessionsMapper.terminateSession(sessionId, userId);
// 记录安全日志
recordSecurityLog(userId, "SESSION_TERMINATE",
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10009") + "" + session.getDeviceName(),
"SUCCESS");
return result;
}
/**
* 终止所有其他会话(除了当前会话)
*
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int terminateAllOtherSessions()
{
Long userId = SecurityUtils.getUserId();
LoginUser loginUser = SecurityUtils.getLoginUser();
String currentToken = loginUser.getToken();
// TODO: 安全功能集成 - 从Redis中批量删除其他会话的token缓存
// 查询所有其他活跃会话
List<HotakeSecurityLoginSessions> otherSessions = loginSessionsMapper.selectActiveSessionsByUserId(userId);
for (HotakeSecurityLoginSessions session : otherSessions)
{
if (!session.getSessionToken().equals(currentToken))
{
// 使用RedisCache直接删除避免依赖TokenService
String userKey = CacheConstants.LOGIN_TOKEN_KEY + session.getSessionToken();
redisCache.deleteObject(userKey);
}
}
// 更新会话状态
int result = loginSessionsMapper.terminateOtherSessions(userId, currentToken);
// 记录安全日志
recordSecurityLog(userId, "SESSION_TERMINATE_ALL",
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10010"),
"SUCCESS");
return result;
}
/**
* 记录登录会话
*
* @param userId 用户ID
* @param sessionToken 会话令牌
* @param ipAddress IP地址
* @param userAgent 用户代理
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int recordLoginSession(Long userId, String sessionToken, String ipAddress, String userAgent)
{
// 解析User-Agent
UserAgent ua = UserAgent.parseUserAgentString(userAgent);
Browser browser = ua.getBrowser();
OperatingSystem os = ua.getOperatingSystem();
// 获取地理位置
String location = AddressUtils.getRealAddressByIP(ipAddress);
String[] locationParts = location.split(" ");
String country = locationParts.length > 0 ? locationParts[0] : "";
String city = locationParts.length > 1 ? locationParts[1] : "";
// 判断设备类型
String deviceType = "Desktop";
if (os.getDeviceType() != null)
{
deviceType = os.getDeviceType().getName();
}
// 创建会话记录
HotakeSecurityLoginSessions session = new HotakeSecurityLoginSessions();
session.setUserId(userId);
session.setSessionId(sessionToken);
session.setSessionToken(sessionToken);
session.setDeviceType(deviceType);
session.setDeviceName(browser.getName() + " / " + os.getName());
session.setBrowser(browser.getName());
session.setBrowserVersion(ua.getBrowserVersion() != null ? ua.getBrowserVersion().getVersion() : "");
session.setOs(os.getName());
session.setOsVersion("");
session.setIpAddress(ipAddress);
session.setLocationCountry(country);
session.setLocationCity(city);
session.setIsActive(1);
session.setLoginTime(new Date());
session.setLastActivityTime(new Date());
// 计算过期时间
Date expiresAt = new Date(System.currentTimeMillis() + expireTime * 60 * 1000L);
session.setExpiresAt(expiresAt);
fill(FillTypeEnum.INSERT.getCode(), session);
return loginSessionsMapper.insertHotakeSecurityLoginSessions(session);
}
/**
* 更新会话活动时间
*
* @param sessionToken 会话令牌
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int updateSessionActivity(String sessionToken)
{
HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsByToken(sessionToken);
if (session != null)
{
session.setLastActivityTime(new Date());
fill(FillTypeEnum.UPDATE.getCode(), session);
return loginSessionsMapper.updateHotakeSecurityLoginSessions(session);
}
return 0;
}
/**
* 记录安全日志
*
* @param userId 用户ID
* @param eventType 事件类型
* @param eventDescription 事件描述
* @param status 状态
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int recordSecurityLog(Long userId, String eventType, String eventDescription, String status)
{
HotakeSecuritySecurityLogs log = new HotakeSecuritySecurityLogs();
log.setUserId(userId);
log.setEventType(eventType);
log.setEventDescription(eventDescription);
log.setIpAddress(IpUtils.getIpAddr());
log.setUserAgent(StringUtils.substring(ServletUtils.getRequest().getHeader("User-Agent"), 0, 500));
log.setLocation(AddressUtils.getRealAddressByIP(IpUtils.getIpAddr()));
log.setStatus(status);
fill(FillTypeEnum.INSERT.getCode(), log);
return securityLogsMapper.insertHotakeSecuritySecurityLogs(log);
}
/**
* 更新会话为已登出状态
*
* @param sessionToken 会话令牌
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public int updateSessionLogout(String sessionToken)
{
// 查询会话信息
HotakeSecurityLoginSessions session = loginSessionsMapper.selectHotakeSecurityLoginSessionsByToken(sessionToken);
if (session != null && session.getIsActive() == 1)
{
// 更新会话状态
session.setIsActive(0);
session.setLogoutTime(new Date());
fill(FillTypeEnum.UPDATE.getCode(), session);
// 记录安全日志
recordSecurityLog(session.getUserId(), "LOGOUT",
MessageUtils.messageCustomize("HotakeSecurityServiceImpl10011"), "SUCCESS");
return loginSessionsMapper.updateHotakeSecurityLoginSessions(session);
}
return 0;
}
}

View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vetti.hotake.mapper.HotakeSecurityLoginSessionsMapper">
<resultMap type="HotakeSecurityLoginSessions" id="HotakeSecurityLoginSessionsResult">
<result property="id" column="id" />
<result property="userId" column="user_id" />
<result property="sessionId" column="session_id" />
<result property="sessionToken" column="session_token" />
<result property="deviceType" column="device_type" />
<result property="deviceName" column="device_name" />
<result property="browser" column="browser" />
<result property="browserVersion" column="browser_version" />
<result property="os" column="os" />
<result property="osVersion" column="os_version" />
<result property="ipAddress" column="ip_address" />
<result property="locationCountry" column="location_country" />
<result property="locationCity" column="location_city" />
<result property="isActive" column="is_active" />
<result property="lastActivityTime" column="last_activity_time" />
<result property="loginTime" column="login_time" />
<result property="logoutTime" column="logout_time" />
<result property="expiresAt" column="expires_at" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectHotakeSecurityLoginSessionsVo">
select id, user_id, session_id, session_token, device_type, device_name,
browser, browser_version, os, os_version, ip_address,
location_country, location_city, is_active, last_activity_time,
login_time, logout_time, expires_at,
del_flag, create_by, create_time, update_by, update_time, remark
from hotake_security_login_sessions
</sql>
<select id="selectHotakeSecurityLoginSessionsList" parameterType="HotakeSecurityLoginSessions" resultMap="HotakeSecurityLoginSessionsResult">
<include refid="selectHotakeSecurityLoginSessionsVo"/>
<where>
del_flag = '0'
<if test="userId != null"> and user_id = #{userId}</if>
<if test="sessionToken != null and sessionToken != ''"> and session_token = #{sessionToken}</if>
<if test="isActive != null"> and is_active = #{isActive}</if>
</where>
order by login_time desc
</select>
<select id="selectHotakeSecurityLoginSessionsById" parameterType="Long" resultMap="HotakeSecurityLoginSessionsResult">
<include refid="selectHotakeSecurityLoginSessionsVo"/>
where id = #{id} and del_flag = '0'
</select>
<select id="selectHotakeSecurityLoginSessionsByToken" parameterType="String" resultMap="HotakeSecurityLoginSessionsResult">
<include refid="selectHotakeSecurityLoginSessionsVo"/>
where session_token = #{sessionToken} and del_flag = '0'
limit 1
</select>
<select id="selectActiveSessionsByUserId" parameterType="Long" resultMap="HotakeSecurityLoginSessionsResult">
<include refid="selectHotakeSecurityLoginSessionsVo"/>
where user_id = #{userId}
and is_active = 1
and del_flag = '0'
and expires_at > now()
order by login_time desc
</select>
<insert id="insertHotakeSecurityLoginSessions" parameterType="HotakeSecurityLoginSessions" useGeneratedKeys="true" keyProperty="id">
insert into hotake_security_login_sessions
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="sessionId != null">session_id,</if>
<if test="sessionToken != null">session_token,</if>
<if test="deviceType != null">device_type,</if>
<if test="deviceName != null">device_name,</if>
<if test="browser != null">browser,</if>
<if test="browserVersion != null">browser_version,</if>
<if test="os != null">os,</if>
<if test="osVersion != null">os_version,</if>
<if test="ipAddress != null">ip_address,</if>
<if test="locationCountry != null">location_country,</if>
<if test="locationCity != null">location_city,</if>
<if test="isActive != null">is_active,</if>
<if test="lastActivityTime != null">last_activity_time,</if>
<if test="loginTime != null">login_time,</if>
<if test="logoutTime != null">logout_time,</if>
<if test="expiresAt != null">expires_at,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="sessionId != null">#{sessionId},</if>
<if test="sessionToken != null">#{sessionToken},</if>
<if test="deviceType != null">#{deviceType},</if>
<if test="deviceName != null">#{deviceName},</if>
<if test="browser != null">#{browser},</if>
<if test="browserVersion != null">#{browserVersion},</if>
<if test="os != null">#{os},</if>
<if test="osVersion != null">#{osVersion},</if>
<if test="ipAddress != null">#{ipAddress},</if>
<if test="locationCountry != null">#{locationCountry},</if>
<if test="locationCity != null">#{locationCity},</if>
<if test="isActive != null">#{isActive},</if>
<if test="lastActivityTime != null">#{lastActivityTime},</if>
<if test="loginTime != null">#{loginTime},</if>
<if test="logoutTime != null">#{logoutTime},</if>
<if test="expiresAt != null">#{expiresAt},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateHotakeSecurityLoginSessions" parameterType="HotakeSecurityLoginSessions">
update hotake_security_login_sessions
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="sessionId != null">session_id = #{sessionId},</if>
<if test="sessionToken != null">session_token = #{sessionToken},</if>
<if test="deviceType != null">device_type = #{deviceType},</if>
<if test="deviceName != null">device_name = #{deviceName},</if>
<if test="browser != null">browser = #{browser},</if>
<if test="browserVersion != null">browser_version = #{browserVersion},</if>
<if test="os != null">os = #{os},</if>
<if test="osVersion != null">os_version = #{osVersion},</if>
<if test="ipAddress != null">ip_address = #{ipAddress},</if>
<if test="locationCountry != null">location_country = #{locationCountry},</if>
<if test="locationCity != null">location_city = #{locationCity},</if>
<if test="isActive != null">is_active = #{isActive},</if>
<if test="lastActivityTime != null">last_activity_time = #{lastActivityTime},</if>
<if test="loginTime != null">login_time = #{loginTime},</if>
<if test="logoutTime != null">logout_time = #{logoutTime},</if>
<if test="expiresAt != null">expires_at = #{expiresAt},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<update id="terminateOtherSessions">
update hotake_security_login_sessions
set is_active = 0,
logout_time = now(),
update_time = now()
where user_id = #{userId}
and session_token != #{currentSessionToken}
and is_active = 1
and del_flag = '0'
</update>
<update id="terminateSession">
update hotake_security_login_sessions
set is_active = 0,
logout_time = now(),
update_time = now()
where id = #{id}
and user_id = #{userId}
and del_flag = '0'
</update>
<delete id="deleteHotakeSecurityLoginSessionsById" parameterType="Long">
update hotake_security_login_sessions set del_flag = '2' where id = #{id}
</delete>
<delete id="deleteHotakeSecurityLoginSessionsByIds" parameterType="String">
update hotake_security_login_sessions set del_flag = '2' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vetti.hotake.mapper.HotakeSecurityPasswordHistoryMapper">
<resultMap type="HotakeSecurityPasswordHistory" id="HotakeSecurityPasswordHistoryResult">
<result property="id" column="id" />
<result property="userId" column="user_id" />
<result property="passwordHash" column="password_hash" />
<result property="changedAt" column="changed_at" />
<result property="changedBy" column="changed_by" />
<result property="ipAddress" column="ip_address" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectHotakeSecurityPasswordHistoryVo">
select id, user_id, password_hash, changed_at, changed_by, ip_address,
del_flag, create_by, create_time, update_by, update_time, remark
from hotake_security_password_history
</sql>
<select id="selectHotakeSecurityPasswordHistoryList" parameterType="HotakeSecurityPasswordHistory" resultMap="HotakeSecurityPasswordHistoryResult">
<include refid="selectHotakeSecurityPasswordHistoryVo"/>
<where>
del_flag = '0'
<if test="userId != null"> and user_id = #{userId}</if>
</where>
order by changed_at desc
</select>
<select id="selectHotakeSecurityPasswordHistoryById" parameterType="Long" resultMap="HotakeSecurityPasswordHistoryResult">
<include refid="selectHotakeSecurityPasswordHistoryVo"/>
where id = #{id} and del_flag = '0'
</select>
<select id="selectRecentPasswordHistory" resultMap="HotakeSecurityPasswordHistoryResult">
<include refid="selectHotakeSecurityPasswordHistoryVo"/>
where user_id = #{userId} and del_flag = '0'
order by changed_at desc
limit #{limit}
</select>
<insert id="insertHotakeSecurityPasswordHistory" parameterType="HotakeSecurityPasswordHistory" useGeneratedKeys="true" keyProperty="id">
insert into hotake_security_password_history
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="passwordHash != null">password_hash,</if>
<if test="changedAt != null">changed_at,</if>
<if test="changedBy != null">changed_by,</if>
<if test="ipAddress != null">ip_address,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="passwordHash != null">#{passwordHash},</if>
<if test="changedAt != null">#{changedAt},</if>
<if test="changedBy != null">#{changedBy},</if>
<if test="ipAddress != null">#{ipAddress},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateHotakeSecurityPasswordHistory" parameterType="HotakeSecurityPasswordHistory">
update hotake_security_password_history
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="passwordHash != null">password_hash = #{passwordHash},</if>
<if test="changedAt != null">changed_at = #{changedAt},</if>
<if test="changedBy != null">changed_by = #{changedBy},</if>
<if test="ipAddress != null">ip_address = #{ipAddress},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteHotakeSecurityPasswordHistoryById" parameterType="Long">
update hotake_security_password_history set del_flag = '2' where id = #{id}
</delete>
<delete id="deleteHotakeSecurityPasswordHistoryByIds" parameterType="String">
update hotake_security_password_history set del_flag = '2' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vetti.hotake.mapper.HotakeSecuritySecurityLogsMapper">
<resultMap type="HotakeSecuritySecurityLogs" id="HotakeSecuritySecurityLogsResult">
<result property="id" column="id" />
<result property="userId" column="user_id" />
<result property="eventType" column="event_type" />
<result property="eventDescription" column="event_description" />
<result property="ipAddress" column="ip_address" />
<result property="userAgent" column="user_agent" />
<result property="location" column="location" />
<result property="status" column="status" />
<result property="metadata" column="metadata" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectHotakeSecuritySecurityLogsVo">
select id, user_id, event_type, event_description, ip_address, user_agent,
location, status, metadata,
del_flag, create_by, create_time, update_by, update_time, remark
from hotake_security_security_logs
</sql>
<select id="selectHotakeSecuritySecurityLogsList" parameterType="HotakeSecuritySecurityLogs" resultMap="HotakeSecuritySecurityLogsResult">
<include refid="selectHotakeSecuritySecurityLogsVo"/>
<where>
del_flag = '0'
<if test="userId != null"> and user_id = #{userId}</if>
<if test="eventType != null and eventType != ''"> and event_type = #{eventType}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
</where>
order by create_time desc
</select>
<select id="selectHotakeSecuritySecurityLogsById" parameterType="Long" resultMap="HotakeSecuritySecurityLogsResult">
<include refid="selectHotakeSecuritySecurityLogsVo"/>
where id = #{id} and del_flag = '0'
</select>
<insert id="insertHotakeSecuritySecurityLogs" parameterType="HotakeSecuritySecurityLogs" useGeneratedKeys="true" keyProperty="id">
insert into hotake_security_security_logs
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="eventType != null">event_type,</if>
<if test="eventDescription != null">event_description,</if>
<if test="ipAddress != null">ip_address,</if>
<if test="userAgent != null">user_agent,</if>
<if test="location != null">location,</if>
<if test="status != null">status,</if>
<if test="metadata != null">metadata,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="eventType != null">#{eventType},</if>
<if test="eventDescription != null">#{eventDescription},</if>
<if test="ipAddress != null">#{ipAddress},</if>
<if test="userAgent != null">#{userAgent},</if>
<if test="location != null">#{location},</if>
<if test="status != null">#{status},</if>
<if test="metadata != null">#{metadata},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateHotakeSecuritySecurityLogs" parameterType="HotakeSecuritySecurityLogs">
update hotake_security_security_logs
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="eventType != null">event_type = #{eventType},</if>
<if test="eventDescription != null">event_description = #{eventDescription},</if>
<if test="ipAddress != null">ip_address = #{ipAddress},</if>
<if test="userAgent != null">user_agent = #{userAgent},</if>
<if test="location != null">location = #{location},</if>
<if test="status != null">status = #{status},</if>
<if test="metadata != null">metadata = #{metadata},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteHotakeSecuritySecurityLogsById" parameterType="Long">
update hotake_security_security_logs set del_flag = '2' where id = #{id}
</delete>
<delete id="deleteHotakeSecuritySecurityLogsByIds" parameterType="String">
update hotake_security_security_logs set del_flag = '2' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vetti.hotake.mapper.HotakeSecuritySettingsMapper">
<resultMap type="HotakeSecuritySettings" id="HotakeSecuritySettingsResult">
<result property="id" column="id" />
<result property="userId" column="user_id" />
<result property="twoFactorEnabled" column="two_factor_enabled" />
<result property="twoFactorSecret" column="two_factor_secret" />
<result property="twoFactorType" column="two_factor_type" />
<result property="backupCodes" column="backup_codes" />
<result property="passwordChangedAt" column="password_changed_at" />
<result property="passwordResetToken" column="password_reset_token" />
<result property="passwordResetExpires" column="password_reset_expires" />
<result property="failedLoginAttempts" column="failed_login_attempts" />
<result property="accountLockedUntil" column="account_locked_until" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectHotakeSecuritySettingsVo">
select id, user_id, two_factor_enabled, two_factor_secret, two_factor_type, backup_codes,
password_changed_at, password_reset_token, password_reset_expires,
failed_login_attempts, account_locked_until,
del_flag, create_by, create_time, update_by, update_time, remark
from hotake_security_settings
</sql>
<select id="selectHotakeSecuritySettingsList" parameterType="HotakeSecuritySettings" resultMap="HotakeSecuritySettingsResult">
<include refid="selectHotakeSecuritySettingsVo"/>
<where>
del_flag = '0'
<if test="userId != null"> and user_id = #{userId}</if>
<if test="twoFactorEnabled != null"> and two_factor_enabled = #{twoFactorEnabled}</if>
</where>
</select>
<select id="selectHotakeSecuritySettingsById" parameterType="Long" resultMap="HotakeSecuritySettingsResult">
<include refid="selectHotakeSecuritySettingsVo"/>
where id = #{id} and del_flag = '0'
</select>
<select id="selectHotakeSecuritySettingsByUserId" parameterType="Long" resultMap="HotakeSecuritySettingsResult">
<include refid="selectHotakeSecuritySettingsVo"/>
where user_id = #{userId} and del_flag = '0'
limit 1
</select>
<insert id="insertHotakeSecuritySettings" parameterType="HotakeSecuritySettings" useGeneratedKeys="true" keyProperty="id">
insert into hotake_security_settings
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="twoFactorEnabled != null">two_factor_enabled,</if>
<if test="twoFactorSecret != null">two_factor_secret,</if>
<if test="twoFactorType != null">two_factor_type,</if>
<if test="backupCodes != null">backup_codes,</if>
<if test="passwordChangedAt != null">password_changed_at,</if>
<if test="passwordResetToken != null">password_reset_token,</if>
<if test="passwordResetExpires != null">password_reset_expires,</if>
<if test="failedLoginAttempts != null">failed_login_attempts,</if>
<if test="accountLockedUntil != null">account_locked_until,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="twoFactorEnabled != null">#{twoFactorEnabled},</if>
<if test="twoFactorSecret != null">#{twoFactorSecret},</if>
<if test="twoFactorType != null">#{twoFactorType},</if>
<if test="backupCodes != null">#{backupCodes},</if>
<if test="passwordChangedAt != null">#{passwordChangedAt},</if>
<if test="passwordResetToken != null">#{passwordResetToken},</if>
<if test="passwordResetExpires != null">#{passwordResetExpires},</if>
<if test="failedLoginAttempts != null">#{failedLoginAttempts},</if>
<if test="accountLockedUntil != null">#{accountLockedUntil},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateHotakeSecuritySettings" parameterType="HotakeSecuritySettings">
update hotake_security_settings
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="twoFactorEnabled != null">two_factor_enabled = #{twoFactorEnabled},</if>
<if test="twoFactorSecret != null">two_factor_secret = #{twoFactorSecret},</if>
<if test="twoFactorType != null">two_factor_type = #{twoFactorType},</if>
<if test="backupCodes != null">backup_codes = #{backupCodes},</if>
<if test="passwordChangedAt != null">password_changed_at = #{passwordChangedAt},</if>
<if test="passwordResetToken != null">password_reset_token = #{passwordResetToken},</if>
<if test="passwordResetExpires != null">password_reset_expires = #{passwordResetExpires},</if>
<if test="failedLoginAttempts != null">failed_login_attempts = #{failedLoginAttempts},</if>
<if test="accountLockedUntil != null">account_locked_until = #{accountLockedUntil},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteHotakeSecuritySettingsById" parameterType="Long">
update hotake_security_settings set del_flag = '2' where id = #{id}
</delete>
<delete id="deleteHotakeSecuritySettingsByIds" parameterType="String">
update hotake_security_settings set del_flag = '2' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>