添加安全规则

This commit is contained in:
2026-06-04 17:40:59 +08:00
parent da2db011f7
commit c590b1b693
46 changed files with 2576 additions and 45 deletions

View File

@@ -34,6 +34,8 @@ public enum AppCache implements BaseCacheEnum {
// JOB锁
LOCK_JOB("lock:job:", 5 * 60L),
// SYS用户锁
LOCK_SYS_USER("lock:sys-user:", -1L),
// 风控IP
RISK_IP("risk:ip:", -1L),
@@ -42,12 +44,17 @@ public enum AppCache implements BaseCacheEnum {
// 系统参数缓存
SYS_CACHE_PARAM("sys:cache:param:", -1L),
// 账号密码规则缓存
SYS_CACHE_AP_RULE("sys:cache:ap-rule", -1L),
// 系统JAR包缓存
SYS_CACHE_JAR("sys:cache:jar:", -1L),
// 地址缓存
SYS_CACHE_ADDR("sys:cache:addr:", 60 * 60L),
// 天气缓存
SYS_CACHE_HOME_WEATHER("sys:cache:home:weather:", 30 * 60L),
// 系统账号密码错误次数
COUNT_SYS_USER_PASSWD_ERR("count:sys-user:passwd-err:", -1L)
;
/**

View File

@@ -22,6 +22,8 @@ public enum TaskType implements BaseTaskType {
DEL_SYS_LOG(20, "删除系统日志"),
// 删除系统文件
DEL_SYS_FILE(30, "删除系统文件"),
// 删除禁用的系统用户
DEL_DISABLE_SYS_USER(40, "删除禁用的系统用户"),
;
/**

View File

@@ -0,0 +1,14 @@
package xtools.app.sys.api;
/**
* <p>Title : SysUserPasswdRuleApi</p>
* <p>Description : 用户名密码规则 Api</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
public interface SysUserPasswdRuleApi {
}

View File

@@ -109,6 +109,23 @@ public class SysUserController {
return sysUserService.delById(req.getIdList());
}
/**
* 重置密码
*
* @param id 用户ID
* @return 重置密码结果
*/
@Operation(summary = "重置密码")
@PostMapping("reset-passwd/{id}")
public Result<Boolean> resetPasswd(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
return sysUserService.resetPasswd(id);
}
/**
* 根据 ID 集合查询
*

View File

@@ -0,0 +1,135 @@
package xtools.app.sys.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import xtools.app.sys.api.SysUserPasswdRuleApi;
import xtools.app.sys.model.dto.excel.SysUserPasswdRuleExcel;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleAddReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRulePageReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleUpdateReq;
import xtools.app.sys.model.dto.resp.SysUserPasswdRuleResp;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.api.model.dto.req.IdListReq;
import xtools.core.CollectionUtils;
import xtools.extend.office.FesodUtils;
import xtools.web.HttpServletUtils;
import java.io.IOException;
import java.util.List;
/**
* <p>Title : SysUserPasswdRuleController</p>
* <p>Description : 用户名密码规则 Controller</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@RequiredArgsConstructor
@Tag(name = "用户名密码规则")
@RestController
@RequestMapping("/sys/user-passwd-rule")
public class SysUserPasswdRuleController implements SysUserPasswdRuleApi {
private final SysUserPasswdRuleService sysUserPasswdRuleService;
@Operation(summary = "分页请求")
@PostMapping("page")
public Result<PageResp<SysUserPasswdRuleResp>> page(@RequestBody @Valid PageReq<SysUserPasswdRulePageReq> pageReq) {
return sysUserPasswdRuleService.page(pageReq);
}
@Operation(summary = "获取数据")
@GetMapping("base/{id}")
public Result<SysUserPasswdRuleResp> getById(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
return sysUserPasswdRuleService.getById(id);
}
@Operation(summary = "添加数据")
@PostMapping("base")
public Result<Boolean> add(@RequestBody @Valid SysUserPasswdRuleAddReq req) {
return sysUserPasswdRuleService.add(req);
}
@Operation(summary = "更新数据")
@PutMapping("base")
public Result<Boolean> update(@RequestBody @Valid SysUserPasswdRuleUpdateReq req) {
return sysUserPasswdRuleService.update(req);
}
@Operation(summary = "删除数据")
@DeleteMapping("base")
public Result<Boolean> delById(@RequestBody @Valid IdListReq req) {
return sysUserPasswdRuleService.delById(req);
}
@Operation(summary = "设置默认")
@PostMapping("set-def/{id}")
public Result<Boolean> setDef(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
return sysUserPasswdRuleService.setDef(id);
}
@Operation(summary = "导出Excel")
@PostMapping("export")
public void exportExcel(@RequestBody @Valid SysUserPasswdRulePageReq req, HttpServletResponse response) {
String sheetName = "用户名密码规则";
String filename = sheetName + ".xlsx";
List<SysUserPasswdRuleExcel> dataList = sysUserPasswdRuleService.exportExcel(req);
try {
FesodUtils.write(response.getOutputStream(), SysUserPasswdRuleExcel.class, sheetName, dataList);
} catch (IOException e) {
throw new BizError("导出Excel失败");
}
// 设置 header 和 contentType.写在最后的原因是,避免报错时,响应 contentType 已经被修改
HttpServletUtils.addDownloadHeader(response, filename);
}
@Operation(summary = "导入Excel")
@PostMapping("import")
public Result<Boolean> importExcel(@RequestParam("file") MultipartFile file) {
List<SysUserPasswdRuleExcel> dataList;
try {
dataList = FesodUtils.read(file.getInputStream(), SysUserPasswdRuleExcel.class);
} catch (IOException e) {
throw new BizError("导入Excel失败");
}
if (CollectionUtils.isEmpty(dataList)) {
throw new BizWarning("导入Excel没有包含有效数据");
}
sysUserPasswdRuleService.importExcel(dataList);
return Result.ok(true);
}
}

View File

@@ -0,0 +1,72 @@
package xtools.app.sys.convert;
import org.mapstruct.Mapper;
import xtools.app.sys.model.dto.excel.SysUserPasswdRuleExcel;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleAddReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleUpdateReq;
import xtools.app.sys.model.dto.resp.SysUserPasswdRuleResp;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import java.util.List;
/**
* <p>Title : SysUserPasswdRuleConvert</p>
* <p>Description : 用户名密码规则 Convert</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Mapper(componentModel = "spring")
public interface SysUserPasswdRuleConvert {
/**
* 添加请求转实体
*
* @param req 添加请求
* @return 实体
*/
SysUserPasswdRule addReqToEntity(SysUserPasswdRuleAddReq req);
/**
* 修改请求转实体
*
* @param req 修改请求
* @return 实体
*/
SysUserPasswdRule updateReqToEntity(SysUserPasswdRuleUpdateReq req);
/**
* 实体转响应
*
* @param data 实体
* @return 响应
*/
SysUserPasswdRuleResp entityToResp(SysUserPasswdRule data);
/**
* 批量实体转响应
*
* @param dataList 批量实体
* @return 响应
*/
List<SysUserPasswdRuleResp> entityToRespList(List<SysUserPasswdRule> dataList);
/**
* 实体转Excel
*
* @param data 实体
* @return Excel
*/
SysUserPasswdRuleExcel entityToExcel(SysUserPasswdRule data);
/**
* Excel转实体
*
* @param data Excel
* @return 实体
*/
SysUserPasswdRule excelToEntity(SysUserPasswdRuleExcel data);
}

View File

@@ -0,0 +1,35 @@
package xtools.app.sys.job;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import xtools.app.common.job.base.BaseJob;
import xtools.app.sys.service.SysUserService;
/**
* <p>Title : SysUserJob</p>
* <p>Description : SysUserJob</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 1.0.0
* @date : 2026/3/17 21:24
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SysUserJob extends BaseJob {
private final SysUserService sysUserService;
/**
* 运行作业
*/
@Override
public void runJob() {
sysUserService.cleanSysUserJob();
}
}

View File

@@ -0,0 +1,19 @@
package xtools.app.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xtools.app.sys.model.entity.SysUserPasswdRule;
/**
* <p>Title : SysUserPasswdRuleMapper</p>
* <p>Description : 用户名密码规则 Mapper 接口</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Mapper
public interface SysUserPasswdRuleMapper extends BaseMapper<SysUserPasswdRule> {
}

View File

@@ -0,0 +1,156 @@
package xtools.app.sys.model.dto.excel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fesod.sheet.annotation.ExcelProperty;
import java.io.Serializable;
/**
* <p>Title : SysUserPasswdRuleExcel</p>
* <p>Description : 用户名密码规则Excel对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserPasswdRuleExcel implements Serializable {
/**
* 规则名称
*/
@ExcelProperty("规则名称")
private String ruleName;
/**
* 规则描述
*/
@ExcelProperty("规则描述")
private String ruleDesc;
/**
* 规则编码
*/
@ExcelProperty("规则编码")
private String ruleCode;
/**
* 最小密码长度
*/
@ExcelProperty("最小密码长度")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@ExcelProperty("最大密码长度")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@ExcelProperty("最少数字字符个数")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@ExcelProperty("最少特殊字符个数")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@ExcelProperty("最少小写字母个数")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@ExcelProperty("最少大写字母个数")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@ExcelProperty("禁止使用历史最近多少次口令")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@ExcelProperty("禁止使用键盘连续3个字符")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@ExcelProperty("禁止使用弱口令字典中的口令")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@ExcelProperty("禁止账号名在密码中所占长度超过一半")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@ExcelProperty("密码有效期天数")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@ExcelProperty("密码修改后剩余多少天进行提示")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@ExcelProperty("账号锁定后多久没启用进行账号注销")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@ExcelProperty("密码错误时间窗口")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@ExcelProperty("错误密码次数")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@ExcelProperty("账号锁定分钟数")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@ExcelProperty("账号有效期")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@ExcelProperty("账号有效期剩余天数提示")
private Integer accountTipsDays;
/**
* 备注
*/
@ExcelProperty("备注")
private String memo;
}

View File

@@ -6,6 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.Instant;
/**
* <p>Title : SysUserAddReq</p>
@@ -69,6 +70,12 @@ public class SysUserAddReq implements Serializable {
@Schema(description = "状态", example = "1")
private Integer status;
/**
* 账号失效时间,空则不失效
*/
@Schema(description = "账号失效时间,空则不失效")
private Instant accountExpirationTime;
/**
* 创建人 ID
*/

View File

@@ -0,0 +1,174 @@
package xtools.app.sys.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysUserPasswdRuleAddReq</p>
* <p>Description : 用户名密码规则添加请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserPasswdRuleAddReq implements Serializable {
/**
* 规则名称
*/
@Schema(description = "规则名称")
private String ruleName;
/**
* 规则描述
*/
@Schema(description = "规则描述")
private String ruleDesc;
/**
* 规则编码
*/
@Schema(description = "规则编码")
private String ruleCode;
/**
* 默认规则
*/
@Schema(description = "默认规则")
private Integer ruleDefault;
/**
* 最小密码长度
*/
@Schema(description = "最小密码长度")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@Schema(description = "最大密码长度")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@Schema(description = "最少数字字符个数")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@Schema(description = "最少特殊字符个数")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@Schema(description = "最少小写字母个数")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@Schema(description = "最少大写字母个数")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@Schema(description = "禁止使用历史最近多少次口令")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@Schema(description = "禁止使用键盘连续3个字符")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@Schema(description = "禁止使用弱口令字典中的口令")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@Schema(description = "禁止账号名在密码中所占长度超过一半")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@Schema(description = "密码有效期天数")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@Schema(description = "密码修改后剩余多少天进行提示")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@Schema(description = "账号锁定后多久没启用进行账号注销")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@Schema(description = "密码错误时间窗口")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@Schema(description = "错误密码次数")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@Schema(description = "账号锁定分钟数")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@Schema(description = "账号有效期,默认值为90天")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@Schema(description = "账号有效期剩余天数提示,默认值为15天")
private Integer accountTipsDays;
/**
* 创建人 ID
*/
@Schema(description = "创建人 ID")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID")
private Long updateBy;
/**
* 备注
*/
@Schema(description = "备注")
private String memo;
}

View File

@@ -0,0 +1,192 @@
package xtools.app.sys.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.Instant;
/**
* <p>Title : SysUserPasswdRulePageReq</p>
* <p>Description : 用户名密码规则分页请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserPasswdRulePageReq implements Serializable {
/**
* 规则主键id
*/
@Schema(description = "规则主键id")
private Long id;
/**
* 规则名称
*/
@Schema(description = "规则名称")
private String ruleName;
/**
* 规则描述
*/
@Schema(description = "规则描述")
private String ruleDesc;
/**
* 规则编码
*/
@Schema(description = "规则编码")
private String ruleCode;
/**
* 默认规则
*/
@Schema(description = "默认规则")
private Integer ruleDefault;
/**
* 最小密码长度
*/
@Schema(description = "最小密码长度")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@Schema(description = "最大密码长度")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@Schema(description = "最少数字字符个数")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@Schema(description = "最少特殊字符个数")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@Schema(description = "最少小写字母个数")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@Schema(description = "最少大写字母个数")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@Schema(description = "禁止使用历史最近多少次口令")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@Schema(description = "禁止使用键盘连续3个字符")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@Schema(description = "禁止使用弱口令字典中的口令")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@Schema(description = "禁止账号名在密码中所占长度超过一半")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@Schema(description = "密码有效期天数")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@Schema(description = "密码修改后剩余多少天进行提示")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@Schema(description = "账号锁定后多久没启用进行账号注销")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@Schema(description = "密码错误时间窗口")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@Schema(description = "错误密码次数")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@Schema(description = "账号锁定分钟数")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@Schema(description = "账号有效期,默认值为90天")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@Schema(description = "账号有效期剩余天数提示,默认值为15天")
private Integer accountTipsDays;
/**
* 创建人 ID
*/
@Schema(description = "创建人 ID")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID")
private Long updateBy;
/**
* 备注
*/
@Schema(description = "备注")
private String memo;
/**
* 创建时间(范围)
*/
@Schema(description = "创建时间(范围)", example = "['2026-01-01 00:00:00', '2026-01-01 12:00:00']")
private Instant[] gmtCreateRange;
/**
* 更新时间(范围)
*/
@Schema(description = "更新时间(范围)", example = "['2026-01-01 00:00:00', '2026-01-01 12:00:00']")
private Instant[] gmtModifiedRange;
}

View File

@@ -0,0 +1,180 @@
package xtools.app.sys.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysUserPasswdRuleUpdateReq</p>
* <p>Description : 用户名密码规则更新请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserPasswdRuleUpdateReq implements Serializable {
/**
* 规则主键id
*/
@Schema(description = "规则主键id")
private Long id;
/**
* 规则名称
*/
@Schema(description = "规则名称")
private String ruleName;
/**
* 规则描述
*/
@Schema(description = "规则描述")
private String ruleDesc;
/**
* 规则编码
*/
@Schema(description = "规则编码")
private String ruleCode;
/**
* 默认规则
*/
@Schema(description = "默认规则")
private Integer ruleDefault;
/**
* 最小密码长度
*/
@Schema(description = "最小密码长度")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@Schema(description = "最大密码长度")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@Schema(description = "最少数字字符个数")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@Schema(description = "最少特殊字符个数")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@Schema(description = "最少小写字母个数")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@Schema(description = "最少大写字母个数")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@Schema(description = "禁止使用历史最近多少次口令")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@Schema(description = "禁止使用键盘连续3个字符")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@Schema(description = "禁止使用弱口令字典中的口令")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@Schema(description = "禁止账号名在密码中所占长度超过一半")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@Schema(description = "密码有效期天数")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@Schema(description = "密码修改后剩余多少天进行提示")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@Schema(description = "账号锁定后多久没启用进行账号注销")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@Schema(description = "密码错误时间窗口")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@Schema(description = "错误密码次数")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@Schema(description = "账号锁定分钟数")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@Schema(description = "账号有效期,默认值为90天")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@Schema(description = "账号有效期剩余天数提示,默认值为15天")
private Integer accountTipsDays;
/**
* 创建人 ID
*/
@Schema(description = "创建人 ID")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID")
private Long updateBy;
/**
* 备注
*/
@Schema(description = "备注")
private String memo;
}

View File

@@ -6,6 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.Instant;
/**
* <p>Title : SysUserUpdateReq</p>
@@ -75,6 +76,12 @@ public class SysUserUpdateReq implements Serializable {
@Schema(description = "状态", example = "1")
private Integer status;
/**
* 账号失效时间,空则不失效
*/
@Schema(description = "账号失效时间,空则不失效")
private Instant accountExpirationTime;
/**
* 创建人 ID
*/

View File

@@ -47,4 +47,10 @@ public class SysBaseUserInfoResp implements Serializable {
*/
@Schema(description = "按钮权限")
private List<String> btnPerms;
/**
* 提示信息
*/
@Schema(description = "提示信息")
private List<String> tips;
}

View File

@@ -0,0 +1,181 @@
package xtools.app.sys.model.dto.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import xtools.boot.api.model.entity.BaseEntity;
/**
* <p>Title : SysUserPasswdRuleResp</p>
* <p>Description : 用户名密码规则响应对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SysUserPasswdRuleResp extends BaseEntity {
/**
* 规则主键id
*/
@Schema(description = "规则主键id")
private Long id;
/**
* 规则名称
*/
@Schema(description = "规则名称")
private String ruleName;
/**
* 规则描述
*/
@Schema(description = "规则描述")
private String ruleDesc;
/**
* 规则编码
*/
@Schema(description = "规则编码")
private String ruleCode;
/**
* 默认规则
*/
@Schema(description = "默认规则")
private Integer ruleDefault;
/**
* 最小密码长度
*/
@Schema(description = "最小密码长度")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@Schema(description = "最大密码长度")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@Schema(description = "最少数字字符个数")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@Schema(description = "最少特殊字符个数")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@Schema(description = "最少小写字母个数")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@Schema(description = "最少大写字母个数")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@Schema(description = "禁止使用历史最近多少次口令")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@Schema(description = "禁止使用键盘连续3个字符")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@Schema(description = "禁止使用弱口令字典中的口令")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@Schema(description = "禁止账号名在密码中所占长度超过一半")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@Schema(description = "密码有效期天数")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@Schema(description = "密码修改后剩余多少天进行提示")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@Schema(description = "账号锁定后多久没启用进行账号注销")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@Schema(description = "密码错误时间窗口")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@Schema(description = "错误密码次数")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@Schema(description = "账号锁定分钟数")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@Schema(description = "账号有效期,默认值为90天")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@Schema(description = "账号有效期剩余天数提示,默认值为15天")
private Integer accountTipsDays;
/**
* 创建人 ID
*/
@Schema(description = "创建人 ID")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID")
private Long updateBy;
/**
* 备注
*/
@Schema(description = "备注")
private String memo;
}

View File

@@ -9,6 +9,7 @@ import xtools.boot.api.model.entity.BaseEntity;
import xtools.boot.mask.anntation.Mask;
import xtools.boot.mask.enums.MaskType;
import java.time.Instant;
import java.util.List;
/**
@@ -62,7 +63,7 @@ public class SysUserResp extends BaseEntity {
* 手机号
*/
@Schema(description = "手机号", example = "13800138000")
@Mask(value = MaskType.MOBILE_PHONE, always = true)
@Mask(value = MaskType.MOBILE_PHONE)
private String mobile;
/**
@@ -78,13 +79,6 @@ public class SysUserResp extends BaseEntity {
@Schema(description = "性别", example = "1")
private Integer sex;
/**
* 密码
*/
@Schema(description = "密码", example = "******")
@Mask(MaskType.PASSWORD)
private String passwd;
/**
* 状态
*/
@@ -92,16 +86,10 @@ public class SysUserResp extends BaseEntity {
private Integer status;
/**
* 创建人 ID
* 账号失效时间,空则不失效
*/
@Schema(description = "创建人 ID", example = "1")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID", example = "1")
private Long updateBy;
@Schema(description = "账号失效时间,空则不失效")
private Instant accountExpirationTime;
/**
* 备注

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -80,7 +81,7 @@ public class SysAddress extends BaseEntity {
* 公用地址-备注
*/
@Schema(description = "公用地址-备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -94,6 +95,6 @@ public class SysDept extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -73,6 +74,6 @@ public class SysDict extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -94,6 +95,6 @@ public class SysDictItem extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -152,7 +153,7 @@ public class SysFile extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -157,6 +158,6 @@ public class SysLog extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -164,6 +165,6 @@ public class SysMenu extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -131,7 +132,7 @@ public class SysNotice extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -94,7 +95,7 @@ public class SysOptLog implements Serializable {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
/**

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -73,7 +74,7 @@ public class SysParam extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -73,7 +74,7 @@ public class SysRisk extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -87,6 +88,6 @@ public class SysRole extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -52,6 +53,6 @@ public class SysRoleMenu extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -89,6 +90,6 @@ public class SysTask extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -66,6 +67,6 @@ public class SysUpdateHistory extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -11,6 +12,8 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import xtools.boot.api.model.entity.BaseEntity;
import java.time.Instant;
/**
* <p>Title : SysUser</p>
* <p>Description : 系统用户表实体对象</p>
@@ -97,6 +100,34 @@ public class SysUser extends BaseEntity {
@TableField(value = "status")
private Integer status;
/**
* 历史密码
*/
@Schema(description = "历史密码")
@TableField(value = "history_passwd")
private String historyPasswd;
/**
* 密码修改时间
*/
@Schema(description = "密码修改时间")
@TableField(value = "passwd_gmt_modified")
private Instant passwdGmtModified;
/**
* 账号失效时间,空则不失效
*/
@Schema(description = "账号失效时间,空则不失效")
@TableField(value = "account_expiration_time", updateStrategy = FieldStrategy.ALWAYS)
private Instant accountExpirationTime;
/**
* 账号禁用时间
*/
@Schema(description = "账号禁用时间")
@TableField(value = "account_disabled_time", updateStrategy = FieldStrategy.ALWAYS)
private Instant accountDisabledTime;
/**
* 创建人 ID
*/
@@ -115,6 +146,6 @@ public class SysUser extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -75,7 +76,7 @@ public class SysUserNotice extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -0,0 +1,213 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import xtools.boot.api.model.entity.BaseEntity;
/**
* <p>Title : SysUserPasswdRule</p>
* <p>Description : 用户名密码规则实体对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user_passwd_rule")
public class SysUserPasswdRule extends BaseEntity {
/**
* 规则主键id
*/
@Schema(description = "规则主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 规则名称
*/
@Schema(description = "规则名称")
@TableField(value = "rule_name")
private String ruleName;
/**
* 规则描述
*/
@Schema(description = "规则描述")
@TableField(value = "rule_desc", updateStrategy = FieldStrategy.ALWAYS)
private String ruleDesc;
/**
* 规则编码
*/
@Schema(description = "规则编码")
@TableField(value = "rule_code")
private String ruleCode;
/**
* 默认规则
*/
@Schema(description = "默认规则")
@TableField(value = "rule_default")
private Integer ruleDefault;
/**
* 最小密码长度
*/
@Schema(description = "最小密码长度")
@TableField(value = "passwd_min_length")
private Integer passwdMinLength;
/**
* 最大密码长度
*/
@Schema(description = "最大密码长度")
@TableField(value = "passwd_max_length")
private Integer passwdMaxLength;
/**
* 最少数字字符个数
*/
@Schema(description = "最少数字字符个数")
@TableField(value = "min_num_count")
private Integer minNumCount;
/**
* 最少特殊字符个数
*/
@Schema(description = "最少特殊字符个数")
@TableField(value = "min_spec_count")
private Integer minSpecCount;
/**
* 最少小写字母个数
*/
@Schema(description = "最少小写字母个数")
@TableField(value = "min_lower_count")
private Integer minLowerCount;
/**
* 最少大写字母个数
*/
@Schema(description = "最少大写字母个数")
@TableField(value = "min_upper_count")
private Integer minUpperCount;
/**
* 禁止使用历史最近多少次口令
*/
@Schema(description = "禁止使用历史最近多少次口令")
@TableField(value = "his_passwd_count")
private Integer hisPasswdCount;
/**
* 禁止使用键盘连续3个字符
*/
@Schema(description = "禁止使用键盘连续3个字符")
@TableField(value = "continuous_check")
private Integer continuousCheck;
/**
* 禁止使用弱口令字典中的口令
*/
@Schema(description = "禁止使用弱口令字典中的口令")
@TableField(value = "invalid_check")
private Integer invalidCheck;
/**
* 禁止账号名在密码中所占长度超过一半
*/
@Schema(description = "禁止账号名在密码中所占长度超过一半")
@TableField(value = "cover_half_check")
private Integer coverHalfCheck;
/**
* 密码有效期天数
*/
@Schema(description = "密码有效期天数")
@TableField(value = "validity_days")
private Integer validityDays;
/**
* 密码修改后剩余多少天进行提示
*/
@Schema(description = "密码修改后剩余多少天进行提示")
@TableField(value = "residue_tips_days")
private Integer residueTipsDays;
/**
* 账号锁定后多久没启用进行账号注销
*/
@Schema(description = "账号锁定后多久没启用进行账号注销")
@TableField(value = "lock_cancel_days")
private Integer lockCancelDays;
/**
* 密码错误时间窗口
*/
@Schema(description = "密码错误时间窗口")
@TableField(value = "error_passwd_window_minutes")
private Integer errorPasswdWindowMinutes;
/**
* 错误密码次数
*/
@Schema(description = "错误密码次数")
@TableField(value = "error_passwd_count")
private Integer errorPasswdCount;
/**
* 账号锁定分钟数
*/
@Schema(description = "账号锁定分钟数")
@TableField(value = "error_passwd_lock_minutes")
private Integer errorPasswdLockMinutes;
/**
* 账号有效期,默认值为90天
*/
@Schema(description = "账号有效期,默认值为90天")
@TableField(value = "account_validity_days")
private Integer accountValidityDays;
/**
* 账号有效期剩余天数提示,默认值为15天
*/
@Schema(description = "账号有效期剩余天数提示,默认值为15天")
@TableField(value = "account_tips_days")
private Integer accountTipsDays;
/**
* 创建人 ID
*/
@Schema(description = "创建人 ID")
@TableField(value = "create_by")
private Long createBy;
/**
* 修改人 ID
*/
@Schema(description = "修改人 ID")
@TableField(value = "update_by")
private Long updateBy;
/**
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.model.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -52,6 +53,6 @@ public class SysUserRole extends BaseEntity {
* 备注
*/
@Schema(description = "备注")
@TableField(value = "memo")
@TableField(value = "memo", updateStrategy = FieldStrategy.ALWAYS)
private String memo;
}

View File

@@ -0,0 +1,43 @@
package xtools.app.sys.service;
import xtools.app.sys.model.entity.SysUserPasswdRule;
/**
* <p>Title : SysPasswdService</p>
* <p>Description : SysPasswdService</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 1.0.0
* @date : 2026/6/4 09:32
*/
public interface SysPasswdService {
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
*/
void checkPasswd(String passwd, String account);
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
* @param ruleCode 规则码
*/
void checkPasswd(String passwd, String account, String ruleCode);
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
* @param rule 规则
*/
void checkPasswd(String passwd, String account, SysUserPasswdRule rule);
}

View File

@@ -0,0 +1,99 @@
package xtools.app.sys.service;
import xtools.app.sys.api.SysUserPasswdRuleApi;
import xtools.app.sys.model.dto.excel.SysUserPasswdRuleExcel;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleAddReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRulePageReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleUpdateReq;
import xtools.app.sys.model.dto.resp.SysUserPasswdRuleResp;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.api.model.dto.req.IdListReq;
import java.util.List;
/**
* <p>Title : SysUserPasswdRuleService</p>
* <p>Description : 用户名密码规则 Service</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
public interface SysUserPasswdRuleService extends SysUserPasswdRuleApi {
/**
* 分页查询
*
* @param pageReq 分页请求
* @return 分页结果
*/
Result<PageResp<SysUserPasswdRuleResp>> page(PageReq<SysUserPasswdRulePageReq> pageReq);
/**
* 根据 ID 查询
*
* @param id ID
* @return 结果
*/
Result<SysUserPasswdRuleResp> getById(Long id);
/**
* 添加
*
* @param req 添加请求
* @return 添加结果
*/
Result<Boolean> add(SysUserPasswdRuleAddReq req);
/**
* 修改
*
* @param req 修改请求
* @return 修改结果
*/
Result<Boolean> update(SysUserPasswdRuleUpdateReq req);
/**
* 根据 ID 删除
*
* @param req ID 集合
* @return 删除结果
*/
Result<Boolean> delById(IdListReq req);
/**
* 设置默认
*
* @param id ID
* @return 设置结果
*/
Result<Boolean> setDef(Long id);
/**
* 根据规则code获取数据,code为空则获取默认规则
*
* @param code code
* @return 规则
*/
SysUserPasswdRule getByCode(String code);
/**
* 导出 Excel
*
* @param req 请求参数
* @return Excel 数据
*/
List<SysUserPasswdRuleExcel> exportExcel(SysUserPasswdRulePageReq req);
/**
* 导入 Excel
*
* @param dataList Excel 数据
*/
void importExcel(List<SysUserPasswdRuleExcel> dataList);
}

View File

@@ -63,6 +63,14 @@ public interface SysUserService {
*/
Result<Boolean> delById(List<Long> idList);
/**
* 重置密码
*
* @param id ID
* @return 重置密码结果
*/
Result<Boolean> resetPasswd(Long id);
/**
* 根据 ID 集合查询
*
@@ -87,4 +95,9 @@ public interface SysUserService {
*/
boolean saveSysUser(UserInfoEditReq req);
/**
* 清理禁用账号Job
*/
void cleanSysUserJob();
}

View File

@@ -0,0 +1,20 @@
package xtools.app.sys.service.base;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Component;
import xtools.app.sys.mapper.SysUserPasswdRuleMapper;
import xtools.app.sys.model.entity.SysUserPasswdRule;
/**
* <p>Title : SysUserPasswdRuleBaseService</p>
* <p>Description : 用户名密码规则 BaseService</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Component
public class SysUserPasswdRuleBaseService extends ServiceImpl<SysUserPasswdRuleMapper, SysUserPasswdRule> {
}

View File

@@ -1,5 +1,6 @@
package xtools.app.sys.service.impl;
import com.alibaba.fastjson2.JSONArray;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@@ -7,6 +8,7 @@ import org.springframework.transaction.annotation.Transactional;
import xtools.app.sys.auth.model.dto.LoginUserDto;
import xtools.app.sys.auth.utils.AuthUtils;
import xtools.app.sys.convert.SysBaseConvert;
import xtools.app.sys.convert.SysUserConvert;
import xtools.app.sys.model.dto.Sm2KeyDto;
import xtools.app.sys.model.dto.req.IgnoreMaskReq;
import xtools.app.sys.model.dto.req.UserInfoEditReq;
@@ -15,19 +17,28 @@ import xtools.app.sys.model.dto.resp.SysBaseUserInfoResp;
import xtools.app.sys.model.dto.resp.SysIpAddrResp;
import xtools.app.sys.model.entity.SysMenu;
import xtools.app.sys.model.entity.SysUser;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.app.sys.service.SysBaseService;
import xtools.app.sys.service.SysCommonService;
import xtools.app.sys.service.SysMenuService;
import xtools.app.sys.service.SysPasswdService;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.app.sys.service.SysUserService;
import xtools.app.sys.service.base.SysUserBaseService;
import xtools.app.sys.utils.PasswdUtils;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.ip.utils.IpUtils;
import xtools.core.CollectionUtils;
import xtools.core.StringUtils;
import xtools.core.time.InstantUtils;
import xtools.extend.dto.IpAddrDto;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -49,6 +60,10 @@ public class SysBaseServiceImpl implements SysBaseService, BaseParams {
private final SysCommonService sysCommonService;
private final SysUserPasswdRuleService sysUserPasswdRuleService;
private final SysPasswdService sysPasswdService;
private final SysMenuService sysMenuService;
private final SysUserService sysUserService;
@@ -57,6 +72,8 @@ public class SysBaseServiceImpl implements SysBaseService, BaseParams {
private final SysBaseConvert sysBaseConvert;
private final SysUserConvert sysUserConvert;
/**
* 获取用户信息
*
@@ -64,15 +81,18 @@ public class SysBaseServiceImpl implements SysBaseService, BaseParams {
*/
@Override
public Result<SysBaseUserInfoResp> getUserInfo() {
// 用户信息响应
SysBaseUserInfoResp resp = new SysBaseUserInfoResp();
// 获取登录用户信息
LoginUserDto loginUser = AuthUtils.get();
SysUser sysUser = sysUserBaseService.getById(loginUser.getId());
if (Objects.isNull(sysUser)) {
throw new BizWarning("用户不存在");
}
// 用户信息响应
SysBaseUserInfoResp resp = new SysBaseUserInfoResp();
// 设置用户信息
resp.setSysUser(sysUserService.getUserInfo(loginUser.getId()));
resp.setSysUser(sysUserConvert.entityToResp(sysUser));
resp.setTips(checkUser(sysUser));
// 获取用户角色信息
List<Long> roleIds = loginUser.getRoleIds();
if (CollectionUtils.isNotEmpty(roleIds)) {
@@ -85,6 +105,35 @@ public class SysBaseServiceImpl implements SysBaseService, BaseParams {
return Result.ok(resp);
}
/**
* 检查用户信息
*
* @param sysUser 用户信息
* @return 检查结果
*/
private List<String> checkUser(SysUser sysUser) {
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(null);
if (Objects.isNull(rule)) {
throw new BizError("安全规则不存在");
}
List<String> tips = new ArrayList<>();
// 密码过期提醒
Integer validityDays = rule.getValidityDays();
if (validityDays > CP_NUM0) {
Instant passwdGmtModified = sysUser.getPasswdGmtModified();
if (Objects.nonNull(passwdGmtModified) && Instant.now().isAfter(passwdGmtModified.plus(validityDays - rule.getResidueTipsDays(), ChronoUnit.DAYS))) {
Instant expirationTime = passwdGmtModified.plus(validityDays, ChronoUnit.DAYS);
tips.add("密码将于[" + InstantUtils.format(expirationTime) + "]过期,请及时修改密码,届时将无法登录");
}
}
// 账号过期提醒
Instant accountExpirationTime = sysUser.getAccountExpirationTime();
if (Objects.nonNull(accountExpirationTime) && Instant.now().isAfter(accountExpirationTime.plus(-rule.getAccountTipsDays(), ChronoUnit.DAYS))) {
tips.add("账户将于[" + InstantUtils.format(accountExpirationTime) + "]过期,请联系管理员,届时将无法登录");
}
return tips;
}
/**
* 修改用户信息
*
@@ -115,15 +164,50 @@ public class SysBaseServiceImpl implements SysBaseService, BaseParams {
Long userId = AuthUtils.get().getId();
SysUser sysUser = sysUserBaseService.getById(userId);
if (Objects.isNull(sysUser)) {
throw new BizError("用户不存在");
throw new BizWarning("用户不存在");
}
if (!PasswdUtils.check(oldPasswd, sysUser.getPasswd())) {
throw new BizError("原密码错误");
throw new BizWarning("原密码错误");
}
// 获取安全规则
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(null);
if (Objects.isNull(rule)) {
throw new BizError("安全规则不存在");
}
// 检查密码
sysPasswdService.checkPasswd(passwd, sysUser.getAccount(), rule);
// 加密密码
String encryptPasswd = PasswdUtils.encrypt(passwd);
// 历史密码
final Integer hisPasswdCount = rule.getHisPasswdCount();
String historyPasswd = sysUser.getHistoryPasswd();
JSONArray historyPasswdArr;
if (StringUtils.isBlank(historyPasswd)) {
historyPasswdArr = new JSONArray();
} else {
historyPasswdArr = JSONArray.parseArray(historyPasswd);
}
// 检查历史密码
int size = historyPasswdArr.size();
if (hisPasswdCount > CP_NUM0 && historyPasswdArr.contains(encryptPasswd)) {
for (int i = size - CP_NUM1; i > size - hisPasswdCount; i--) {
if (historyPasswdArr.getString(i).equals(encryptPasswd)) {
throw new BizWarning("密码不能与近" + hisPasswdCount + "次的历史密码相同");
}
}
}
// 保存最近20次的历史密码
if (size >= CP_NUM20) {
historyPasswdArr.removeFirst();
}
historyPasswdArr.add(sysUser.getPasswd());
// 修改密码
SysUser update = new SysUser();
update.setId(userId);
update.setPasswd(PasswdUtils.encrypt(passwd));
update.setHistoryPasswd(historyPasswdArr.toJSONString());
update.setPasswdGmtModified(Instant.now());
return Result.ok(sysUserBaseService.updateById(update));
}

View File

@@ -15,8 +15,10 @@ import xtools.app.sys.convert.SysLoginConvert;
import xtools.app.sys.model.dto.Sm2KeyDto;
import xtools.app.sys.model.dto.req.SysLoginReq;
import xtools.app.sys.model.entity.SysUser;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.app.sys.service.SysCommonService;
import xtools.app.sys.service.SysLoginService;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.app.sys.service.base.SysUserBaseService;
import xtools.app.sys.service.base.SysUserRoleBaseService;
import xtools.app.sys.utils.PasswdUtils;
@@ -25,6 +27,7 @@ import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.cache.redis.base.RedisService;
import xtools.core.BytesUtils;
import xtools.core.StringUtils;
import xtools.core.dto.ArithmeticDto;
import xtools.core.encrypt.Base64Utils;
@@ -34,6 +37,8 @@ import xtools.core.img.ImgUtils;
import java.awt.*;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
@@ -54,9 +59,15 @@ import java.util.Objects;
public class SysLoginServiceImpl implements SysLoginService, BaseParams {
private final SysCommonService sysCommonService;
private final SysUserPasswdRuleService sysUserPasswdRuleService;
private final SysUserRoleBaseService sysUserRoleBaseService;
private final SysUserBaseService sysUserBaseService;
private final SysLoginConvert sysLoginConvert;
@Resource
private RedisService redisService;
@@ -123,7 +134,7 @@ public class SysLoginServiceImpl implements SysLoginService, BaseParams {
ArithmeticDto arithmeticDto = ArithmeticUtils.create();
// 根据随机验证码生成图片验证码
byte[] imgCode = ImgUtils.getBytesImgCode(128, 36, arithmeticDto.getArithmetic());
if (imgCode == null) {
if (BytesUtils.isEmpty(imgCode)) {
throw new BizError("验证码生成失败");
}
AppCache cacheParam = AppCache.UID_CAPTCHA;
@@ -168,10 +179,26 @@ public class SysLoginServiceImpl implements SysLoginService, BaseParams {
throw new BizWarning("账户不存在");
}
SysUser user = userList.get(CP_NUM0);
checkLock(user.getId());
if (!Objects.equals(user.getStatus(), CP_NUM1)) {
throw new BizWarning("账户被禁用");
}
Instant accountExpirationTime = user.getAccountExpirationTime();
if (Objects.nonNull(accountExpirationTime) && Instant.now().isAfter(accountExpirationTime)) {
throw new BizWarning("账户已过期,请联系管理员");
}
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(null);
if (Objects.isNull(rule)) {
throw new BizError("安全规则不存在");
}
if (rule.getValidityDays() > CP_NUM0) {
Instant passwdGmtModified = user.getPasswdGmtModified();
if (Objects.nonNull(passwdGmtModified) && Instant.now().isAfter(passwdGmtModified.plus(rule.getValidityDays(), ChronoUnit.DAYS))) {
throw new BizWarning("密码已过期,请联系管理员");
}
}
if (!PasswdUtils.check(passwd, user.getPasswd())) {
passwdError(user.getId(), rule);
throw new BizWarning("密码错误");
}
LoginUserDto loginUserDto = sysLoginConvert.entityToCacheDto(user);
@@ -179,4 +206,51 @@ public class SysLoginServiceImpl implements SysLoginService, BaseParams {
redisService.del(captchaKey);
return Result.ok(AuthUtils.createToken(loginUserDto));
}
/**
* 校验账号是否被锁定
*
* @param id 用户id
*/
private void checkLock(Long id) {
String lockKey = getLockKey(id);
Long lock = redisService.get(lockKey, Long.class);
if (Objects.nonNull(lock)) {
Long time = redisService.getExpire(lockKey);
throw new BizWarning("账户已锁定,请" + Math.round(time / 60.0) + "分钟后解锁");
}
}
/**
* 密码错误
*
* @param id 用户id
* @param rule 规则
*/
private void passwdError(Long id, SysUserPasswdRule rule) {
String key = AppCache.COUNT_SYS_USER_PASSWD_ERR.key() + id;
// 获取密码错误次数
Long count = redisService.incr(key);
if (count >= rule.getErrorPasswdCount()) {
// 大于阈值锁定
redisService.set(getLockKey(id), CP_NUM1, (long) rule.getErrorPasswdLockMinutes() * BaseParams.CP_NUM60);
redisService.del(key);
throw new BizWarning("密码错误次数超过" + rule.getErrorPasswdCount() + "次,账户已锁定" + rule.getErrorPasswdLockMinutes() + "分钟");
} else {
// 更新密码错误时间窗口
redisService.expire(key, (long) rule.getErrorPasswdWindowMinutes() * BaseParams.CP_NUM60);
}
}
/**
* 获取锁定key
*
* @param id 用户id
* @return 锁定key
*/
private String getLockKey(Long id) {
return AppCache.LOCK_SYS_USER.key() + id;
}
}

View File

@@ -0,0 +1,248 @@
package xtools.app.sys.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.app.sys.service.SysPasswdService;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.core.StringUtils;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>Title : SysPasswdServiceImpl</p>
* <p>Description : SysPasswdServiceImpl</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 1.0.0
* @date : 2026/6/4 09:32
*/
@Primary
@Service
@RequiredArgsConstructor
public class SysPasswdServiceImpl implements SysPasswdService, BaseParams {
/**
* 键盘连续字符序列
*/
private static final String[] KEYBOARD_SEQUENCES = {
"1234567890",
"qwertyuiop",
"asdfghjkl",
"zxcvbnm",
"qwerty",
"asdfgh",
"zxcvbn",
"qazwsx",
"edcrfv",
"tgbyhn",
"ujmik,"
};
/**
* 常见弱口令列表
*/
private static final Set<String> WEAK_PASSWORDS = Set.of(
"111111",
"123456",
"1234567",
"12345678",
"123456789",
"1234567890",
"abc123",
"admin",
"root",
"password",
"qwerty",
"welcome",
"monkey",
"login",
"princess",
"dragon",
"master",
"qwertyuiop"
);
private final SysUserPasswdRuleService sysUserPasswdRuleService;
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
*/
@Override
public void checkPasswd(String passwd, String account) {
checkPasswd(passwd, account, CP_EMPTY);
}
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
* @param ruleCode 规则码
*/
@Override
public void checkPasswd(String passwd, String account, String ruleCode) {
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(ruleCode);
if (Objects.isNull(rule)) {
throw new BizError("安全规则不存在");
}
checkPasswd(passwd, account, rule);
}
/**
* 检查密码
*
* @param passwd 密码
* @param account 账号
* @param rule 规则
*/
@Override
public void checkPasswd(String passwd, String account, SysUserPasswdRule rule) {
if (StringUtils.isBlank(passwd)) {
throw new BizError("密码不能为空");
}
// 校验最小密码长度
if (Objects.nonNull(rule.getPasswdMinLength()) && passwd.length() < rule.getPasswdMinLength()) {
throw new BizWarning("密码长度不能少于" + rule.getPasswdMinLength() + "个字符");
}
// 校验最大密码长度
if (Objects.nonNull(rule.getPasswdMaxLength()) && passwd.length() > rule.getPasswdMaxLength()) {
throw new BizWarning("密码长度不能超过" + rule.getPasswdMaxLength() + "个字符");
}
// 校验最少数字字符个数
if (Objects.nonNull(rule.getMinNumCount()) && rule.getMinNumCount() > CP_NUM0) {
int numCount = countMatches(passwd, "\\d");
if (numCount < rule.getMinNumCount()) {
throw new BizWarning("密码中至少需要包含" + rule.getMinNumCount() + "个数字");
}
}
// 校验最少特殊字符个数
if (Objects.nonNull(rule.getMinSpecCount()) && rule.getMinSpecCount() > CP_NUM0) {
int specCount = countMatches(passwd, "[^a-zA-Z0-9]");
if (specCount < rule.getMinSpecCount()) {
throw new BizWarning("密码中至少需要包含" + rule.getMinSpecCount() + "个特殊字符");
}
}
// 校验最少小写字母个数
if (Objects.nonNull(rule.getMinLowerCount()) && rule.getMinLowerCount() > CP_NUM0) {
int lowerCount = countMatches(passwd, "[a-z]");
if (lowerCount < rule.getMinLowerCount()) {
throw new BizWarning("密码中至少需要包含" + rule.getMinLowerCount() + "个小写字母");
}
}
// 校验最少大写字母个数
if (Objects.nonNull(rule.getMinUpperCount()) && rule.getMinUpperCount() > CP_NUM0) {
int upperCount = countMatches(passwd, "[A-Z]");
if (upperCount < rule.getMinUpperCount()) {
throw new BizWarning("密码中至少需要包含" + rule.getMinUpperCount() + "个大写字母");
}
}
// 校验键盘连续字符
if (Objects.nonNull(rule.getContinuousCheck()) && rule.getContinuousCheck() > CP_NUM0) {
checkKeyboardSequence(passwd);
}
// 校验弱口令字典
if (Objects.nonNull(rule.getInvalidCheck()) && rule.getInvalidCheck() > CP_NUM0) {
checkWeakPassword(passwd);
}
// 校验账号名在密码中占比
if (Objects.nonNull(rule.getCoverHalfCheck()) && rule.getCoverHalfCheck() > CP_NUM0) {
checkAccountInPassword(passwd, account);
}
}
/**
* 统计正则匹配的次数
*
* @param password 密码
* @param regex 正则表达式
* @return 匹配次数
*/
private int countMatches(String password, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(password);
int count = CP_NUM0;
while (matcher.find()) {
count++;
}
return count;
}
/**
* 检查键盘连续字符
*
* @param password 密码
*/
private void checkKeyboardSequence(String password) {
int length = CP_NUM3;
String lowerPassword = password.toLowerCase();
for (String sequence : KEYBOARD_SEQUENCES) {
for (int i = CP_NUM0; i <= sequence.length() - length; i++) {
String subSequence = sequence.substring(i, i + length);
if (lowerPassword.contains(subSequence)) {
throw new BizWarning("密码不能包含键盘连续字符");
}
// 检查反向序列
String reversedSequence = new StringBuilder(subSequence).reverse().toString();
if (lowerPassword.contains(reversedSequence)) {
throw new BizWarning("密码不能包含键盘连续字符");
}
}
}
}
/**
* 检查弱口令
*
* @param password 密码
*/
private void checkWeakPassword(String password) {
if (WEAK_PASSWORDS.contains(password.toLowerCase())) {
throw new BizWarning("密码过于简单,请使用更复杂的密码");
}
}
/**
* 检查账号名在密码中的占比
*
* @param password 密码
* @param account 账号名
*/
private void checkAccountInPassword(String password, String account) {
if (StringUtils.isBlank(account)) {
return;
}
String lowerPassword = password.toLowerCase();
String lowerAccount = account.toLowerCase();
// 检查账号名是否出现在密码中
if (lowerPassword.contains(lowerAccount)) {
// 计算账号名在密码中的占比
double ratio = (double) account.length() / password.length();
if (ratio > 0.5) {
throw new BizWarning("账号名在密码中所占长度不能超过一半");
}
}
}
}

View File

@@ -0,0 +1,377 @@
package xtools.app.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xtools.app.common.cache.enums.AppCache;
import xtools.app.sys.auth.model.dto.LoginUserDto;
import xtools.app.sys.auth.utils.AuthUtils;
import xtools.app.sys.convert.SysUserPasswdRuleConvert;
import xtools.app.sys.model.dto.excel.SysUserPasswdRuleExcel;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleAddReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRulePageReq;
import xtools.app.sys.model.dto.req.SysUserPasswdRuleUpdateReq;
import xtools.app.sys.model.dto.resp.SysUserPasswdRuleResp;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.app.sys.service.base.SysUserPasswdRuleBaseService;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.api.model.dto.req.IdListReq;
import xtools.boot.cache.redis.base.RedisService;
import xtools.boot.db.mybatisplus.utils.QueryUtils;
import xtools.core.StringUtils;
import java.util.List;
import java.util.Objects;
/**
* <p>Title : SysUserPasswdRuleServiceImpl</p>
* <p>Description : 用户名密码规则 ServiceImpl</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@Primary
@Service
@RequiredArgsConstructor
public class SysUserPasswdRuleServiceImpl implements SysUserPasswdRuleService, BaseParams {
private final static AppCache CACHE_PARAM = AppCache.SYS_CACHE_AP_RULE;
private final SysUserPasswdRuleBaseService sysUserPasswdRuleBaseService;
private final SysUserPasswdRuleConvert sysUserPasswdRuleConvert;
@Resource
private RedisService redisService;
/**
* 分页查询
*
* @param pageReq 分页请求
* @return 分页结果
*/
@Override
public Result<PageResp<SysUserPasswdRuleResp>> page(PageReq<SysUserPasswdRulePageReq> pageReq) {
// 分页查询
Page<SysUserPasswdRule> page = getPageData(pageReq.getCurrentPage(), pageReq.getPageSize(), pageReq.getQuery());
// 分装结果
PageResp<SysUserPasswdRuleResp> pageResp = new PageResp<>(pageReq, page.getTotal(), sysUserPasswdRuleConvert.entityToRespList(page.getRecords()));
return Result.ok(pageResp);
}
/**
* 根据 ID 查询
*
* @param id ID
* @return 结果
*/
@Override
public Result<SysUserPasswdRuleResp> getById(Long id) {
SysUserPasswdRule data = sysUserPasswdRuleBaseService.getById(id);
return Result.ok(sysUserPasswdRuleConvert.entityToResp(data));
}
/**
* 添加
*
* @param req 添加请求
* @return 添加结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> add(SysUserPasswdRuleAddReq req) {
SysUserPasswdRule entity = sysUserPasswdRuleConvert.addReqToEntity(req);
if (Objects.nonNull(existsEntity(entity))) {
throw new BizWarning("数据已存在");
}
LoginUserDto loginUser = AuthUtils.get();
entity.setCreateBy(loginUser.getId());
entity.setUpdateBy(loginUser.getId());
return Result.ok(sysUserPasswdRuleBaseService.save(entity));
}
/**
* 修改
*
* @param req 修改请求
* @return 修改结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> update(SysUserPasswdRuleUpdateReq req) {
SysUserPasswdRule entity = sysUserPasswdRuleConvert.updateReqToEntity(req);
if (Objects.nonNull(existsEntity(entity))) {
throw new BizWarning("数据已存在");
}
LoginUserDto loginUser = AuthUtils.get();
entity.setUpdateBy(loginUser.getId());
boolean updated = sysUserPasswdRuleBaseService.updateById(entity);
if (!updated) {
throw new BizError("修改失败");
}
delCache();
return Result.ok(true);
}
/**
* 根据 ID 删除
*
* @param req ID 集合
* @return 删除结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> delById(IdListReq req) {
boolean removed = sysUserPasswdRuleBaseService.removeByIds(req.getIdList());
if (!removed) {
throw new BizError("删除失败");
}
delCache();
return Result.ok(true);
}
/**
* 设置默认
*
* @param id ID
* @return 设置结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> setDef(Long id) {
SysUserPasswdRule entity = new SysUserPasswdRule();
entity.setRuleDefault(CP_NUM0);
sysUserPasswdRuleBaseService.update(entity, null);
entity.setId(id);
entity.setRuleDefault(CP_NUM1);
sysUserPasswdRuleBaseService.updateById(entity);
delCache();
return Result.ok(true);
}
/**
* 根据规则code获取数据,code为空则获取默认规则
*
* @param code code
* @return 规则
*/
@Override
public SysUserPasswdRule getByCode(String code) {
// 默认规则code
String defaultCode = "default";
// 从缓存获取规则
String cacheKey = StringUtils.isBlank(code) ? defaultCode : code;
SysUserPasswdRule rule = redisService.hashGet(CACHE_PARAM.key(), cacheKey, SysUserPasswdRule.class);
if (Objects.nonNull(rule)) {
return rule;
}
// 创建查询条件
LambdaQueryWrapper<SysUserPasswdRule> query = new LambdaQueryWrapper<>();
if (StringUtils.isBlank(code)) {
query.eq(SysUserPasswdRule::getRuleDefault, CP_NUM1);
} else {
query.eq(SysUserPasswdRule::getRuleCode, code);
}
rule = sysUserPasswdRuleBaseService.getOne(query);
if (Objects.isNull(rule)) {
return null;
}
redisService.hashPut(CACHE_PARAM.key(), cacheKey, rule);
return rule;
}
/**
* 删除缓存
*/
private void delCache() {
redisService.del(CACHE_PARAM.key());
}
/**
* 导出 Excel
*
* @param req 请求参数
* @return Excel 数据
*/
@Override
public List<SysUserPasswdRuleExcel> exportExcel(SysUserPasswdRulePageReq req) {
// 创建查询条件
LambdaQueryWrapper<SysUserPasswdRule> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(
SysUserPasswdRule::getRuleName
, SysUserPasswdRule::getRuleDesc
, SysUserPasswdRule::getRuleCode
, SysUserPasswdRule::getPasswdMinLength
, SysUserPasswdRule::getPasswdMaxLength
, SysUserPasswdRule::getMinNumCount
, SysUserPasswdRule::getMinSpecCount
, SysUserPasswdRule::getMinLowerCount
, SysUserPasswdRule::getMinUpperCount
, SysUserPasswdRule::getHisPasswdCount
, SysUserPasswdRule::getContinuousCheck
, SysUserPasswdRule::getInvalidCheck
, SysUserPasswdRule::getCoverHalfCheck
, SysUserPasswdRule::getValidityDays
, SysUserPasswdRule::getResidueTipsDays
, SysUserPasswdRule::getLockCancelDays
, SysUserPasswdRule::getErrorPasswdWindowMinutes
, SysUserPasswdRule::getErrorPasswdCount
, SysUserPasswdRule::getErrorPasswdLockMinutes
, SysUserPasswdRule::getAccountValidityDays
, SysUserPasswdRule::getAccountTipsDays
, SysUserPasswdRule::getMemo
);
// 设置查询条件
setQueryWrapper(query, req);
// 排序
query.orderByDesc(SysUserPasswdRule::getGmtCreate);
List<SysUserPasswdRule> dataList = sysUserPasswdRuleBaseService.list(query);
return dataList.stream().map(sysUserPasswdRuleConvert::entityToExcel).toList();
}
/**
* 导入 Excel
*
* @param dataList Excel 数据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void importExcel(List<SysUserPasswdRuleExcel> dataList) {
for (SysUserPasswdRuleExcel excel : dataList) {
SysUserPasswdRule item = sysUserPasswdRuleConvert.excelToEntity(excel);
SysUserPasswdRule dbItem = existsEntity(item);
if (Objects.isNull(dbItem)) {
// 新增
sysUserPasswdRuleBaseService.save(item);
} else {
// 修改
item.setId(dbItem.getId());
sysUserPasswdRuleBaseService.updateById(item);
}
}
}
/**
* 获取分页数据
*
* @param currentPage 当前页
* @param pageSize 每页数量
* @param req 请求参数
* @return 分页数据
*/
private Page<SysUserPasswdRule> getPageData(Integer currentPage, Integer pageSize, SysUserPasswdRulePageReq req) {
// 创建查询条件
LambdaQueryWrapper<SysUserPasswdRule> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(
SysUserPasswdRule::getId
, SysUserPasswdRule::getRuleName
, SysUserPasswdRule::getRuleDesc
, SysUserPasswdRule::getRuleCode
, SysUserPasswdRule::getRuleDefault
, SysUserPasswdRule::getPasswdMinLength
, SysUserPasswdRule::getPasswdMaxLength
, SysUserPasswdRule::getMinNumCount
, SysUserPasswdRule::getMinSpecCount
, SysUserPasswdRule::getMinLowerCount
, SysUserPasswdRule::getMinUpperCount
, SysUserPasswdRule::getHisPasswdCount
, SysUserPasswdRule::getContinuousCheck
, SysUserPasswdRule::getInvalidCheck
, SysUserPasswdRule::getCoverHalfCheck
, SysUserPasswdRule::getValidityDays
, SysUserPasswdRule::getResidueTipsDays
, SysUserPasswdRule::getLockCancelDays
, SysUserPasswdRule::getErrorPasswdWindowMinutes
, SysUserPasswdRule::getErrorPasswdCount
, SysUserPasswdRule::getErrorPasswdLockMinutes
, SysUserPasswdRule::getAccountValidityDays
, SysUserPasswdRule::getAccountTipsDays
, SysUserPasswdRule::getCreateBy
, SysUserPasswdRule::getUpdateBy
, SysUserPasswdRule::getMemo
, SysUserPasswdRule::getGmtCreate
, SysUserPasswdRule::getGmtModified
);
// 设置查询条件
setQueryWrapper(query, req);
// 排序
query.orderByDesc(SysUserPasswdRule::getGmtCreate);
return sysUserPasswdRuleBaseService.page(QueryUtils.getPage(currentPage, pageSize), query);
}
/**
* 设置查询条件
*
* @param query 查询条件
* @param req 请求参数
*/
private void setQueryWrapper(LambdaQueryWrapper<SysUserPasswdRule> query, SysUserPasswdRulePageReq req) {
if (Objects.isNull(req)) {
return;
}
// 查询条件
query.eq(Objects.nonNull(req.getId()), SysUserPasswdRule::getId, req.getId());
query.like(StringUtils.isNotBlank(req.getRuleName()), SysUserPasswdRule::getRuleName, req.getRuleName());
query.like(StringUtils.isNotBlank(req.getRuleDesc()), SysUserPasswdRule::getRuleDesc, req.getRuleDesc());
query.like(StringUtils.isNotBlank(req.getRuleCode()), SysUserPasswdRule::getRuleCode, req.getRuleCode());
query.eq(Objects.nonNull(req.getRuleDefault()), SysUserPasswdRule::getRuleDefault, req.getRuleDefault());
query.eq(Objects.nonNull(req.getPasswdMinLength()), SysUserPasswdRule::getPasswdMinLength, req.getPasswdMinLength());
query.eq(Objects.nonNull(req.getPasswdMaxLength()), SysUserPasswdRule::getPasswdMaxLength, req.getPasswdMaxLength());
query.eq(Objects.nonNull(req.getMinNumCount()), SysUserPasswdRule::getMinNumCount, req.getMinNumCount());
query.eq(Objects.nonNull(req.getMinSpecCount()), SysUserPasswdRule::getMinSpecCount, req.getMinSpecCount());
query.eq(Objects.nonNull(req.getMinLowerCount()), SysUserPasswdRule::getMinLowerCount, req.getMinLowerCount());
query.eq(Objects.nonNull(req.getMinUpperCount()), SysUserPasswdRule::getMinUpperCount, req.getMinUpperCount());
query.eq(Objects.nonNull(req.getHisPasswdCount()), SysUserPasswdRule::getHisPasswdCount, req.getHisPasswdCount());
query.eq(Objects.nonNull(req.getContinuousCheck()), SysUserPasswdRule::getContinuousCheck, req.getContinuousCheck());
query.eq(Objects.nonNull(req.getInvalidCheck()), SysUserPasswdRule::getInvalidCheck, req.getInvalidCheck());
query.eq(Objects.nonNull(req.getCoverHalfCheck()), SysUserPasswdRule::getCoverHalfCheck, req.getCoverHalfCheck());
query.eq(Objects.nonNull(req.getValidityDays()), SysUserPasswdRule::getValidityDays, req.getValidityDays());
query.eq(Objects.nonNull(req.getResidueTipsDays()), SysUserPasswdRule::getResidueTipsDays, req.getResidueTipsDays());
query.eq(Objects.nonNull(req.getLockCancelDays()), SysUserPasswdRule::getLockCancelDays, req.getLockCancelDays());
query.eq(Objects.nonNull(req.getErrorPasswdWindowMinutes()), SysUserPasswdRule::getErrorPasswdWindowMinutes, req.getErrorPasswdWindowMinutes());
query.eq(Objects.nonNull(req.getErrorPasswdCount()), SysUserPasswdRule::getErrorPasswdCount, req.getErrorPasswdCount());
query.eq(Objects.nonNull(req.getErrorPasswdLockMinutes()), SysUserPasswdRule::getErrorPasswdLockMinutes, req.getErrorPasswdLockMinutes());
query.eq(Objects.nonNull(req.getAccountValidityDays()), SysUserPasswdRule::getAccountValidityDays, req.getAccountValidityDays());
query.eq(Objects.nonNull(req.getAccountTipsDays()), SysUserPasswdRule::getAccountTipsDays, req.getAccountTipsDays());
query.eq(Objects.nonNull(req.getCreateBy()), SysUserPasswdRule::getCreateBy, req.getCreateBy());
query.eq(Objects.nonNull(req.getUpdateBy()), SysUserPasswdRule::getUpdateBy, req.getUpdateBy());
query.like(StringUtils.isNotBlank(req.getMemo()), SysUserPasswdRule::getMemo, req.getMemo());
QueryUtils.addTimeRange(query, req.getGmtCreateRange(), SysUserPasswdRule::getGmtCreate);
QueryUtils.addTimeRange(query, req.getGmtModifiedRange(), SysUserPasswdRule::getGmtModified);
}
/**
* 判断实体是否存在
*
* @param entity 实体
* @return 是否存在
*/
private SysUserPasswdRule existsEntity(SysUserPasswdRule entity) {
// 创建查询条件
LambdaQueryWrapper<SysUserPasswdRule> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(SysUserPasswdRule::getId);
// 排除当前数据
query.ne(Objects.nonNull(entity.getId()), SysUserPasswdRule::getId, entity.getId());
query.eq(StringUtils.isNotBlank(entity.getRuleCode()), SysUserPasswdRule::getRuleCode, entity.getRuleCode());
// 校验数据时候存在的条件
return sysUserPasswdRuleBaseService.getOne(query);
}
}

View File

@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xtools.app.common.task.enums.TaskType;
import xtools.app.sys.auth.utils.AuthUtils;
import xtools.app.sys.config.SysConfig;
import xtools.app.sys.convert.SysUserConvert;
@@ -15,11 +16,14 @@ import xtools.app.sys.model.dto.req.SysUserUpdateReq;
import xtools.app.sys.model.dto.req.UserInfoEditReq;
import xtools.app.sys.model.dto.resp.SysUserResp;
import xtools.app.sys.model.entity.SysUser;
import xtools.app.sys.model.entity.SysUserPasswdRule;
import xtools.app.sys.service.SysUserPasswdRuleService;
import xtools.app.sys.service.SysUserService;
import xtools.app.sys.service.base.SysDeptBaseService;
import xtools.app.sys.service.base.SysUserBaseService;
import xtools.app.sys.service.base.SysUserRoleBaseService;
import xtools.app.sys.utils.PasswdUtils;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
@@ -27,9 +31,17 @@ import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.api.model.dto.req.IdListReq;
import xtools.boot.db.mybatisplus.utils.QueryUtils;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.boot.task.TaskBus;
import xtools.boot.task.enums.TaskStatus;
import xtools.core.StringUtils;
import xtools.core.UuidUtils;
import xtools.core.enums.LogLevel;
import xtools.core.extend.CheckUtils;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -46,12 +58,14 @@ import java.util.Objects;
@Primary
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl implements SysUserService {
public class SysUserServiceImpl implements SysUserService, BaseParams {
private final SysConfig sysConfig;
private final SysDeptBaseService sysDeptBaseService;
private final SysUserPasswdRuleService sysUserPasswdRuleService;
private final SysUserRoleBaseService sysUserRoleBaseService;
private final SysUserBaseService sysUserBaseService;
@@ -89,7 +103,6 @@ public class SysUserServiceImpl implements SysUserService {
SysUser data = sysUserBaseService.getById(id);
SysUserResp sysUserResp = sysUserConvert.entityToResp(data);
sysUserResp.setRoleIds(sysUserRoleBaseService.getUserRole(sysUserResp.getId()));
sysUserResp.setPasswd(null);
return Result.ok(sysUserResp);
}
@@ -107,6 +120,13 @@ public class SysUserServiceImpl implements SysUserService {
throw new BizWarning("数据已存在");
}
entity.setPasswd(PasswdUtils.encrypt(sysConfig.getUser().getPasswd()));
if (Objects.isNull(entity.getAccountExpirationTime())) {
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(null);
Integer validityDays = rule.getAccountValidityDays();
if (validityDays > CP_NUM0) {
entity.setAccountExpirationTime(Instant.now().plus(validityDays, ChronoUnit.DAYS));
}
}
boolean saved = sysUserBaseService.save(entity);
if (!saved) {
throw new BizError("添加失败");
@@ -130,6 +150,7 @@ public class SysUserServiceImpl implements SysUserService {
throw new BizWarning("数据已存在");
}
entity.setPasswd(null);
entity.setAccountDisabledTime(Objects.equals(entity.getStatus(), CP_NUM0) ? Instant.now() : null);
boolean updated = sysUserBaseService.updateById(entity);
if (!updated) {
throw new BizError("修改失败");
@@ -157,6 +178,30 @@ public class SysUserServiceImpl implements SysUserService {
return Result.ok(true);
}
/**
* 重置密码
*
* @param id ID
* @return 重置密码结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> resetPasswd(Long id) {
SysUser entity = sysUserBaseService.getById(id);
if (Objects.isNull(entity)) {
throw new BizError("用户不存在");
}
SysUser update = new SysUser();
update.setId(id);
update.setPasswd(PasswdUtils.encrypt(sysConfig.getUser().getPasswd()));
update.setPasswdGmtModified(Instant.now());
boolean updated = sysUserBaseService.updateById(update);
if (!updated) {
throw new BizError("重置密码失败");
}
return Result.ok(true);
}
/**
* 根据 ID 集合查询
*
@@ -208,6 +253,7 @@ public class SysUserServiceImpl implements SysUserService {
SysUser::getEmail,
SysUser::getSex,
SysUser::getStatus,
SysUser::getAccountExpirationTime,
SysUser::getMemo,
SysUser::getGmtCreate,
SysUser::getGmtModified
@@ -303,4 +349,60 @@ public class SysUserServiceImpl implements SysUserService {
return sysUserBaseService.updateById(entity);
}
/**
* 清理禁用账号Job
*/
@Override
public void cleanSysUserJob() {
// 任务日志code
String code = UuidUtils.get();
// 任务类型
TaskType taskType = TaskType.DEL_DISABLE_SYS_USER;
// 任务信息
String info = "开始清理禁用账号,请稍候...";
// 保存任务
TaskBus.init(code, taskType, TaskStatus.ING).info(info).save();
// 异常
Exception ex = null;
try {
info = cleanSysUser();
} catch (Exception e) {
ex = e;
info = "执行失败";
throw e;
} finally {
// 保存任务
TaskStatus status = Objects.isNull(ex) ? TaskStatus.SUCCESS : TaskStatus.ERROR;
TaskBus.init(code, taskType, status).info(info).save();
if (Objects.nonNull(ex)) {
// 保存异常日志
LogBus.init(LogLevel.ERROR, LogBusBaseType.TASK).error(ex).save();
}
}
}
/**
* 清理禁用账号
*
* @return 清理结果
*/
private String cleanSysUser() {
SysUserPasswdRule rule = sysUserPasswdRuleService.getByCode(null);
if (Objects.isNull(rule)) {
return "安全规则不存在";
}
Integer lockCancelDays = rule.getLockCancelDays();
if (lockCancelDays <= CP_NUM0) {
return "清理时间为0,则不清理禁用账号";
}
Instant time = Instant.now().plus(-lockCancelDays, ChronoUnit.DAYS);
// 创建查询条件
LambdaQueryWrapper<SysUser> query = new LambdaQueryWrapper<>();
query.eq(SysUser::getStatus, CP_NUM0);
query.le(SysUser::getAccountDisabledTime, time);
boolean remove = sysUserBaseService.remove(query);
return remove ? "清理成功" : "清理失败";
}
}

View File

@@ -0,0 +1,18 @@
package xtools.app.sys.call;
import org.springframework.web.service.annotation.HttpExchange;
import xtools.app.sys.api.SysUserPasswdRuleApi;
/**
* <p>Title : SysUserPasswdRuleCall</p>
* <p>Description : 用户名密码规则 Call</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-06-03 16:26:10
*/
@HttpExchange("/sys/user-passwd-rule")
public interface SysUserPasswdRuleCall extends SysUserPasswdRuleApi {
}