mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-09-17 19:06:39 +08:00
v3.6.0 三级等保重磅更新:1、【新增】双因子方式登录;2、【新增】定期修改密码;3、【新增】最大活跃时间;4、【新增】敏感数据脱敏;5、【新增】登录锁定配置;6、【新增】密码复杂度配置;7、【新增】三级等保可配置
This commit is contained in:
parent
ac7c9940bf
commit
92dddd507b
@ -19,9 +19,10 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<springboot.version>2.7.5</springboot.version>
|
<springboot.version>2.7.18</springboot.version>
|
||||||
<spring-mock.version>2.0.8</spring-mock.version>
|
<spring-mock.version>2.0.8</spring-mock.version>
|
||||||
<mybatis-plus.version>3.5.2</mybatis-plus.version>
|
<mybatis-plus.version>3.5.2</mybatis-plus.version>
|
||||||
|
<mysql-connector-j.version>8.0.33</mysql-connector-j.version>
|
||||||
<p6spy.version>3.9.1</p6spy.version>
|
<p6spy.version>3.9.1</p6spy.version>
|
||||||
<springdoc-openapi-ui.version>1.7.0</springdoc-openapi-ui.version>
|
<springdoc-openapi-ui.version>1.7.0</springdoc-openapi-ui.version>
|
||||||
<knife4j.version>4.3.0</knife4j.version>
|
<knife4j.version>4.3.0</knife4j.version>
|
||||||
@ -42,7 +43,7 @@
|
|||||||
<poi.version>5.2.4</poi.version>
|
<poi.version>5.2.4</poi.version>
|
||||||
<ooxml-schemas.version>1.4</ooxml-schemas.version>
|
<ooxml-schemas.version>1.4</ooxml-schemas.version>
|
||||||
<aws-java-sdk.version>1.11.842</aws-java-sdk.version>
|
<aws-java-sdk.version>1.11.842</aws-java-sdk.version>
|
||||||
<log4j-spring-boot.version>2.17.2</log4j-spring-boot.version>
|
<log4j-spring-boot.version>2.23.1</log4j-spring-boot.version>
|
||||||
<hutool.version>5.7.22</hutool.version>
|
<hutool.version>5.7.22</hutool.version>
|
||||||
<velocity-engine-core.version>2.3</velocity-engine-core.version>
|
<velocity-engine-core.version>2.3</velocity-engine-core.version>
|
||||||
<jjwt.version>0.9.1</jjwt.version>
|
<jjwt.version>0.9.1</jjwt.version>
|
||||||
@ -52,8 +53,12 @@
|
|||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
<bcprov.version>1.59</bcprov.version>
|
<bcprov.version>1.59</bcprov.version>
|
||||||
<jackson-datatype-jsr310.version>2.13.4</jackson-datatype-jsr310.version>
|
<jackson-datatype-jsr310.version>2.13.4</jackson-datatype-jsr310.version>
|
||||||
|
<jackson-dataformat-yaml.version>2.16.1</jackson-dataformat-yaml.version>
|
||||||
<smartdb.version>1.2.0</smartdb.version>
|
<smartdb.version>1.2.0</smartdb.version>
|
||||||
<redisson.version>3.25.0</redisson.version>
|
<redisson.version>3.25.0</redisson.version>
|
||||||
|
<snakeyaml.version>2.2</snakeyaml.version>
|
||||||
|
<freemarker.version>2.3.33</freemarker.version>
|
||||||
|
<jsoup.version>1.18.1</jsoup.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@ -81,6 +86,12 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>${mysql-connector-j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
@ -201,12 +212,6 @@
|
|||||||
<version>${commons-text.version}</version>
|
<version>${commons-text.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-spring-boot</artifactId>
|
|
||||||
<version>${log4j-spring-boot.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
@ -309,6 +314,12 @@
|
|||||||
<version>${jackson-datatype-jsr310.version}</version>
|
<version>${jackson-datatype-jsr310.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
|
<version>${jackson-dataformat-yaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.1024lab</groupId>
|
<groupId>net.1024lab</groupId>
|
||||||
<artifactId>smartdb</artifactId>
|
<artifactId>smartdb</artifactId>
|
||||||
@ -341,6 +352,24 @@
|
|||||||
<version>${redisson.version}</version>
|
<version>${redisson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>${snakeyaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>${jsoup.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.freemarker</groupId>
|
||||||
|
<artifactId>freemarker</artifactId>
|
||||||
|
<version>${freemarker.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
@ -50,9 +50,6 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
@Resource
|
@Resource
|
||||||
private SystemEnvironment systemEnvironment;
|
private SystemEnvironment systemEnvironment;
|
||||||
|
|
||||||
@Value("${sa-token.active-timeout}")
|
|
||||||
private long tokenActiveTimeout;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
|
||||||
@ -158,11 +155,6 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 小于1 ,也不需要检测
|
|
||||||
if (tokenActiveTimeout < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StpUtil.checkActiveTimeout();
|
StpUtil.checkActiveTimeout();
|
||||||
StpUtil.updateLastActiveToNow();
|
StpUtil.updateLastActiveToNow();
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import net.lab1024.sa.admin.module.system.employee.service.EmployeeService;
|
|||||||
import net.lab1024.sa.base.common.domain.PageResult;
|
import net.lab1024.sa.base.common.domain.PageResult;
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
||||||
|
import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -32,6 +34,9 @@ public class EmployeeController {
|
|||||||
@Resource
|
@Resource
|
||||||
private EmployeeService employeeService;
|
private EmployeeService employeeService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
@PostMapping("/employee/query")
|
@PostMapping("/employee/query")
|
||||||
@Operation(summary = "员工管理查询 @author 卓大")
|
@Operation(summary = "员工管理查询 @author 卓大")
|
||||||
public ResponseDTO<PageResult<EmployeeVO>> query(@Valid @RequestBody EmployeeQueryForm query) {
|
public ResponseDTO<PageResult<EmployeeVO>> query(@Valid @RequestBody EmployeeQueryForm query) {
|
||||||
@ -89,9 +94,17 @@ public class EmployeeController {
|
|||||||
|
|
||||||
@Operation(summary = "修改密码 @author 卓大")
|
@Operation(summary = "修改密码 @author 卓大")
|
||||||
@PostMapping("/employee/update/password")
|
@PostMapping("/employee/update/password")
|
||||||
|
@ApiDecrypt
|
||||||
public ResponseDTO<String> updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) {
|
public ResponseDTO<String> updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) {
|
||||||
updatePasswordForm.setEmployeeId(SmartRequestUtil.getRequestUserId());
|
updatePasswordForm.setEmployeeId(SmartRequestUtil.getRequestUserId());
|
||||||
return employeeService.updatePassword(updatePasswordForm);
|
return employeeService.updatePassword(SmartRequestUtil.getRequestUser(), updatePasswordForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取密码复杂度 @author 卓大")
|
||||||
|
@GetMapping("/employee/getPasswordComplexityEnabled")
|
||||||
|
@ApiDecrypt
|
||||||
|
public ResponseDTO<Boolean> getPasswordComplexityEnabled() {
|
||||||
|
return ResponseDTO.ok(level3ProtectConfigService.isPasswordComplexityEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "重置员工密码 @author 卓大")
|
@Operation(summary = "重置员工密码 @author 卓大")
|
||||||
|
@ -53,6 +53,11 @@ public class EmployeeEntity {
|
|||||||
*/
|
*/
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门id
|
* 部门id
|
||||||
*/
|
*/
|
||||||
|
@ -51,6 +51,9 @@ public class EmployeeAddForm {
|
|||||||
@Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确")
|
@Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确")
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
@Schema(description = "角色列表")
|
@Schema(description = "角色列表")
|
||||||
private List<Long> roleIdList;
|
private List<Long> roleIdList;
|
||||||
|
|
||||||
|
@ -24,11 +24,9 @@ public class EmployeeUpdatePasswordForm {
|
|||||||
|
|
||||||
@Schema(description = "原密码")
|
@Schema(description = "原密码")
|
||||||
@NotBlank(message = "原密码不能为空哦")
|
@NotBlank(message = "原密码不能为空哦")
|
||||||
@Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "原密码请输入6-15位(数字|大小写字母|小数点)")
|
|
||||||
private String oldPassword;
|
private String oldPassword;
|
||||||
|
|
||||||
@Schema(description = "新密码")
|
@Schema(description = "新密码")
|
||||||
@NotBlank(message = "新密码不能为空哦")
|
@NotBlank(message = "新密码不能为空哦")
|
||||||
@Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "新密码请输入6-15位(数字|大小写字母|小数点)")
|
|
||||||
private String newPassword;
|
private String newPassword;
|
||||||
}
|
}
|
||||||
|
@ -62,4 +62,7 @@ public class EmployeeVO {
|
|||||||
@Schema(description = "职务名称")
|
@Schema(description = "职务名称")
|
||||||
private String positionName;
|
private String positionName;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,6 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存员工
|
* 保存员工
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
public void saveEmployee(EmployeeEntity employee, List<Long> roleIdList) {
|
public void saveEmployee(EmployeeEntity employee, List<Long> roleIdList) {
|
||||||
@ -53,18 +52,21 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新员工
|
* 更新员工
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
public void updateEmployee(EmployeeEntity employee, List<Long> roleIdList) {
|
public void updateEmployee(EmployeeEntity employee, List<Long> roleIdList) {
|
||||||
// 保存员工 获得id
|
// 保存员工 获得id
|
||||||
employeeDao.updateById(employee);
|
employeeDao.updateById(employee);
|
||||||
|
|
||||||
if (CollectionUtils.isNotEmpty(roleIdList)) {
|
// 若为空,则删除所有角色
|
||||||
|
if (CollectionUtils.isEmpty(roleIdList)) {
|
||||||
|
roleEmployeeDao.deleteByEmployeeId(employee.getEmployeeId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
|
List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
|
||||||
this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
|
this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新员工角色
|
* 更新员工角色
|
||||||
|
@ -20,15 +20,16 @@ import net.lab1024.sa.admin.module.system.role.domain.vo.RoleEmployeeVO;
|
|||||||
import net.lab1024.sa.base.common.code.UserErrorCode;
|
import net.lab1024.sa.base.common.code.UserErrorCode;
|
||||||
import net.lab1024.sa.base.common.constant.StringConst;
|
import net.lab1024.sa.base.common.constant.StringConst;
|
||||||
import net.lab1024.sa.base.common.domain.PageResult;
|
import net.lab1024.sa.base.common.domain.PageResult;
|
||||||
|
import net.lab1024.sa.base.common.domain.RequestUser;
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
|
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
|
||||||
import net.lab1024.sa.base.common.util.SmartBeanUtil;
|
import net.lab1024.sa.base.common.util.SmartBeanUtil;
|
||||||
import net.lab1024.sa.base.common.util.SmartPageUtil;
|
import net.lab1024.sa.base.common.util.SmartPageUtil;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
|
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService;
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -46,8 +47,6 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class EmployeeService {
|
public class EmployeeService {
|
||||||
|
|
||||||
private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private EmployeeDao employeeDao;
|
private EmployeeDao employeeDao;
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ public class EmployeeService {
|
|||||||
private DepartmentService departmentService;
|
private DepartmentService departmentService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProtectPasswordService protectPasswordService;
|
private SecurityPasswordService securityPasswordService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@Lazy
|
@Lazy
|
||||||
@ -121,16 +120,11 @@ public class EmployeeService {
|
|||||||
* 新增员工
|
* 新增员工
|
||||||
*/
|
*/
|
||||||
public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
|
public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
|
||||||
// 校验名称是否重复
|
// 校验登录名是否重复
|
||||||
EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null);
|
EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null);
|
||||||
if (null != employeeEntity) {
|
if (null != employeeEntity) {
|
||||||
return ResponseDTO.userErrorParam("登录名重复");
|
return ResponseDTO.userErrorParam("登录名重复");
|
||||||
}
|
}
|
||||||
// 校验姓名是否重复
|
|
||||||
employeeEntity = employeeDao.getByActualName(employeeAddForm.getActualName(), null);
|
|
||||||
if (null != employeeEntity) {
|
|
||||||
return ResponseDTO.userErrorParam("姓名重复");
|
|
||||||
}
|
|
||||||
// 校验电话是否存在
|
// 校验电话是否存在
|
||||||
employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null);
|
employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null);
|
||||||
if (null != employeeEntity) {
|
if (null != employeeEntity) {
|
||||||
@ -146,8 +140,8 @@ public class EmployeeService {
|
|||||||
EmployeeEntity entity = SmartBeanUtil.copy(employeeAddForm, EmployeeEntity.class);
|
EmployeeEntity entity = SmartBeanUtil.copy(employeeAddForm, EmployeeEntity.class);
|
||||||
|
|
||||||
// 设置密码 默认密码
|
// 设置密码 默认密码
|
||||||
String password = protectPasswordService.randomPassword();
|
String password = securityPasswordService.randomPassword();
|
||||||
entity.setLoginPwd(getEncryptPwd(password));
|
entity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password));
|
||||||
|
|
||||||
// 保存数据
|
// 保存数据
|
||||||
entity.setDeletedFlag(Boolean.FALSE);
|
entity.setDeletedFlag(Boolean.FALSE);
|
||||||
@ -185,11 +179,6 @@ public class EmployeeService {
|
|||||||
return ResponseDTO.userErrorParam("手机号已存在");
|
return ResponseDTO.userErrorParam("手机号已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
existEntity = employeeDao.getByActualName(employeeUpdateForm.getActualName(), null);
|
|
||||||
if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) {
|
|
||||||
return ResponseDTO.userErrorParam("姓名重复");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不更新密码
|
// 不更新密码
|
||||||
EmployeeEntity entity = SmartBeanUtil.copy(employeeUpdateForm, EmployeeEntity.class);
|
EmployeeEntity entity = SmartBeanUtil.copy(employeeUpdateForm, EmployeeEntity.class);
|
||||||
entity.setLoginPwd(null);
|
entity.setLoginPwd(null);
|
||||||
@ -301,36 +290,46 @@ public class EmployeeService {
|
|||||||
/**
|
/**
|
||||||
* 更新密码
|
* 更新密码
|
||||||
*/
|
*/
|
||||||
public ResponseDTO<String> updatePassword(EmployeeUpdatePasswordForm updatePasswordForm) {
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
|
public ResponseDTO<String> updatePassword(RequestUser requestUser, EmployeeUpdatePasswordForm updatePasswordForm) {
|
||||||
Long employeeId = updatePasswordForm.getEmployeeId();
|
Long employeeId = updatePasswordForm.getEmployeeId();
|
||||||
EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
|
EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
|
||||||
if (employeeEntity == null) {
|
if (employeeEntity == null) {
|
||||||
return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
|
return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
|
||||||
}
|
}
|
||||||
// 校验原始密码
|
// 校验原始密码
|
||||||
String encryptPwd = getEncryptPwd(updatePasswordForm.getOldPassword());
|
String oldPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getOldPassword());
|
||||||
if (!Objects.equals(encryptPwd, employeeEntity.getLoginPwd())) {
|
if (!Objects.equals(oldPassword, employeeEntity.getLoginPwd())) {
|
||||||
return ResponseDTO.userErrorParam("原密码有误,请重新输入");
|
return ResponseDTO.userErrorParam("原密码有误,请重新输入");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验密码复杂度
|
||||||
|
ResponseDTO<String> validatePassComplexity = securityPasswordService.validatePasswordComplexity(updatePasswordForm.getNewPassword());
|
||||||
|
if (!validatePassComplexity.getOk()) {
|
||||||
|
return validatePassComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
// 新旧密码相同
|
// 新旧密码相同
|
||||||
String newPassword = updatePasswordForm.getNewPassword();
|
String newPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword());
|
||||||
if (Objects.equals(updatePasswordForm.getOldPassword(), newPassword)) {
|
if (Objects.equals(oldPassword, newPassword)) {
|
||||||
return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入");
|
return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验密码复杂度
|
// 根据三级等保规则,校验密码是否重复
|
||||||
ResponseDTO<String> validatePassComplexity = protectPasswordService.validatePassComplexity(newPassword);
|
ResponseDTO<String> passwordRepeatTimes = securityPasswordService.validatePasswordRepeatTimes(requestUser, updatePasswordForm.getNewPassword());
|
||||||
if (!validatePassComplexity.getOk()) {
|
if (!passwordRepeatTimes.getOk()) {
|
||||||
return validatePassComplexity;
|
return ResponseDTO.error(passwordRepeatTimes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新密码
|
// 更新密码
|
||||||
EmployeeEntity updateEntity = new EmployeeEntity();
|
EmployeeEntity updateEntity = new EmployeeEntity();
|
||||||
updateEntity.setEmployeeId(employeeId);
|
updateEntity.setEmployeeId(employeeId);
|
||||||
updateEntity.setLoginPwd(getEncryptPwd(newPassword));
|
updateEntity.setLoginPwd(newPassword);
|
||||||
employeeDao.updateById(updateEntity);
|
employeeDao.updateById(updateEntity);
|
||||||
|
|
||||||
|
// 保存修改密码密码记录
|
||||||
|
securityPasswordService.saveUserChangePasswordLog(requestUser, newPassword, oldPassword);
|
||||||
|
|
||||||
return ResponseDTO.ok();
|
return ResponseDTO.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,18 +363,11 @@ public class EmployeeService {
|
|||||||
* 重置密码
|
* 重置密码
|
||||||
*/
|
*/
|
||||||
public ResponseDTO<String> resetPassword(Integer employeeId) {
|
public ResponseDTO<String> resetPassword(Integer employeeId) {
|
||||||
String password = protectPasswordService.randomPassword();
|
String password = securityPasswordService.randomPassword();
|
||||||
employeeDao.updatePassword(employeeId, getEncryptPwd(password));
|
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
||||||
return ResponseDTO.ok(password);
|
return ResponseDTO.ok(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 加密后 的密码
|
|
||||||
*/
|
|
||||||
public static String getEncryptPwd(String password) {
|
|
||||||
return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询全部员工
|
* 查询全部员工
|
||||||
|
@ -2,8 +2,8 @@ package net.lab1024.sa.admin.module.system.login.controller;
|
|||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.extra.servlet.ServletUtil;
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import net.lab1024.sa.admin.constant.AdminSwaggerTagConst;
|
import net.lab1024.sa.admin.constant.AdminSwaggerTagConst;
|
||||||
import net.lab1024.sa.admin.module.system.login.domain.LoginForm;
|
import net.lab1024.sa.admin.module.system.login.domain.LoginForm;
|
||||||
import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO;
|
import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO;
|
||||||
@ -14,6 +14,7 @@ import net.lab1024.sa.base.common.constant.RequestHeaderConst;
|
|||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
import net.lab1024.sa.base.common.util.SmartRequestUtil;
|
||||||
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
|
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -36,6 +37,9 @@ public class LoginController {
|
|||||||
@Resource
|
@Resource
|
||||||
private LoginService loginService;
|
private LoginService loginService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
@NoNeedLogin
|
@NoNeedLogin
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@Operation(summary = "登录 @author 卓大")
|
@Operation(summary = "登录 @author 卓大")
|
||||||
@ -48,8 +52,8 @@ public class LoginController {
|
|||||||
@GetMapping("/login/getLoginInfo")
|
@GetMapping("/login/getLoginInfo")
|
||||||
@Operation(summary = "获取登录结果信息 @author 卓大")
|
@Operation(summary = "获取登录结果信息 @author 卓大")
|
||||||
public ResponseDTO<LoginResultVO> getLoginInfo() {
|
public ResponseDTO<LoginResultVO> getLoginInfo() {
|
||||||
LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser());
|
|
||||||
String tokenValue = StpUtil.getTokenValue();
|
String tokenValue = StpUtil.getTokenValue();
|
||||||
|
LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), tokenValue);
|
||||||
loginResult.setToken(tokenValue);
|
loginResult.setToken(tokenValue);
|
||||||
return ResponseDTO.ok(loginResult);
|
return ResponseDTO.ok(loginResult);
|
||||||
}
|
}
|
||||||
@ -67,4 +71,20 @@ public class LoginController {
|
|||||||
return loginService.getCaptcha();
|
return loginService.getCaptcha();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NoNeedLogin
|
||||||
|
@GetMapping("/login/sendEmailCode/{loginName}")
|
||||||
|
@Operation(summary = "获取邮箱登录验证码 @author 卓大")
|
||||||
|
public ResponseDTO<String> sendEmailCode(@PathVariable String loginName) {
|
||||||
|
return loginService.sendEmailCode(loginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NoNeedLogin
|
||||||
|
@GetMapping("/login/getTwoFactorLoginFlag")
|
||||||
|
@Operation(summary = "获取双因子登录标识 @author 卓大")
|
||||||
|
public ResponseDTO<Boolean> getTwoFactorLoginFlag() {
|
||||||
|
// 双因子登录
|
||||||
|
boolean twoFactorLoginEnabled = level3ProtectConfigService.isTwoFactorLoginEnabled();
|
||||||
|
return ResponseDTO.ok(twoFactorLoginEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,7 @@ public class LoginForm extends CaptchaForm {
|
|||||||
@SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class)
|
@SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class)
|
||||||
@CheckEnum(value = LoginDeviceEnum.class, required = true, message = "此终端不允许登录")
|
@CheckEnum(value = LoginDeviceEnum.class, required = true, message = "此终端不允许登录")
|
||||||
private Integer loginDevice;
|
private Integer loginDevice;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱验证码")
|
||||||
|
private String emailCode;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,9 @@ public class LoginResultVO extends RequestEmployee {
|
|||||||
@Schema(description = "菜单列表")
|
@Schema(description = "菜单列表")
|
||||||
private List<MenuVO> menuList;
|
private List<MenuVO> menuList;
|
||||||
|
|
||||||
|
@Schema(description = "是否需要修改密码")
|
||||||
|
private Boolean needUpdatePwdFlag;
|
||||||
|
|
||||||
@Schema(description = "上次登录ip")
|
@Schema(description = "上次登录ip")
|
||||||
private String lastLoginIp;
|
private String lastLoginIp;
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package net.lab1024.sa.admin.module.system.login.service;
|
|||||||
import cn.dev33.satoken.stp.StpInterface;
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.core.lang.UUID;
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.extra.servlet.ServletUtil;
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -27,7 +29,10 @@ import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
|
|||||||
import net.lab1024.sa.base.common.util.SmartBeanUtil;
|
import net.lab1024.sa.base.common.util.SmartBeanUtil;
|
||||||
import net.lab1024.sa.base.common.util.SmartEnumUtil;
|
import net.lab1024.sa.base.common.util.SmartEnumUtil;
|
||||||
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
import net.lab1024.sa.base.common.util.SmartIpUtil;
|
||||||
|
import net.lab1024.sa.base.common.util.SmartStringUtil;
|
||||||
import net.lab1024.sa.base.constant.LoginDeviceEnum;
|
import net.lab1024.sa.base.constant.LoginDeviceEnum;
|
||||||
|
import net.lab1024.sa.base.constant.RedisKeyConst;
|
||||||
|
import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService;
|
||||||
import net.lab1024.sa.base.module.support.captcha.CaptchaService;
|
import net.lab1024.sa.base.module.support.captcha.CaptchaService;
|
||||||
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
|
import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO;
|
||||||
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
|
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
|
||||||
@ -37,9 +42,13 @@ import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum;
|
|||||||
import net.lab1024.sa.base.module.support.loginlog.LoginLogService;
|
import net.lab1024.sa.base.module.support.loginlog.LoginLogService;
|
||||||
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity;
|
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity;
|
||||||
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO;
|
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.MailService;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.redis.RedisService;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity;
|
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectLoginService;
|
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
|
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityLoginService;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -107,14 +116,26 @@ public class LoginService implements StpInterface {
|
|||||||
private RoleMenuService roleMenuService;
|
private RoleMenuService roleMenuService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProtectLoginService protectLoginService;
|
private SecurityLoginService securityLoginService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProtectPasswordService profectPasswordService;
|
private SecurityPasswordService protectPasswordService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IFileStorageService fileStorageService;
|
private IFileStorageService fileStorageService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApiEncryptService apiEncryptService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MailService mailService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取验证码
|
* 获取验证码
|
||||||
*/
|
*/
|
||||||
@ -153,12 +174,18 @@ public class LoginService implements StpInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解密前端加密的密码
|
// 解密前端加密的密码
|
||||||
String requestPassword = profectPasswordService.decryptPassword(loginForm.getPassword());
|
String requestPassword = apiEncryptService.decrypt(loginForm.getPassword());
|
||||||
|
|
||||||
// 验证密码 是否为万能密码
|
// 验证密码 是否为万能密码
|
||||||
String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
|
String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
|
||||||
boolean superPasswordFlag = superPassword.equals(requestPassword);
|
boolean superPasswordFlag = superPassword.equals(requestPassword);
|
||||||
|
|
||||||
|
// 校验双因子登录
|
||||||
|
ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
|
||||||
|
if (!validateEmailCode.getOk()) {
|
||||||
|
return ResponseDTO.error(validateEmailCode);
|
||||||
|
}
|
||||||
|
|
||||||
// 万能密码特殊操作
|
// 万能密码特殊操作
|
||||||
if (superPasswordFlag) {
|
if (superPasswordFlag) {
|
||||||
|
|
||||||
@ -170,23 +197,27 @@ public class LoginService implements StpInterface {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
// 按照等保登录要求,进行登录失败次数校验
|
// 按照等保登录要求,进行登录失败次数校验
|
||||||
ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = protectLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
|
ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
|
||||||
if (!loginFailEntityResponseDTO.getOk()) {
|
if (!loginFailEntityResponseDTO.getOk()) {
|
||||||
return ResponseDTO.error(loginFailEntityResponseDTO);
|
return ResponseDTO.error(loginFailEntityResponseDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 密码错误
|
// 密码错误
|
||||||
if (!employeeEntity.getLoginPwd().equals(EmployeeService.getEncryptPwd(requestPassword))) {
|
if (!employeeEntity.getLoginPwd().equals(SecurityPasswordService.getEncryptPwd(requestPassword))) {
|
||||||
// 记录登录失败
|
// 记录登录失败
|
||||||
saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
|
saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
|
||||||
// 记录等级保护次数
|
// 记录等级保护次数
|
||||||
String msg = protectLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
|
String msg = securityLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
|
||||||
return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
|
return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
|
String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
|
StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
|
||||||
|
|
||||||
|
// 移除邮箱验证码
|
||||||
|
deleteEmailCode(employeeEntity.getEmployeeId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取员工信息
|
// 获取员工信息
|
||||||
@ -196,16 +227,17 @@ public class LoginService implements StpInterface {
|
|||||||
loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
|
loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
|
||||||
|
|
||||||
// 移除登录失败
|
// 移除登录失败
|
||||||
protectLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
|
securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
|
||||||
|
|
||||||
// 获取登录结果信息
|
// 获取登录结果信息
|
||||||
LoginResultVO loginResultVO = getLoginResult(requestEmployee);
|
String token = StpUtil.getTokenValue();
|
||||||
|
LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
|
||||||
|
|
||||||
//保存登录记录
|
//保存登录记录
|
||||||
saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
|
saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
|
||||||
|
|
||||||
// 设置 token
|
// 设置 token
|
||||||
loginResultVO.setToken(StpUtil.getTokenValue());
|
loginResultVO.setToken(token);
|
||||||
|
|
||||||
// 清除权限缓存
|
// 清除权限缓存
|
||||||
permissionCache.remove(employeeEntity.getEmployeeId());
|
permissionCache.remove(employeeEntity.getEmployeeId());
|
||||||
@ -217,7 +249,7 @@ public class LoginService implements StpInterface {
|
|||||||
/**
|
/**
|
||||||
* 获取登录结果信息
|
* 获取登录结果信息
|
||||||
*/
|
*/
|
||||||
public LoginResultVO getLoginResult(RequestEmployee requestEmployee) {
|
public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) {
|
||||||
|
|
||||||
// 基础信息
|
// 基础信息
|
||||||
LoginResultVO loginResultVO = SmartBeanUtil.copy(requestEmployee, LoginResultVO.class);
|
LoginResultVO loginResultVO = SmartBeanUtil.copy(requestEmployee, LoginResultVO.class);
|
||||||
@ -240,6 +272,16 @@ public class LoginService implements StpInterface {
|
|||||||
loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
|
loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否需要强制修改密码
|
||||||
|
boolean needChangePasswordFlag = protectPasswordService.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId());
|
||||||
|
loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag);
|
||||||
|
|
||||||
|
// 万能密码登录,则不需要设置强制修改密码
|
||||||
|
String loginIdByToken = (String) StpUtil.getLoginIdByToken(token);
|
||||||
|
if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
|
||||||
|
loginResultVO.setNeedUpdatePwdFlag(false);
|
||||||
|
}
|
||||||
|
|
||||||
return loginResultVO;
|
return loginResultVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +399,6 @@ public class LoginService implements StpInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除员工登录缓存
|
* 清除员工登录缓存
|
||||||
* @param employeeId
|
|
||||||
*/
|
*/
|
||||||
public void clearLoginEmployeeCache(Long employeeId) {
|
public void clearLoginEmployeeCache(Long employeeId) {
|
||||||
// 清空登录信息缓存
|
// 清空登录信息缓存
|
||||||
@ -451,4 +492,94 @@ public class LoginService implements StpInterface {
|
|||||||
|
|
||||||
return userPermission;
|
return userPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 邮箱 验证码
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> sendEmailCode(String loginName) {
|
||||||
|
|
||||||
|
// 开启双因子登录
|
||||||
|
if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
|
||||||
|
return ResponseDTO.userErrorParam("无需使用邮箱验证码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证登录名
|
||||||
|
EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName);
|
||||||
|
if (null == employeeEntity) {
|
||||||
|
return ResponseDTO.userErrorParam("登录名不存在!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证账号状态
|
||||||
|
if (employeeEntity.getDisabledFlag()) {
|
||||||
|
return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
|
||||||
|
}
|
||||||
|
|
||||||
|
String mail = employeeEntity.getEmail();
|
||||||
|
if (SmartStringUtil.isBlank(mail)) {
|
||||||
|
return ResponseDTO.userErrorParam("您暂未配置邮箱地址,请联系管理员配置邮箱");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验验证码发送时间,60秒内不能重复发生
|
||||||
|
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
|
||||||
|
String emailCode = redisService.get(redisVerificationCodeKey);
|
||||||
|
long sendCodeTimeMills = -1;
|
||||||
|
if (!SmartStringUtil.isEmpty(emailCode)) {
|
||||||
|
sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) {
|
||||||
|
return ResponseDTO.userErrorParam("邮箱验证码已发送,一分钟内请勿重复发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成验证码
|
||||||
|
long currentTimeMillis = System.currentTimeMillis();
|
||||||
|
String verificationCode = RandomUtil.randomNumbers(4);
|
||||||
|
redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300);
|
||||||
|
|
||||||
|
// 发送邮件验证码
|
||||||
|
HashMap<String, Object> mailParams = new HashMap<>();
|
||||||
|
mailParams.put("code", verificationCode);
|
||||||
|
return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验邮箱验证码
|
||||||
|
*/
|
||||||
|
private ResponseDTO<String> validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) {
|
||||||
|
// 万能密码则不校验
|
||||||
|
if (superPasswordFlag) {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未开启双因子登录
|
||||||
|
if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SmartStringUtil.isEmpty(loginForm.getEmailCode())) {
|
||||||
|
return ResponseDTO.userErrorParam("请输入邮箱验证码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验验证码
|
||||||
|
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
|
||||||
|
String emailCode = redisService.get(redisVerificationCodeKey);
|
||||||
|
if (SmartStringUtil.isEmpty(emailCode)) {
|
||||||
|
return ResponseDTO.userErrorParam("邮箱验证码已失效,请重新发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) {
|
||||||
|
return ResponseDTO.userErrorParam("邮箱验证码错误,请重新填写");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除邮箱验证码
|
||||||
|
*/
|
||||||
|
private void deleteEmailCode(Long employeeId) {
|
||||||
|
String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId);
|
||||||
|
redisService.delete(redisVerificationCodeKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package net.lab1024.sa.admin.module.system.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.Data;
|
||||||
|
import net.lab1024.sa.base.common.controller.SupportBaseController;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
|
import net.lab1024.sa.base.constant.SwaggerTagConst;
|
||||||
|
import net.lab1024.sa.base.module.support.datamasking.DataMasking;
|
||||||
|
import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据脱敏demo
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-主任:卓大
|
||||||
|
* @Date 2024/08/01 22:07:27
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = SwaggerTagConst.Support.DATA_MASKING)
|
||||||
|
public class AdminDataMaskingDemoController extends SupportBaseController {
|
||||||
|
|
||||||
|
@Operation(summary = "数据脱敏demo @author 1024创新实验室-主任-卓大")
|
||||||
|
@GetMapping("/dataMasking/demo/query")
|
||||||
|
public ResponseDTO<List<DataVO>> query() {
|
||||||
|
|
||||||
|
List<DataVO> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < RandomUtil.randomInt(10,16); i++) {
|
||||||
|
DataVO data = new DataVO();
|
||||||
|
data.setUserId(RandomUtil.randomLong(1328479238, 83274298347982L));
|
||||||
|
data.setPhone("1" + RandomUtil.randomNumbers(10));
|
||||||
|
data.setIdCard("410" + RandomUtil.randomNumbers(3) + RandomUtil.randomInt(1980, 2010) + RandomUtil.randomInt(10, 12) + RandomUtil.randomInt(10, 30) + RandomUtil.randomNumbers(4));
|
||||||
|
data.setAddress(RandomUtil.randomBoolean() ? "河南省洛阳市洛龙区一零二四大街1024号" : "河南省郑州市高新区六边形大街六边形大楼");
|
||||||
|
data.setPassword(RandomUtil.randomString(10));
|
||||||
|
data.setEmail(RandomUtil.randomString(RandomUtil.randomInt(6, 10)) + "@" + RandomUtil.randomString(2) + ".com");
|
||||||
|
data.setCarLicense("豫" + RandomStringUtils.randomAlphabetic(1).toUpperCase()+" " + RandomStringUtils.randomAlphanumeric(5).toUpperCase());
|
||||||
|
data.setBankCard("6225" + RandomStringUtils.randomNumeric(14));
|
||||||
|
data.setOther(RandomStringUtils.randomAlphanumeric(1, 12));
|
||||||
|
list.add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseDTO.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DataVO {
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.USER_ID)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.PHONE)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.ID_CARD)
|
||||||
|
private String idCard;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.ADDRESS)
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.PASSWORD)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.EMAIL)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.CAR_LICENSE)
|
||||||
|
private String carLicense;
|
||||||
|
|
||||||
|
@DataMasking(DataMaskingTypeEnum.BANK_CARD)
|
||||||
|
private String bankCard;
|
||||||
|
|
||||||
|
@DataMasking
|
||||||
|
private String other;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,15 +1,20 @@
|
|||||||
package net.lab1024.sa.admin.module.system.support;
|
package net.lab1024.sa.admin.module.system.support;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import net.lab1024.sa.base.common.controller.SupportBaseController;
|
import net.lab1024.sa.base.common.controller.SupportBaseController;
|
||||||
import net.lab1024.sa.base.common.domain.PageResult;
|
import net.lab1024.sa.base.common.domain.PageResult;
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.domain.ValidateList;
|
import net.lab1024.sa.base.common.domain.ValidateList;
|
||||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
|
import net.lab1024.sa.base.constant.SwaggerTagConst;
|
||||||
|
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.config.ConfigService;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm;
|
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO;
|
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectLoginService;
|
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityLoginService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -18,7 +23,6 @@ import javax.annotation.Resource;
|
|||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* 网络安全
|
* 网络安全
|
||||||
*
|
*
|
||||||
* @Author 1024创新实验室-主任:卓大
|
* @Author 1024创新实验室-主任:卓大
|
||||||
@ -33,20 +37,37 @@ import javax.validation.Valid;
|
|||||||
public class AdminProtectController extends SupportBaseController {
|
public class AdminProtectController extends SupportBaseController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProtectLoginService protectLoginService;
|
private SecurityLoginService securityLoginService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ConfigService configService;
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大")
|
@Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大")
|
||||||
@PostMapping("/protect/loginFail/queryPage")
|
@PostMapping("/protect/loginFail/queryPage")
|
||||||
public ResponseDTO<PageResult<LoginFailVO>> queryPage(@RequestBody @Valid LoginFailQueryForm queryForm) {
|
public ResponseDTO<PageResult<LoginFailVO>> queryPage(@RequestBody @Valid LoginFailQueryForm queryForm) {
|
||||||
return ResponseDTO.ok(protectLoginService.queryPage(queryForm));
|
return ResponseDTO.ok(securityLoginService.queryPage(queryForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "批量删除 @author 1024创新实验室-主任-卓大")
|
@Operation(summary = "批量删除 @author 1024创新实验室-主任-卓大")
|
||||||
@PostMapping("/protect/loginFail/batchDelete")
|
@PostMapping("/protect/loginFail/batchDelete")
|
||||||
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
|
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
|
||||||
return protectLoginService.batchDelete(idList);
|
return securityLoginService.batchDelete(idList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新三级等保配置 @author 1024创新实验室-主任-卓大")
|
||||||
|
@PostMapping("/protect/level3protect/updateConfig")
|
||||||
|
public ResponseDTO<String> updateConfig(@RequestBody @Valid Level3ProtectConfigForm configForm) {
|
||||||
|
return level3ProtectConfigService.updateLevel3Config(configForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询 三级等保配置 @author 1024创新实验室-主任-卓大")
|
||||||
|
@GetMapping("/protect/level3protect/getConfig")
|
||||||
|
public ResponseDTO<String> getConfig() {
|
||||||
|
return ResponseDTO.ok(configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,26 +20,3 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: '@profiles.active@'
|
active: '@profiles.active@'
|
||||||
|
|
||||||
####################################### 安全等级保护 相关配置 ##################################################
|
|
||||||
# #
|
|
||||||
# 建议开启 "三级等保" 所要求的配置,具体如下: #
|
|
||||||
# 1)连续登录失败 5 次锁定账户 30 分钟, #
|
|
||||||
# 2)登录超时时长建议为 30分钟,超过此时间没有访问系统会重新要求登录 #
|
|
||||||
# 3)密码复杂度至少三种字符,最小 8 位 #
|
|
||||||
# #
|
|
||||||
#############################################################################################################
|
|
||||||
|
|
||||||
classified-protect:
|
|
||||||
# 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
|
||||||
login-max-fail-times: 5
|
|
||||||
# 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
|
||||||
login-fail-locked-seconds: 1800
|
|
||||||
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启,false 不开启,建议开启
|
|
||||||
password-complexity-enabled: true
|
|
||||||
|
|
||||||
sa-token:
|
|
||||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
|
||||||
active-timeout: 1800
|
|
||||||
# token 有效期(单位:秒) 1天(86400秒),-1 代表永久有效
|
|
||||||
timeout: 86400
|
|
@ -20,26 +20,3 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: '@profiles.active@'
|
active: '@profiles.active@'
|
||||||
|
|
||||||
####################################### 安全等级保护 相关配置 ##################################################
|
|
||||||
# #
|
|
||||||
# 建议开启 "三级等保" 所要求的配置,具体如下: #
|
|
||||||
# 1)连续登录失败 5 次锁定账户 30 分钟, #
|
|
||||||
# 2)登录超时时长建议为 30分钟,超过此时间没有访问系统会重新要求登录 #
|
|
||||||
# 3)密码复杂度至少三种字符,最小 8 位 #
|
|
||||||
# #
|
|
||||||
#############################################################################################################
|
|
||||||
|
|
||||||
classified-protect:
|
|
||||||
# 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
|
||||||
login-max-fail-times: 5
|
|
||||||
# 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
|
||||||
login-fail-locked-seconds: 1800
|
|
||||||
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启,false 不开启,建议开启
|
|
||||||
password-complexity-enabled: true
|
|
||||||
|
|
||||||
sa-token:
|
|
||||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
|
||||||
active-timeout: 1800
|
|
||||||
# token 有效期(单位:秒) 1天(86400秒),-1 代表永久有效
|
|
||||||
timeout: 86400
|
|
@ -20,26 +20,3 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: '@profiles.active@'
|
active: '@profiles.active@'
|
||||||
|
|
||||||
####################################### 安全等级保护 相关配置 ##################################################
|
|
||||||
# #
|
|
||||||
# 建议开启 "三级等保" 所要求的配置,具体如下: #
|
|
||||||
# 1)连续登录失败 5 次锁定账户 30 分钟, #
|
|
||||||
# 2)登录超时时长建议为 30分钟,超过此时间没有访问系统会重新要求登录 #
|
|
||||||
# 3)密码复杂度至少三种字符,最小 8 位 #
|
|
||||||
# #
|
|
||||||
#############################################################################################################
|
|
||||||
|
|
||||||
classified-protect:
|
|
||||||
# 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
|
||||||
login-max-fail-times: 5
|
|
||||||
# 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
|
||||||
login-fail-locked-seconds: 1800
|
|
||||||
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启,false 不开启,建议开启
|
|
||||||
password-complexity-enabled: true
|
|
||||||
|
|
||||||
sa-token:
|
|
||||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
|
||||||
active-timeout: 1800
|
|
||||||
# token 有效期(单位:秒) 1天(86400秒),-1 代表永久有效
|
|
||||||
timeout: 86400
|
|
@ -8,7 +8,7 @@
|
|||||||
# 项目配置: 名称、日志目录
|
# 项目配置: 名称、日志目录
|
||||||
project:
|
project:
|
||||||
name: sa-admin
|
name: sa-admin
|
||||||
log-directory: /home/project/smartadmin/sit/log
|
log-directory: /home/project/smartadmin/test/log
|
||||||
|
|
||||||
# 项目端口和url根路径
|
# 项目端口和url根路径
|
||||||
server:
|
server:
|
||||||
@ -20,26 +20,3 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: '@profiles.active@'
|
active: '@profiles.active@'
|
||||||
|
|
||||||
####################################### 安全等级保护 相关配置 ##################################################
|
|
||||||
# #
|
|
||||||
# 建议开启 "三级等保" 所要求的配置,具体如下: #
|
|
||||||
# 1)连续登录失败 5 次锁定账户 30 分钟, #
|
|
||||||
# 2)登录超时时长建议为 30分钟,超过此时间没有访问系统会重新要求登录 #
|
|
||||||
# 3)密码复杂度至少三种字符,最小 8 位 #
|
|
||||||
# #
|
|
||||||
#############################################################################################################
|
|
||||||
|
|
||||||
classified-protect:
|
|
||||||
# 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
|
||||||
login-max-fail-times: 5
|
|
||||||
# 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
|
||||||
login-fail-locked-seconds: 1800
|
|
||||||
# 密码复杂度开启(默认复杂度为:至少三种字符,最小 8 位), true 开启,false 不开启,建议开启
|
|
||||||
password-complexity-enabled: true
|
|
||||||
|
|
||||||
sa-token:
|
|
||||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
|
||||||
active-timeout: 1800
|
|
||||||
# token 有效期(单位:秒) 1天(86400秒),-1 代表永久有效
|
|
||||||
timeout: 86400
|
|
@ -88,8 +88,8 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -263,6 +263,11 @@
|
|||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.1024lab</groupId>
|
<groupId>net.1024lab</groupId>
|
||||||
<artifactId>smartdb</artifactId>
|
<artifactId>smartdb</artifactId>
|
||||||
@ -278,6 +283,27 @@
|
|||||||
<artifactId>redisson-spring-data-27</artifactId>
|
<artifactId>redisson-spring-data-27</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.freemarker</groupId>
|
||||||
|
<artifactId>freemarker</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package net.lab1024.sa.base.common.json.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.BeanProperty;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||||
|
import net.lab1024.sa.base.module.support.datamasking.DataMasking;
|
||||||
|
import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.datamasking.SmartDataMaskingUtil;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏序列化
|
||||||
|
*
|
||||||
|
* @author 罗伊
|
||||||
|
* @description:
|
||||||
|
* @date 2024/7/21 4:39 下午
|
||||||
|
*/
|
||||||
|
public class DataMaskingSerializer extends JsonSerializer<Object> implements ContextualSerializer {
|
||||||
|
|
||||||
|
private DataMaskingTypeEnum typeEnum;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
|
||||||
|
|
||||||
|
if (ObjectUtils.isEmpty(value)) {
|
||||||
|
jsonGenerator.writeObject(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeEnum == null) {
|
||||||
|
jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(String.valueOf(value)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(value, typeEnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||||
|
// 判断beanProperty是不是空
|
||||||
|
if (null == property) {
|
||||||
|
return prov.findNullValueSerializer(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataMasking annotation = property.getAnnotation(DataMasking.class);
|
||||||
|
if (null == annotation) {
|
||||||
|
return prov.findValueSerializer(property.getType(), property);
|
||||||
|
}
|
||||||
|
|
||||||
|
typeEnum = annotation.value();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,7 +28,7 @@ import java.util.Optional;
|
|||||||
* springdoc-openapi 配置
|
* springdoc-openapi 配置
|
||||||
* nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
|
* nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
|
||||||
* location /v3/api-docs/ {
|
* location /v3/api-docs/ {
|
||||||
* proxy_pass http://127.0.0.1:11024/v3/api-docs/;
|
* proxy_pass http://127.0.0.1:1024/v3/api-docs/;
|
||||||
* }
|
* }
|
||||||
* @Author 1024创新实验室-主任: 卓大
|
* @Author 1024创新实验室-主任: 卓大
|
||||||
* @Date 2020-03-25 22:54:46
|
* @Date 2020-03-25 22:54:46
|
||||||
@ -43,7 +43,7 @@ public class SwaggerConfig {
|
|||||||
/**
|
/**
|
||||||
* 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
|
* 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
|
||||||
*/
|
*/
|
||||||
@Value("${springdoc.swagger-ui.server-base-url:''}")
|
@Value("${springdoc.swagger-ui.server-base-url}")
|
||||||
private String serverBaseUrl;
|
private String serverBaseUrl;
|
||||||
|
|
||||||
public static final String[] SWAGGER_WHITELIST = {
|
public static final String[] SWAGGER_WHITELIST = {
|
||||||
|
@ -23,5 +23,7 @@ public class RedisKeyConst {
|
|||||||
|
|
||||||
public static final String CAPTCHA = "captcha:";
|
public static final String CAPTCHA = "captcha:";
|
||||||
|
|
||||||
|
public static final String LOGIN_VERIFICATION_CODE = "login:verification-code:";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ public class SwaggerTagConst {
|
|||||||
|
|
||||||
public static final String PROTECT = "业务支撑-网络安全";
|
public static final String PROTECT = "业务支撑-网络安全";
|
||||||
|
|
||||||
|
public static final String DATA_MASKING = "业务支撑-数据脱敏";
|
||||||
|
|
||||||
public static final String JOB = "业务支撑-定时任务";
|
public static final String JOB = "业务支撑-定时任务";
|
||||||
|
|
||||||
public static final String MESSAGE = "业务支撑-消息";
|
public static final String MESSAGE = "业务支撑-消息";
|
||||||
|
@ -42,8 +42,10 @@ public class CaptchaService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private DefaultKaptcha defaultKaptcha;
|
private DefaultKaptcha defaultKaptcha;
|
||||||
@Autowired
|
|
||||||
|
@Resource
|
||||||
private SystemEnvironment systemEnvironment;
|
private SystemEnvironment systemEnvironment;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ public enum ConfigKeyEnum implements BaseEnum {
|
|||||||
*/
|
*/
|
||||||
SUPER_PASSWORD("super_password", "万能密码"),
|
SUPER_PASSWORD("super_password", "万能密码"),
|
||||||
|
|
||||||
|
LEVEL3_PROTECT_CONFIG("level3_protect_config", "三级等保配置"),
|
||||||
;
|
;
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
@ -109,7 +109,8 @@ public class ConfigService {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String getConfigValue(ConfigKeyEnum configKey) {
|
public String getConfigValue(ConfigKeyEnum configKey) {
|
||||||
return this.getConfig(configKey).getConfigValue();
|
ConfigVO config = this.getConfig(configKey);
|
||||||
|
return config == null ? null : config.getConfigValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,12 +126,12 @@ public class ConfigService {
|
|||||||
* 添加系统配置
|
* 添加系统配置
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public ResponseDTO<String> add(ConfigAddForm configAddDTO) {
|
public ResponseDTO<String> add(ConfigAddForm configAddForm) {
|
||||||
ConfigEntity entity = configDao.selectByKey(configAddDTO.getConfigKey());
|
ConfigEntity entity = configDao.selectByKey(configAddForm.getConfigKey());
|
||||||
if (null != entity) {
|
if (null != entity) {
|
||||||
return ResponseDTO.error(UserErrorCode.ALREADY_EXIST);
|
return ResponseDTO.error(UserErrorCode.ALREADY_EXIST);
|
||||||
}
|
}
|
||||||
entity = SmartBeanUtil.copy(configAddDTO, ConfigEntity.class);
|
entity = SmartBeanUtil.copy(configAddForm, ConfigEntity.class);
|
||||||
configDao.insert(entity);
|
configDao.insert(entity);
|
||||||
|
|
||||||
// 刷新缓存
|
// 刷新缓存
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.datamasking;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import net.lab1024.sa.base.common.json.serializer.DataMaskingSerializer;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏注解
|
||||||
|
*
|
||||||
|
* @author 罗伊
|
||||||
|
* @description:
|
||||||
|
* @date 2024/7/21 4:39 下午
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@JacksonAnnotationsInside
|
||||||
|
@JsonSerialize(using = DataMaskingSerializer.class, nullsUsing = DataMaskingSerializer.class)
|
||||||
|
public @interface DataMasking {
|
||||||
|
|
||||||
|
DataMaskingTypeEnum value() default DataMaskingTypeEnum.COMMON;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.datamasking;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏数据类型
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/1
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum DataMaskingTypeEnum {
|
||||||
|
|
||||||
|
COMMON(null, "通用"),
|
||||||
|
PHONE(DesensitizedUtil.DesensitizedType.MOBILE_PHONE, "手机号"),
|
||||||
|
CHINESE_NAME(DesensitizedUtil.DesensitizedType.CHINESE_NAME, "中文名"),
|
||||||
|
ID_CARD(DesensitizedUtil.DesensitizedType.ID_CARD, "身份证号"),
|
||||||
|
FIXED_PHONE(DesensitizedUtil.DesensitizedType.FIXED_PHONE, "座机号"),
|
||||||
|
ADDRESS(DesensitizedUtil.DesensitizedType.ADDRESS, "地址"),
|
||||||
|
EMAIL(DesensitizedUtil.DesensitizedType.EMAIL, "电子邮件"),
|
||||||
|
PASSWORD(DesensitizedUtil.DesensitizedType.PASSWORD, "密码"),
|
||||||
|
CAR_LICENSE(DesensitizedUtil.DesensitizedType.CAR_LICENSE, "中国大陆车牌"),
|
||||||
|
BANK_CARD(DesensitizedUtil.DesensitizedType.BANK_CARD, "银行卡"),
|
||||||
|
USER_ID(DesensitizedUtil.DesensitizedType.USER_ID, "用户id");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private DesensitizedUtil.DesensitizedType type;
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.datamasking;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.beans.IntrospectionException;
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏工具类
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-主任: 卓大
|
||||||
|
* @Date 2024-07-23 21:38:52
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||||
|
*/
|
||||||
|
public class SmartDataMaskingUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类 加注解字段缓存
|
||||||
|
*/
|
||||||
|
private static final ConcurrentHashMap<Class<?>, List<Field>> fieldMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static String dataMasking(String value) {
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length() < 4) {
|
||||||
|
return StrUtil.hide(value, 0, value.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
int valueLength = value.length();
|
||||||
|
int startHideIndex = getHideStartIndex(valueLength);
|
||||||
|
int endHideIndex = getHideEndIndex(valueLength);
|
||||||
|
return StrUtil.hide(value, startHideIndex, endHideIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object dataMasking(Object value, DataMaskingTypeEnum dataType) {
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataType == null) {
|
||||||
|
return dataMasking(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case PHONE:
|
||||||
|
return DesensitizedUtil.mobilePhone(String.valueOf(value));
|
||||||
|
case CHINESE_NAME:
|
||||||
|
return DesensitizedUtil.chineseName(String.valueOf(value));
|
||||||
|
case ID_CARD:
|
||||||
|
return DesensitizedUtil.idCardNum(String.valueOf(value), 6, 2);
|
||||||
|
case FIXED_PHONE:
|
||||||
|
return DesensitizedUtil.fixedPhone(String.valueOf(value));
|
||||||
|
case ADDRESS:
|
||||||
|
return StrUtil.hide(String.valueOf(value), 6, String.valueOf(value).length() - 1);
|
||||||
|
case EMAIL:
|
||||||
|
return DesensitizedUtil.email(String.valueOf(value));
|
||||||
|
case PASSWORD:
|
||||||
|
return DesensitizedUtil.password(String.valueOf(value));
|
||||||
|
case CAR_LICENSE:
|
||||||
|
return DesensitizedUtil.carLicense(String.valueOf(value));
|
||||||
|
case BANK_CARD:
|
||||||
|
return DesensitizedUtil.bankCard(String.valueOf(value));
|
||||||
|
case USER_ID:
|
||||||
|
return DesensitizedUtil.userId();
|
||||||
|
default:
|
||||||
|
return dataMasking(String.valueOf(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量脱敏
|
||||||
|
*/
|
||||||
|
public static <T> void dataMasking(Collection<T> objectList) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
|
||||||
|
if (CollectionUtils.isEmpty(objectList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (T object : objectList) {
|
||||||
|
dataMasking(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个脱敏
|
||||||
|
*/
|
||||||
|
public static <T> void dataMasking(T object) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
|
||||||
|
Class<?> tClass = object.getClass();
|
||||||
|
List<Field> fieldList = getField(object);
|
||||||
|
for (Field field : fieldList) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
String fieldValue = "";
|
||||||
|
try {
|
||||||
|
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass);
|
||||||
|
Method getMethod = pd.getReadMethod();
|
||||||
|
Object value = getMethod.invoke(object);
|
||||||
|
if (value != null) {
|
||||||
|
fieldValue = value.toString();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(fieldValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int valueLength = fieldValue.length();
|
||||||
|
int startHideIndex = getHideStartIndex(valueLength);
|
||||||
|
int endHideIndex = getHideEndIndex(valueLength);
|
||||||
|
try {
|
||||||
|
field.set(object, StrUtil.hide(fieldValue, startHideIndex, endHideIndex));
|
||||||
|
} catch (Exception e1) {
|
||||||
|
throw new RuntimeException(e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getHideStartIndex(int totalLength) {
|
||||||
|
if (totalLength <= 4) {
|
||||||
|
return 1;
|
||||||
|
} else if (totalLength <= 6) {
|
||||||
|
return 1;
|
||||||
|
} else if (totalLength <= 10) {
|
||||||
|
return 2;
|
||||||
|
} else if (totalLength <= 18) {
|
||||||
|
return 3;
|
||||||
|
} else if (totalLength <= 27) {
|
||||||
|
return 5;
|
||||||
|
} else if (totalLength <= 34) {
|
||||||
|
return 7;
|
||||||
|
} else if (totalLength <= 41) {
|
||||||
|
return 9;
|
||||||
|
} else {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getHideEndIndex(int totalLength) {
|
||||||
|
if (totalLength <= 4) {
|
||||||
|
return totalLength - 1;
|
||||||
|
} else if (totalLength <= 6) {
|
||||||
|
return totalLength - 2;
|
||||||
|
} else if (totalLength <= 10) {
|
||||||
|
return totalLength - 2;
|
||||||
|
} else if (totalLength <= 18) {
|
||||||
|
return totalLength - 4;
|
||||||
|
} else if (totalLength <= 27) {
|
||||||
|
return totalLength - 6;
|
||||||
|
} else if (totalLength <= 34) {
|
||||||
|
return totalLength - 8;
|
||||||
|
} else if (totalLength <= 41) {
|
||||||
|
return totalLength - 10;
|
||||||
|
} else {
|
||||||
|
return totalLength - 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<Field> getField(Object object) throws IntrospectionException {
|
||||||
|
// 从缓存中查询
|
||||||
|
Class<?> tClass = object.getClass();
|
||||||
|
List<Field> fieldList = fieldMap.get(tClass);
|
||||||
|
if (null != fieldList) {
|
||||||
|
return fieldList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这一段递归代码 是为了 从父类中获取属性
|
||||||
|
Class<?> tempClass = tClass;
|
||||||
|
fieldList = new ArrayList<>();
|
||||||
|
while (tempClass != null) {
|
||||||
|
Field[] declaredFields = tempClass.getDeclaredFields();
|
||||||
|
for (Field field : declaredFields) {
|
||||||
|
boolean stringField = false;
|
||||||
|
try {
|
||||||
|
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass);
|
||||||
|
Method getMethod = pd.getReadMethod();
|
||||||
|
Type returnType = getMethod.getGenericReturnType();
|
||||||
|
stringField = "java.lang.String".equals(returnType.getTypeName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (field.isAnnotationPresent(DataMasking.class) && stringField) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
fieldList.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempClass = tempClass.getSuperclass();
|
||||||
|
}
|
||||||
|
fieldMap.put(tClass, fieldList);
|
||||||
|
return fieldList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(dataMasking("a", null));
|
||||||
|
System.out.println(dataMasking("ab", null));
|
||||||
|
System.out.println(dataMasking("abc", null));
|
||||||
|
System.out.println(dataMasking("abcd", null));
|
||||||
|
System.out.println(dataMasking("abcde", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,9 +19,9 @@ import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO;
|
|||||||
import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO;
|
import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO;
|
||||||
import net.lab1024.sa.base.module.support.file.domain.vo.FileVO;
|
import net.lab1024.sa.base.module.support.file.domain.vo.FileVO;
|
||||||
import net.lab1024.sa.base.module.support.redis.RedisService;
|
import net.lab1024.sa.base.module.support.redis.RedisService;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityFileService;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@ -58,9 +58,8 @@ public class FileService {
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
@Value("${spring.servlet.multipart.max-file-size}")
|
@Resource
|
||||||
private String maxFileSize;
|
private SecurityFileService securityFileService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传服务
|
* 文件上传服务
|
||||||
@ -89,11 +88,10 @@ public class FileService {
|
|||||||
return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH);
|
return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验文件大小
|
// 校验文件大小以及安全性
|
||||||
String maxSizeStr = maxFileSize.toLowerCase().replace("mb", "");
|
ResponseDTO<String> validateFile = securityFileService.checkFile(file);
|
||||||
long maxSize = Integer.parseInt(maxSizeStr) * 1024 * 1024L;
|
if (!validateFile.getOk()) {
|
||||||
if (file.getSize() > maxSize) {
|
return ResponseDTO.error(validateFile);
|
||||||
return ResponseDTO.userErrorParam("上传文件最大为:" + maxSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进行上传
|
// 进行上传
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.mail;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import freemarker.cache.StringTemplateLoader;
|
||||||
|
import freemarker.template.Configuration;
|
||||||
|
import freemarker.template.Template;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
|
import net.lab1024.sa.base.common.domain.SystemEnvironment;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.constant.MailTemplateTypeEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.text.StringSubstitutor;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 发生邮件:<br/>
|
||||||
|
* 1、支持直接发送 <br/>
|
||||||
|
* 2、支持使用邮件模板发送
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/5
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class MailService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JavaMailSender javaMailSender;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MailTemplateDao mailTemplateDao;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SystemEnvironment systemEnvironment;
|
||||||
|
|
||||||
|
@Value("${spring.mail.username}")
|
||||||
|
private String clientMail;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用模板发送邮件
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> sendMail(MailTemplateCodeEnum templateCode, Map<String, Object> templateParamsMap, List<String> receiverUserList, List<File> fileList) {
|
||||||
|
|
||||||
|
MailTemplateEntity mailTemplateEntity = mailTemplateDao.selectById(templateCode.name().toLowerCase());
|
||||||
|
if (mailTemplateEntity == null) {
|
||||||
|
return ResponseDTO.userErrorParam("模版不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mailTemplateEntity.getDisableFlag()) {
|
||||||
|
return ResponseDTO.userErrorParam("模版已禁用,无法发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = null;
|
||||||
|
if (MailTemplateTypeEnum.FREEMARKER.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) {
|
||||||
|
content = freemarkerResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap);
|
||||||
|
} else if (MailTemplateTypeEnum.STRING.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) {
|
||||||
|
content = stringResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap);
|
||||||
|
} else {
|
||||||
|
return ResponseDTO.userErrorParam("模版类型不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
this.sendMail(mailTemplateEntity.getTemplateSubject(), content, fileList, receiverUserList, true);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("邮件发送失败", e);
|
||||||
|
return ResponseDTO.userErrorParam("邮件发送失败");
|
||||||
|
}
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用模板发送邮件
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> sendMail(MailTemplateCodeEnum templateCode, Map<String, Object> templateParamsMap, List<String> receiverUserList) {
|
||||||
|
return this.sendMail(templateCode, templateParamsMap, receiverUserList, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件
|
||||||
|
*
|
||||||
|
* @param subject 主题
|
||||||
|
* @param content 内容
|
||||||
|
* @param fileList 文件
|
||||||
|
* @param receiverUserList 接收方
|
||||||
|
* @throws MessagingException
|
||||||
|
*/
|
||||||
|
public void sendMail(String subject, String content, List<File> fileList, List<String> receiverUserList, boolean isHtml) throws MessagingException {
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(receiverUserList)) {
|
||||||
|
throw new RuntimeException("接收方不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(content)) {
|
||||||
|
throw new RuntimeException("邮件内容不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!systemEnvironment.isProd()) {
|
||||||
|
subject = "(测试)" + subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||||
|
|
||||||
|
//是否为多文件上传
|
||||||
|
boolean multiparty = !CollectionUtils.isEmpty(fileList);
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, multiparty);
|
||||||
|
helper.setFrom(clientMail);
|
||||||
|
helper.setTo(receiverUserList.toArray(new String[0]));
|
||||||
|
helper.setSubject(subject);
|
||||||
|
//发送html格式
|
||||||
|
helper.setText(content, isHtml);
|
||||||
|
|
||||||
|
//附件
|
||||||
|
if (multiparty) {
|
||||||
|
for (File file : fileList) {
|
||||||
|
helper.addAttachment(file.getName(), file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
javaMailSender.send(mimeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用字符串生成最终内容
|
||||||
|
*/
|
||||||
|
private String stringResolverContent(String stringTemplate, Map<String, Object> templateParamsMap) {
|
||||||
|
StringSubstitutor stringSubstitutor = new StringSubstitutor(templateParamsMap);
|
||||||
|
String contractHtml = stringSubstitutor.replace(stringTemplate);
|
||||||
|
Document doc = Jsoup.parse(contractHtml);
|
||||||
|
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
|
||||||
|
return doc.outerHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 freemarker 生成最终内容
|
||||||
|
*/
|
||||||
|
private String freemarkerResolverContent(String htmlTemplate, Map<String, Object> templateParamsMap) {
|
||||||
|
Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
|
||||||
|
StringTemplateLoader stringLoader = new StringTemplateLoader();
|
||||||
|
String templateName = UUID.fastUUID().toString(true);
|
||||||
|
stringLoader.putTemplate(templateName, htmlTemplate);
|
||||||
|
configuration.setTemplateLoader(stringLoader);
|
||||||
|
try {
|
||||||
|
Template template = configuration.getTemplate(templateName, "utf-8");
|
||||||
|
Writer out = new StringWriter(2048);
|
||||||
|
template.process(templateParamsMap, out);
|
||||||
|
return out.toString();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("freemarkerResolverContent error: ", e);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.mail;
|
||||||
|
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件模板
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/5
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
@Component
|
||||||
|
public interface MailTemplateDao extends BaseMapper<MailTemplateEntity> {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.mail.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模版编码
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/5
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
public enum MailTemplateCodeEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录验证码
|
||||||
|
*/
|
||||||
|
LOGIN_VERIFICATION_CODE
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.mail.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.lab1024.sa.base.common.enumeration.BaseEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件模板类型
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/5
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum MailTemplateTypeEnum implements BaseEnum {
|
||||||
|
|
||||||
|
STRING("string", "字符串替代器"),
|
||||||
|
|
||||||
|
FREEMARKER("freemarker", "freemarker模板引擎");
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.mail.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 邮件模板
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/8/5
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("t_mail_template")
|
||||||
|
public class MailTemplateEntity {
|
||||||
|
|
||||||
|
@TableId(type = IdType.NONE)
|
||||||
|
private String templateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题
|
||||||
|
*/
|
||||||
|
private String templateSubject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板类型
|
||||||
|
*/
|
||||||
|
private String templateType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板内容
|
||||||
|
*/
|
||||||
|
private String templateContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用标识
|
||||||
|
*/
|
||||||
|
private Boolean disableFlag;
|
||||||
|
|
||||||
|
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
@ -22,6 +22,12 @@ public class OperateLogQueryForm extends PageParam {
|
|||||||
@Schema(description = "用户类型")
|
@Schema(description = "用户类型")
|
||||||
private Integer operateUserType;
|
private Integer operateUserType;
|
||||||
|
|
||||||
|
@Schema(description = "关键字:模块、操作内容")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "请求关键字:请求地址、请求方法、请求参数")
|
||||||
|
private String requestKeywords;
|
||||||
|
|
||||||
@Schema(description = "开始日期")
|
@Schema(description = "开始日期")
|
||||||
private String startDate;
|
private String startDate;
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
@Component
|
||||||
|
public interface PasswordLogDao extends BaseMapper<PasswordLogEntity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询最后一次修改密码记录
|
||||||
|
*
|
||||||
|
* @param userType
|
||||||
|
* @param userId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
PasswordLogEntity selectLastByUserTypeAndUserId(@Param("userType") Integer userType, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询最近几次修改后的密码
|
||||||
|
*
|
||||||
|
* @param userType
|
||||||
|
* @param userId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<String> selectOldPassword(@Param("userType") Integer userType, @Param("userId") Long userId, @Param("limit") int limit);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.domain;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三级等保相关配置
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/7/30
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Level3ProtectConfigForm {
|
||||||
|
|
||||||
|
@Schema(description = "连续登录失败次数则锁定")
|
||||||
|
@NotNull(message = "连续登录失败次数则锁定 不能为空")
|
||||||
|
private Integer loginFailMaxTimes;
|
||||||
|
|
||||||
|
@Schema(description = "连续登录失败锁定时间(单位:分钟)")
|
||||||
|
@NotNull(message = "连续登录失败锁定时间(单位:分钟) 不能为空")
|
||||||
|
private Integer loginFailLockMinutes;
|
||||||
|
|
||||||
|
@Schema(description = "最低活跃时间(单位:分钟)")
|
||||||
|
@NotNull(message = "最低活跃时间(单位:分钟) 不能为空")
|
||||||
|
private Integer loginActiveTimeoutMinutes;
|
||||||
|
|
||||||
|
@Schema(description = "开启双因子登录")
|
||||||
|
@NotNull(message = "开启双因子登录 不能为空")
|
||||||
|
private Boolean twoFactorLoginEnabled;
|
||||||
|
|
||||||
|
@Schema(description = "密码复杂度 是否开启,默认:开启")
|
||||||
|
@NotNull(message = "密码复杂度 是否开启 不能为空")
|
||||||
|
private Boolean passwordComplexityEnabled;
|
||||||
|
|
||||||
|
@Schema(description = "定期修改密码时间间隔(默认:月)")
|
||||||
|
@NotNull(message = "定期修改密码时间间隔(默认:月) 不能为空")
|
||||||
|
private Integer regularChangePasswordMonths;
|
||||||
|
|
||||||
|
@Schema(description = "定期修改密码不允许重复次数,默认:3次以内密码不能相同(默认:次)")
|
||||||
|
@NotNull(message = "定期修改密码不允许重复次数 不能为空")
|
||||||
|
private Integer regularChangePasswordNotAllowRepeatTimes;
|
||||||
|
|
||||||
|
@Schema(description = "文件检测,默认:不开启")
|
||||||
|
@NotNull(message = "文件检测 是否开启 不能为空")
|
||||||
|
private Boolean fileDetectFlag;
|
||||||
|
|
||||||
|
@Schema(description = "文件大小限制,单位 mb ,(默认:50 mb)")
|
||||||
|
@NotNull(message = "文件大小限制 不能为空")
|
||||||
|
private Long maxUploadFileSizeMb;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yandy
|
||||||
|
* @description:
|
||||||
|
* @date 2024/7/15 1:39 下午
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("t_password_log")
|
||||||
|
public class PasswordLogEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键id
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Integer userType;
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.service;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
|
import net.lab1024.sa.base.module.support.config.ConfigKeyEnum;
|
||||||
|
import net.lab1024.sa.base.module.support.config.ConfigService;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三级等保配置
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-创始人兼主任:卓大
|
||||||
|
* @Date 2024/7/30
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> ,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class Level3ProtectConfigService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启双因子登录,默认:开启
|
||||||
|
*/
|
||||||
|
private boolean twoFactorLoginEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
||||||
|
*/
|
||||||
|
private int loginFailMaxTimes = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
||||||
|
*/
|
||||||
|
private int loginFailLockSeconds = 1800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟
|
||||||
|
*/
|
||||||
|
private int loginActiveTimeoutSeconds = 1800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码复杂度 是否开启,默认:开启
|
||||||
|
*/
|
||||||
|
private boolean passwordComplexityEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期修改密码时间间隔(默认:天),默认:建议90天更换密码
|
||||||
|
*/
|
||||||
|
private int regularChangePasswordDays = 90;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期修改密码不允许相同次数,默认:3次以内密码不能相同
|
||||||
|
*/
|
||||||
|
private int regularChangePasswordNotAllowRepeatTimes = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小限制,单位 mb ,(默认:50 mb)
|
||||||
|
*/
|
||||||
|
private long maxUploadFileSizeMb = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件检测,默认:不开启
|
||||||
|
*/
|
||||||
|
private boolean fileDetectFlag = false;
|
||||||
|
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ConfigService configService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件检测,默认:不开启
|
||||||
|
*/
|
||||||
|
public boolean isFileDetectFlag() {
|
||||||
|
return fileDetectFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小限制,单位 mb ,(默认:50 mb)
|
||||||
|
*/
|
||||||
|
public long getMaxUploadFileSizeMb() {
|
||||||
|
return maxUploadFileSizeMb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录
|
||||||
|
*/
|
||||||
|
public int getLoginFailMaxTimes() {
|
||||||
|
return loginFailMaxTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟
|
||||||
|
*/
|
||||||
|
public int getLoginFailLockSeconds() {
|
||||||
|
return loginFailLockSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟
|
||||||
|
*/
|
||||||
|
public int getLoginActiveTimeoutSeconds() {
|
||||||
|
return loginActiveTimeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期修改密码时间间隔(默认:天),默认:建议90天更换密码
|
||||||
|
*/
|
||||||
|
public int getRegularChangePasswordDays() {
|
||||||
|
return regularChangePasswordDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启双因子登录,默认:开启
|
||||||
|
*/
|
||||||
|
public boolean isTwoFactorLoginEnabled() {
|
||||||
|
return twoFactorLoginEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码复杂度 是否开启,默认:开启
|
||||||
|
*/
|
||||||
|
public boolean isPasswordComplexityEnabled() {
|
||||||
|
return passwordComplexityEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期修改密码不允许相同次数,默认:3次以内密码不能相同
|
||||||
|
*/
|
||||||
|
public int getRegularChangePasswordNotAllowRepeatTimes() {
|
||||||
|
return regularChangePasswordNotAllowRepeatTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void init() {
|
||||||
|
String configValue = configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG);
|
||||||
|
if (StrUtil.isEmpty(configValue)) {
|
||||||
|
throw new ExceptionInInitializerError("t_config 表 三级等保配置为空,请进行配置!");
|
||||||
|
}
|
||||||
|
Level3ProtectConfigForm level3ProtectConfigForm = JSON.parseObject(configValue, Level3ProtectConfigForm.class);
|
||||||
|
setProp(level3ProtectConfigForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置属性
|
||||||
|
*/
|
||||||
|
private void setProp(Level3ProtectConfigForm configForm) {
|
||||||
|
|
||||||
|
if (configForm.getFileDetectFlag() != null) {
|
||||||
|
this.fileDetectFlag = configForm.getFileDetectFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getMaxUploadFileSizeMb() != null) {
|
||||||
|
this.maxUploadFileSizeMb = configForm.getMaxUploadFileSizeMb();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getLoginFailLockMinutes() != null) {
|
||||||
|
this.loginFailLockSeconds = configForm.getLoginFailLockMinutes() * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getLoginActiveTimeoutMinutes() != null) {
|
||||||
|
this.loginActiveTimeoutSeconds = configForm.getLoginActiveTimeoutMinutes() * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getPasswordComplexityEnabled() != null) {
|
||||||
|
this.passwordComplexityEnabled = configForm.getPasswordComplexityEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getRegularChangePasswordMonths() != null) {
|
||||||
|
this.regularChangePasswordDays = configForm.getRegularChangePasswordMonths() * 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getTwoFactorLoginEnabled() != null) {
|
||||||
|
this.twoFactorLoginEnabled = configForm.getTwoFactorLoginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configForm.getRegularChangePasswordNotAllowRepeatTimes() != null) {
|
||||||
|
this.regularChangePasswordNotAllowRepeatTimes = configForm.getRegularChangePasswordNotAllowRepeatTimes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 最低活跃时间(单位:秒)
|
||||||
|
if (this.loginActiveTimeoutSeconds > 0) {
|
||||||
|
StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(getLoginActiveTimeoutSeconds());
|
||||||
|
} else {
|
||||||
|
StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新三级等保配置
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> updateLevel3Config(Level3ProtectConfigForm configForm) {
|
||||||
|
// 设置属性
|
||||||
|
setProp(configForm);
|
||||||
|
// 保存数据库
|
||||||
|
String configFormJsonString = JSON.toJSONString(configForm, true);
|
||||||
|
return configService.updateValueByKey(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG, configFormJsonString);
|
||||||
|
}
|
||||||
|
}
|
@ -1,99 +0,0 @@
|
|||||||
package net.lab1024.sa.base.module.support.securityprotect.service;
|
|
||||||
|
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
|
||||||
import net.lab1024.sa.base.common.util.SmartStringUtil;
|
|
||||||
import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService;
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 三级等保 密码 相关
|
|
||||||
*
|
|
||||||
* @Author 1024创新实验室-主任:卓大
|
|
||||||
* @Date 2023/10/11 19:25:59
|
|
||||||
* @Wechat zhuoda1024
|
|
||||||
* @Email lab1024@163.com
|
|
||||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ProtectPasswordService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码长度8-20位且包含大写字母、小写字母、数字三种
|
|
||||||
*/
|
|
||||||
public static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,20}$";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码长度8-20位且包含大写字母、小写字母、数字三种
|
|
||||||
*/
|
|
||||||
public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且包含大写字母、小写字母、数字三种";
|
|
||||||
|
|
||||||
|
|
||||||
private static final int PASSWORD_LENGTH = 8;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码复杂度开启, 默认为true 开启,false 不开启
|
|
||||||
*/
|
|
||||||
@Value("${classified-protect.password-complexity-enabled}")
|
|
||||||
private Boolean passwordComplexityEnabled;
|
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ApiEncryptService apiEncryptService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验密码复杂度
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public ResponseDTO<String> validatePassComplexity(String password) {
|
|
||||||
|
|
||||||
// 无需校验
|
|
||||||
if (!passwordComplexityEnabled) {
|
|
||||||
return ResponseDTO.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SmartStringUtil.isEmpty(password)) {
|
|
||||||
return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password.matches(PASSWORD_PATTERN)) {
|
|
||||||
return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseDTO.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 随机生成密码
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String randomPassword() {
|
|
||||||
// 未开启密码复杂度,则由8为数字构成
|
|
||||||
if (passwordComplexityEnabled) {
|
|
||||||
return RandomStringUtils.randomNumeric(PASSWORD_LENGTH);
|
|
||||||
} else {
|
|
||||||
// 3位大写字母,2位数字,3位小写字母
|
|
||||||
return RandomStringUtils.randomAlphabetic(3).toUpperCase() + RandomStringUtils.randomNumeric(2) + RandomStringUtils.randomAlphabetic(3).toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解密 SM4 or AES 加密过的密码
|
|
||||||
*
|
|
||||||
* @param encryptedPassword
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String decryptPassword(String encryptedPassword) {
|
|
||||||
return apiEncryptService.decrypt(encryptedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.service;
|
||||||
|
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三级等保 文件上传 相关
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-主任:卓大
|
||||||
|
* @Date 2024/08/22 19:25:59
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SecurityFileService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测文件安全类型
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> checkFile(MultipartFile file) {
|
||||||
|
|
||||||
|
// 检验文件大小
|
||||||
|
if (level3ProtectConfigService.getMaxUploadFileSizeMb() > 0) {
|
||||||
|
long maxSize = level3ProtectConfigService.getMaxUploadFileSizeMb() * 1024 * 1024;
|
||||||
|
if (file.getSize() > maxSize) {
|
||||||
|
return ResponseDTO.userErrorParam("上传文件最大为:" + level3ProtectConfigService.getMaxUploadFileSizeMb() + " mb");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件类型安全检测
|
||||||
|
if (!level3ProtectConfigService.isFileDetectFlag()) {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测文件类型
|
||||||
|
// .....
|
||||||
|
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
;
|
@ -12,7 +12,6 @@ import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity
|
|||||||
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm;
|
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm;
|
||||||
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO;
|
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -30,23 +29,14 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ProtectLoginService {
|
public class SecurityLoginService {
|
||||||
|
|
||||||
private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!";
|
private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!";
|
||||||
|
|
||||||
private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!";
|
private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!";
|
||||||
|
|
||||||
/**
|
@Resource
|
||||||
* 连续登录失败次数则锁定,-1表示不受限制,可以一直登录
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
*/
|
|
||||||
@Value("${classified-protect.login-max-fail-times}")
|
|
||||||
private Integer loginMaxFailTimes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连续登录失败锁定时间(单位:秒),-1表示不锁定
|
|
||||||
*/
|
|
||||||
@Value("${classified-protect.login-fail-locked-seconds}")
|
|
||||||
private Integer loginFailLockedSeconds;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private LoginFailDao loginFailDao;
|
private LoginFailDao loginFailDao;
|
||||||
@ -61,8 +51,8 @@ public class ProtectLoginService {
|
|||||||
*/
|
*/
|
||||||
public ResponseDTO<LoginFailEntity> checkLogin(Long userId, UserTypeEnum userType) {
|
public ResponseDTO<LoginFailEntity> checkLogin(Long userId, UserTypeEnum userType) {
|
||||||
|
|
||||||
// 无需校验
|
// 若登录最大失败次数小于1,无需校验
|
||||||
if (loginMaxFailTimes < 1) {
|
if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
|
||||||
return ResponseDTO.ok();
|
return ResponseDTO.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,19 +62,24 @@ public class ProtectLoginService {
|
|||||||
return ResponseDTO.ok();
|
return ResponseDTO.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验次数
|
// 校验登录失败次数
|
||||||
if (loginFailEntity.getLoginFailCount() < loginMaxFailTimes) {
|
if (loginFailEntity.getLoginFailCount() < level3ProtectConfigService.getLoginFailMaxTimes()) {
|
||||||
|
return ResponseDTO.ok(loginFailEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验是否锁定
|
||||||
|
if (loginFailEntity.getLoginLockBeginTime() == null) {
|
||||||
return ResponseDTO.ok(loginFailEntity);
|
return ResponseDTO.ok(loginFailEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验锁定时长
|
// 校验锁定时长
|
||||||
if(loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds).isBefore(LocalDateTime.now())){
|
if (loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()).isBefore(LocalDateTime.now())) {
|
||||||
// 过了锁定时间
|
// 过了锁定时间
|
||||||
return ResponseDTO.ok(loginFailEntity);
|
return ResponseDTO.ok(loginFailEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds);
|
LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds());
|
||||||
return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime)));
|
return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,43 +91,40 @@ public class ProtectLoginService {
|
|||||||
*/
|
*/
|
||||||
public String recordLoginFail(Long userId, UserTypeEnum userType, String loginName, LoginFailEntity loginFailEntity) {
|
public String recordLoginFail(Long userId, UserTypeEnum userType, String loginName, LoginFailEntity loginFailEntity) {
|
||||||
|
|
||||||
// 无需校验
|
// 若登录最大失败次数小于1,无需记录
|
||||||
if (loginMaxFailTimes < 1) {
|
if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 登录失败
|
||||||
|
int loginFailCount = loginFailEntity == null ? 1 : loginFailEntity.getLoginFailCount() + 1;
|
||||||
|
boolean lockFlag = loginFailCount >= level3ProtectConfigService.getLoginFailMaxTimes();
|
||||||
|
LocalDateTime lockBeginTime = lockFlag ? LocalDateTime.now() : null;
|
||||||
|
|
||||||
if (loginFailEntity == null) {
|
if (loginFailEntity == null) {
|
||||||
loginFailEntity = LoginFailEntity.builder()
|
loginFailEntity = LoginFailEntity.builder()
|
||||||
.userId(userId)
|
.userId(userId)
|
||||||
.userType(userType.getValue())
|
.userType(userType.getValue())
|
||||||
.loginName(loginName)
|
.loginName(loginName)
|
||||||
.loginFailCount(1)
|
.loginFailCount(loginFailCount)
|
||||||
.lockFlag(false)
|
.lockFlag(lockFlag)
|
||||||
.loginLockBeginTime(null).build();
|
.loginLockBeginTime(lockBeginTime)
|
||||||
|
.build();
|
||||||
loginFailDao.insert(loginFailEntity);
|
loginFailDao.insert(loginFailEntity);
|
||||||
} else {
|
} else {
|
||||||
|
loginFailEntity.setLoginLockBeginTime(lockBeginTime);
|
||||||
// 如果是已经锁定状态,则重新计算
|
loginFailEntity.setLoginFailCount(loginFailCount);
|
||||||
if(loginFailEntity.getLockFlag()){
|
loginFailEntity.setLockFlag(lockFlag);
|
||||||
loginFailEntity.setLockFlag(false);
|
|
||||||
loginFailEntity.setLoginFailCount(1);
|
|
||||||
loginFailEntity.setLoginLockBeginTime(null);
|
|
||||||
}else{
|
|
||||||
loginFailEntity.setLoginLockBeginTime(LocalDateTime.now());
|
|
||||||
loginFailEntity.setLoginFailCount(loginFailEntity.getLoginFailCount() + 1);
|
|
||||||
loginFailEntity.setLockFlag(loginFailEntity.getLoginFailCount() >= loginMaxFailTimes);
|
|
||||||
}
|
|
||||||
|
|
||||||
loginFailEntity.setLoginName(loginName);
|
loginFailEntity.setLoginName(loginName);
|
||||||
loginFailDao.updateById(loginFailEntity);
|
loginFailDao.updateById(loginFailEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提示信息
|
// 提示信息
|
||||||
if (loginFailEntity.getLoginFailCount() >= loginMaxFailTimes) {
|
if (lockFlag) {
|
||||||
LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds);
|
LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds());
|
||||||
return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime));
|
return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime));
|
||||||
} else {
|
} else {
|
||||||
return String.format(LOGIN_FAIL_MSG, loginMaxFailTimes, loginFailLockedSeconds / 60, loginMaxFailTimes - loginFailEntity.getLoginFailCount());
|
return String.format(LOGIN_FAIL_MSG, level3ProtectConfigService.getLoginFailMaxTimes(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, level3ProtectConfigService.getLoginFailMaxTimes() - loginFailEntity.getLoginFailCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,8 +135,9 @@ public class ProtectLoginService {
|
|||||||
* @param userType
|
* @param userType
|
||||||
*/
|
*/
|
||||||
public void removeLoginFail(Long userId, UserTypeEnum userType) {
|
public void removeLoginFail(Long userId, UserTypeEnum userType) {
|
||||||
// 无需校验
|
|
||||||
if (loginMaxFailTimes < 1) {
|
// 若登录最大失败次数小于1,无需校验
|
||||||
|
if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +153,7 @@ public class ProtectLoginService {
|
|||||||
public PageResult<LoginFailVO> queryPage(LoginFailQueryForm queryForm) {
|
public PageResult<LoginFailVO> queryPage(LoginFailQueryForm queryForm) {
|
||||||
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
|
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
|
||||||
List<LoginFailVO> list = loginFailDao.queryPage(page, queryForm);
|
List<LoginFailVO> list = loginFailDao.queryPage(page, queryForm);
|
||||||
PageResult<LoginFailVO> pageResult = SmartPageUtil.convert2PageResult(page, list);
|
return SmartPageUtil.convert2PageResult(page, list);
|
||||||
return pageResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -0,0 +1,149 @@
|
|||||||
|
package net.lab1024.sa.base.module.support.securityprotect.service;
|
||||||
|
|
||||||
|
import net.lab1024.sa.base.common.domain.RequestUser;
|
||||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
|
import net.lab1024.sa.base.common.util.SmartStringUtil;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao;
|
||||||
|
import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三级等保 密码 相关
|
||||||
|
*
|
||||||
|
* @Author 1024创新实验室-主任:卓大
|
||||||
|
* @Date 2023/10/11 19:25:59
|
||||||
|
* @Wechat zhuoda1024
|
||||||
|
* @Email lab1024@163.com
|
||||||
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SecurityPasswordService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码长度8-20位且包含大小写字母、数字、特殊符号三种及以上组合
|
||||||
|
*/
|
||||||
|
public static final String PASSWORD_PATTERN = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\\W_!@#$%^&*`~()-+=]+$)(?![0-9\\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\\W_!@#$%^&*`~()-+=]*$";
|
||||||
|
|
||||||
|
public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且必须包含大小写字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符";
|
||||||
|
|
||||||
|
|
||||||
|
private static final int PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
|
private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
|
||||||
|
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PasswordLogDao passwordLogDao;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Level3ProtectConfigService level3ProtectConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验密码复杂度
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> validatePasswordComplexity(String password) {
|
||||||
|
|
||||||
|
if (SmartStringUtil.isEmpty(password)) {
|
||||||
|
return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密码长度必须大于等于8位
|
||||||
|
if (password.length() < PASSWORD_LENGTH) {
|
||||||
|
return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无需校验 密码复杂度
|
||||||
|
if (!level3ProtectConfigService.isPasswordComplexityEnabled()) {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.matches(PASSWORD_PATTERN)) {
|
||||||
|
return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验密码重复次数
|
||||||
|
*/
|
||||||
|
public ResponseDTO<String> validatePasswordRepeatTimes(RequestUser requestUser, String newPassword) {
|
||||||
|
|
||||||
|
// 密码重复次数小于1 无需校验
|
||||||
|
if (level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes() < 1) {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查最近几次是否有重复密码
|
||||||
|
List<String> oldPasswords = passwordLogDao.selectOldPassword(requestUser.getUserType().getValue(), requestUser.getUserId(), level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes());
|
||||||
|
if (oldPasswords != null && oldPasswords.contains(getEncryptPwd(newPassword))) {
|
||||||
|
return ResponseDTO.userErrorParam(String.format("与前%s个历史密码重复,请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成密码
|
||||||
|
*/
|
||||||
|
public String randomPassword() {
|
||||||
|
// 未开启密码复杂度,则由8为数字构成
|
||||||
|
if (!level3ProtectConfigService.isPasswordComplexityEnabled()) {
|
||||||
|
return RandomStringUtils.randomNumeric(PASSWORD_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3位大写字母,2位数字,2位小写字母 + 1位特殊符号
|
||||||
|
return RandomStringUtils.randomAlphabetic(3).toUpperCase()
|
||||||
|
+ RandomStringUtils.randomNumeric(2)
|
||||||
|
+ RandomStringUtils.randomAlphabetic(2).toLowerCase()
|
||||||
|
+ (ThreadLocalRandom.current().nextBoolean() ? "#" : "@");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存修改密码
|
||||||
|
*/
|
||||||
|
public void saveUserChangePasswordLog(RequestUser requestUser, String newPassword, String oldPassword) {
|
||||||
|
|
||||||
|
PasswordLogEntity passwordLogEntity = new PasswordLogEntity();
|
||||||
|
passwordLogEntity.setNewPassword(newPassword);
|
||||||
|
passwordLogEntity.setOldPassword(oldPassword);
|
||||||
|
passwordLogEntity.setUserId(requestUser.getUserId());
|
||||||
|
passwordLogEntity.setUserType(requestUser.getUserType().getValue());
|
||||||
|
passwordLogDao.insert(passwordLogEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否需要修改密码
|
||||||
|
*/
|
||||||
|
public boolean checkNeedChangePassword(Integer userType, Long userId) {
|
||||||
|
|
||||||
|
if (level3ProtectConfigService.getRegularChangePasswordDays() < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordLogEntity passwordLogEntity = passwordLogDao.selectLastByUserTypeAndUserId(userType, userId);
|
||||||
|
if (passwordLogEntity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime nextUpdateTime = passwordLogEntity.getCreateTime().plusDays(level3ProtectConfigService.getRegularChangePasswordDays());
|
||||||
|
return nextUpdateTime.isBefore(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 加密后 的密码
|
||||||
|
*/
|
||||||
|
public static String getEncryptPwd(String password) {
|
||||||
|
return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
spring:
|
spring:
|
||||||
# 数据库连接信息
|
# 数据库连接信息
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: Zhuoda1024lab
|
password: 11024Lab
|
||||||
initial-size: 2
|
initial-size: 2
|
||||||
min-idle: 2
|
min-idle: 2
|
||||||
max-active: 10
|
max-active: 10
|
||||||
@ -22,10 +22,10 @@ spring:
|
|||||||
|
|
||||||
# redis 连接池配置信息
|
# redis 连接池配置信息
|
||||||
redis:
|
redis:
|
||||||
database: 1
|
database: 7
|
||||||
host: 127.0.0.1
|
host: 47.96.105.74
|
||||||
port: 6379
|
port: 6666
|
||||||
password:
|
password: ASDasd123
|
||||||
timeout: 10000ms
|
timeout: 10000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
@ -34,11 +34,22 @@ spring:
|
|||||||
max-idle: 3
|
max-idle: 3
|
||||||
max-wait: 30000ms
|
max-wait: 30000ms
|
||||||
|
|
||||||
# 上传文件大小配置
|
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||||
servlet:
|
mail:
|
||||||
multipart:
|
host: smtp.163.com
|
||||||
max-file-size: 30MB
|
port: 465
|
||||||
max-request-size: 30MB
|
username: lab1024@163.com
|
||||||
|
password: ROIMSIQCEXHTQFTA
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
ssl:
|
||||||
|
enable: true
|
||||||
|
socketFactory:
|
||||||
|
class: com.sun.mail.util.MailSSLSocketFactory
|
||||||
|
fallback: false
|
||||||
|
debug: false
|
||||||
|
|
||||||
# json序列化相关配置
|
# json序列化相关配置
|
||||||
jackson:
|
jackson:
|
||||||
@ -76,8 +87,8 @@ file:
|
|||||||
region: oss-cn-hangzhou
|
region: oss-cn-hangzhou
|
||||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||||
bucket-name: 1024lab-smart-admin
|
bucket-name: 1024lab-smart-admin
|
||||||
access-key:
|
access-key: LTAI5tBAbehjXWyAqLhc58e1
|
||||||
secret-key:
|
secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
|
||||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||||
private-url-expire-seconds: 3600
|
private-url-expire-seconds: 3600
|
||||||
|
|
||||||
@ -87,6 +98,7 @@ springdoc:
|
|||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
doc-expansion: none #关闭展开
|
doc-expansion: none #关闭展开
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
|
server-base-url:
|
||||||
api-docs:
|
api-docs:
|
||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
knife4j:
|
knife4j:
|
||||||
|
@ -22,6 +22,12 @@
|
|||||||
<if test="query.userName != null and query.userName != ''">
|
<if test="query.userName != null and query.userName != ''">
|
||||||
AND INSTR(operate_user_name,#{query.userName})
|
AND INSTR(operate_user_name,#{query.userName})
|
||||||
</if>
|
</if>
|
||||||
|
<if test="query.keywords != null and query.keywords != ''">
|
||||||
|
AND (INSTR(`module`,#{query.keywords}) OR INSTR(content,#{query.keywords}))
|
||||||
|
</if>
|
||||||
|
<if test="query.requestKeywords != null and query.requestKeywords != ''">
|
||||||
|
AND (INSTR(`url`,#{query.requestKeywords}) OR INSTR(`method`,#{query.requestKeywords}) OR INSTR(`param`,#{query.requestKeywords}))
|
||||||
|
</if>
|
||||||
<if test="query.successFlag != null">
|
<if test="query.successFlag != null">
|
||||||
AND success_flag = #{query.successFlag}
|
AND success_flag = #{query.successFlag}
|
||||||
</if>
|
</if>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<?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="net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao">
|
||||||
|
|
||||||
|
<select id="selectLastByUserTypeAndUserId"
|
||||||
|
resultType="net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity">
|
||||||
|
select
|
||||||
|
*
|
||||||
|
from t_password_log
|
||||||
|
where
|
||||||
|
user_id = #{userId}
|
||||||
|
and user_type = #{userType}
|
||||||
|
order by id desc
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectOldPassword" resultType="java.lang.String">
|
||||||
|
select
|
||||||
|
new_password
|
||||||
|
from t_password_log
|
||||||
|
where
|
||||||
|
user_id = #{userId}
|
||||||
|
and user_type = #{userType}
|
||||||
|
order by id desc
|
||||||
|
limit #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
@ -1,9 +1,9 @@
|
|||||||
spring:
|
spring:
|
||||||
# 数据库连接信息
|
# 数据库连接信息
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: Zhuoda1024lab
|
password: 11024Lab
|
||||||
initial-size: 2
|
initial-size: 2
|
||||||
min-idle: 2
|
min-idle: 2
|
||||||
max-active: 10
|
max-active: 10
|
||||||
@ -22,10 +22,10 @@ spring:
|
|||||||
|
|
||||||
# redis 连接池配置信息
|
# redis 连接池配置信息
|
||||||
redis:
|
redis:
|
||||||
database: 1
|
database: 7
|
||||||
host: 127.0.0.1
|
host: 47.96.105.74
|
||||||
port: 6379
|
port: 6666
|
||||||
password:
|
password: ASDasd123
|
||||||
timeout: 10000ms
|
timeout: 10000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
@ -34,11 +34,22 @@ spring:
|
|||||||
max-idle: 3
|
max-idle: 3
|
||||||
max-wait: 30000ms
|
max-wait: 30000ms
|
||||||
|
|
||||||
# 上传文件大小配置
|
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||||
servlet:
|
mail:
|
||||||
multipart:
|
host: smtp.163.com
|
||||||
max-file-size: 30MB
|
port: 465
|
||||||
max-request-size: 30MB
|
username: lab1024@163.com
|
||||||
|
password: ROIMSIQCEXHTQFTA
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
ssl:
|
||||||
|
enable: true
|
||||||
|
socketFactory:
|
||||||
|
class: com.sun.mail.util.MailSSLSocketFactory
|
||||||
|
fallback: false
|
||||||
|
debug: false
|
||||||
|
|
||||||
# json序列化相关配置
|
# json序列化相关配置
|
||||||
jackson:
|
jackson:
|
||||||
@ -65,7 +76,6 @@ server:
|
|||||||
max-days: 7
|
max-days: 7
|
||||||
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
|
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
|
||||||
|
|
||||||
|
|
||||||
# 文件上传 配置
|
# 文件上传 配置
|
||||||
file:
|
file:
|
||||||
storage:
|
storage:
|
||||||
@ -77,24 +87,24 @@ file:
|
|||||||
region: oss-cn-hangzhou
|
region: oss-cn-hangzhou
|
||||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||||
bucket-name: 1024lab-smart-admin
|
bucket-name: 1024lab-smart-admin
|
||||||
access-key:
|
access-key: LTAI5tBAbehjXWyAqLhc58e1
|
||||||
secret-key:
|
secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
|
||||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||||
private-url-expire-seconds: 3600
|
private-url-expire-seconds: 3600
|
||||||
|
|
||||||
|
|
||||||
# open api配置
|
# open api配置
|
||||||
springdoc:
|
springdoc:
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
doc-expansion: none #关闭展开
|
doc-expansion: none #关闭展开
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
|
server-base-url:
|
||||||
api-docs:
|
api-docs:
|
||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
knife4j:
|
knife4j:
|
||||||
enable: true
|
enable: true
|
||||||
basic:
|
basic:
|
||||||
enable: true
|
enable: false
|
||||||
username: api # Basic认证用户名
|
username: api # Basic认证用户名
|
||||||
password: 1024 # Basic认证密码
|
password: 1024 # Basic认证密码
|
||||||
|
|
||||||
@ -112,11 +122,11 @@ access-control-allow-origin: '*'
|
|||||||
|
|
||||||
# 心跳配置
|
# 心跳配置
|
||||||
heart-beat:
|
heart-beat:
|
||||||
interval-seconds: 60
|
interval-seconds: 300
|
||||||
|
|
||||||
# 热加载配置
|
# 热加载配置
|
||||||
reload:
|
reload:
|
||||||
interval-seconds: 60
|
interval-seconds: 300
|
||||||
|
|
||||||
# sa-token 配置
|
# sa-token 配置
|
||||||
sa-token:
|
sa-token:
|
||||||
@ -135,9 +145,9 @@ sa-token:
|
|||||||
# 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
|
# 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
|
||||||
auto-renew: true
|
auto-renew: true
|
||||||
# 是否输出操作日志
|
# 是否输出操作日志
|
||||||
is-log: false
|
is-log: true
|
||||||
# 日志等级(trace、debug、info、warn、error、fatal)
|
# 日志等级(trace、debug、info、warn、error、fatal)
|
||||||
log-level: warn
|
log-level: debug
|
||||||
# 启动时的字符画打印
|
# 启动时的字符画打印
|
||||||
is-print: false
|
is-print: false
|
||||||
# 是否从cookie读取token
|
# 是否从cookie读取token
|
||||||
|
@ -33,12 +33,22 @@ spring:
|
|||||||
min-idle: 10
|
min-idle: 10
|
||||||
max-idle: 50
|
max-idle: 50
|
||||||
max-wait: 30000ms
|
max-wait: 30000ms
|
||||||
|
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||||
# 上传文件大小配置
|
mail:
|
||||||
servlet:
|
host: smtp.163.com
|
||||||
multipart:
|
port: 465
|
||||||
max-file-size: 30MB
|
username: lab1024@163.com
|
||||||
max-request-size: 30MB
|
password: 1024lab
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
ssl:
|
||||||
|
enable: true
|
||||||
|
socketFactory:
|
||||||
|
class: com.sun.mail.util.MailSSLSocketFactory
|
||||||
|
fallback: false
|
||||||
|
debug: false
|
||||||
|
|
||||||
# json序列化相关配置
|
# json序列化相关配置
|
||||||
jackson:
|
jackson:
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
spring:
|
spring:
|
||||||
# 数据库连接信息
|
# 数据库连接信息
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
url: jdbc:p6spy:mysql://47.96.105.74:11024/smart_admin_v3_dev?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: Zhuoda1024lab
|
password: 11024Lab
|
||||||
initial-size: 2
|
initial-size: 2
|
||||||
min-idle: 2
|
min-idle: 2
|
||||||
max-active: 10
|
max-active: 10
|
||||||
@ -22,10 +22,10 @@ spring:
|
|||||||
|
|
||||||
# redis 连接池配置信息
|
# redis 连接池配置信息
|
||||||
redis:
|
redis:
|
||||||
database: 1
|
database: 7
|
||||||
host: 127.0.0.1
|
host: 47.96.105.74
|
||||||
port: 6379
|
port: 6666
|
||||||
password:
|
password: ASDasd123
|
||||||
timeout: 10000ms
|
timeout: 10000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
@ -34,11 +34,22 @@ spring:
|
|||||||
max-idle: 3
|
max-idle: 3
|
||||||
max-wait: 30000ms
|
max-wait: 30000ms
|
||||||
|
|
||||||
# 上传文件大小配置
|
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||||
servlet:
|
mail:
|
||||||
multipart:
|
host: smtp.163.com
|
||||||
max-file-size: 30MB
|
port: 465
|
||||||
max-request-size: 30MB
|
username: lab1024@163.com
|
||||||
|
password: ROIMSIQCEXHTQFTA
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
ssl:
|
||||||
|
enable: true
|
||||||
|
socketFactory:
|
||||||
|
class: com.sun.mail.util.MailSSLSocketFactory
|
||||||
|
fallback: false
|
||||||
|
debug: false
|
||||||
|
|
||||||
# json序列化相关配置
|
# json序列化相关配置
|
||||||
jackson:
|
jackson:
|
||||||
@ -65,7 +76,6 @@ server:
|
|||||||
max-days: 7
|
max-days: 7
|
||||||
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
|
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
|
||||||
|
|
||||||
|
|
||||||
# 文件上传 配置
|
# 文件上传 配置
|
||||||
file:
|
file:
|
||||||
storage:
|
storage:
|
||||||
@ -77,25 +87,24 @@ file:
|
|||||||
region: oss-cn-hangzhou
|
region: oss-cn-hangzhou
|
||||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||||
bucket-name: 1024lab-smart-admin
|
bucket-name: 1024lab-smart-admin
|
||||||
access-key:
|
access-key: LTAI5tBAbehjXWyAqLhc58e1
|
||||||
secret-key:
|
secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
|
||||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||||
private-url-expire-seconds: 3600
|
private-url-expire-seconds: 3600
|
||||||
|
|
||||||
|
|
||||||
# open api配置
|
# open api配置
|
||||||
springdoc:
|
springdoc:
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
doc-expansion: none #关闭展开
|
doc-expansion: none #关闭展开
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
server-base-url: http://smartadmin.dev.1024lab.net/api/
|
server-base-url:
|
||||||
api-docs:
|
api-docs:
|
||||||
enabled: true # 开关
|
enabled: true # 开关
|
||||||
knife4j:
|
knife4j:
|
||||||
enable: true
|
enable: true
|
||||||
basic:
|
basic:
|
||||||
enable: true
|
enable: false
|
||||||
username: api # Basic认证用户名
|
username: api # Basic认证用户名
|
||||||
password: 1024 # Basic认证密码
|
password: 1024 # Basic认证密码
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
VITE_APP_TITLE='SmartAdmin 开发环境(Dev)'
|
VITE_APP_TITLE='SmartAdmin 开发环境(Dev)'
|
||||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
|
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/sa-api'
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
VITE_APP_TITLE='SmartAdmin 测试环境(Test)'
|
VITE_APP_TITLE='SmartAdmin 测试环境(Test)'
|
||||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
|
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/sa-api'
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 数据脱敏api
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任-卓大
|
||||||
|
* @Date: 2024-07-31 21:02:37
|
||||||
|
* @Copyright 1024创新实验室
|
||||||
|
*/
|
||||||
|
import { getRequest } from '/src/lib/axios';
|
||||||
|
|
||||||
|
export const dataMaskingApi = {
|
||||||
|
/**
|
||||||
|
* 查询脱敏数据
|
||||||
|
*/
|
||||||
|
query: () => {
|
||||||
|
return getRequest('/support/dataMasking/demo/query');
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 三级等保 api 封装
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任-卓大
|
||||||
|
* @Date: 2024-07-31 21:02:37
|
||||||
|
* @Copyright 1024创新实验室
|
||||||
|
*/
|
||||||
|
import { postRequest, getRequest } from '/src/lib/axios';
|
||||||
|
|
||||||
|
export const level3ProtectApi = {
|
||||||
|
/**
|
||||||
|
* 查询 三级等保配置 @author 1024创新实验室-主任-卓大
|
||||||
|
*/
|
||||||
|
getConfig: () => {
|
||||||
|
return getRequest('/support/protect/level3protect/getConfig');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新三级等保配置 @author 1024创新实验室-主任-卓大
|
||||||
|
*/
|
||||||
|
updateConfig: (form) => {
|
||||||
|
return postRequest('/support/protect/level3protect/updateConfig', form);
|
||||||
|
},
|
||||||
|
};
|
@ -8,7 +8,7 @@
|
|||||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getRequest, postRequest } from '/src/lib/axios';
|
import { getRequest, postEncryptRequest, postRequest } from '/src/lib/axios';
|
||||||
|
|
||||||
export const employeeApi = {
|
export const employeeApi = {
|
||||||
/**
|
/**
|
||||||
@ -72,11 +72,19 @@ export const employeeApi = {
|
|||||||
return getRequest(`/employee/update/password/reset/${employeeId}`);
|
return getRequest(`/employee/update/password/reset/${employeeId}`);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 修改面面
|
* 修改密码
|
||||||
*/
|
*/
|
||||||
updateEmployeePassword: (param) => {
|
updateEmployeePassword: (param) => {
|
||||||
return postRequest('/employee/update/password', param);
|
return postEncryptRequest('/employee/update/password', param);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取密码复杂度
|
||||||
|
*/
|
||||||
|
getPasswordComplexityEnabled: () => {
|
||||||
|
return getRequest('/employee/getPasswordComplexityEnabled');
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新员工禁用状态
|
* 更新员工禁用状态
|
||||||
*/
|
*/
|
||||||
|
@ -37,4 +37,18 @@ export const loginApi = {
|
|||||||
getLoginInfo: () => {
|
getLoginInfo: () => {
|
||||||
return getRequest('/login/getLoginInfo');
|
return getRequest('/login/getLoginInfo');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取邮箱登录验证码 @author 卓大
|
||||||
|
*/
|
||||||
|
sendLoginEmailCode: (loginName) => {
|
||||||
|
return getRequest(`/login/sendEmailCode/${loginName}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取双因子登录标识 @author 卓大
|
||||||
|
*/
|
||||||
|
getTwoFactorLoginFlag: () => {
|
||||||
|
return getRequest('/login/getTwoFactorLoginFlag');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -98,10 +98,12 @@
|
|||||||
//取消全屏
|
//取消全屏
|
||||||
exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
|
exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
|
||||||
fullScreenFlag.value = false;
|
fullScreenFlag.value = false;
|
||||||
|
document.querySelector('#smartAdminPageTag').style.visibility = 'visible';
|
||||||
} else {
|
} else {
|
||||||
//全屏
|
//全屏
|
||||||
launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
|
launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
|
||||||
fullScreenFlag.value = true;
|
fullScreenFlag.value = true;
|
||||||
|
document.querySelector('#smartAdminPageTag').style.visibility = 'hidden';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,4 +25,5 @@ export const regular = {
|
|||||||
isElseFileReg: new RegExp(/\.(doc|docx|xls|xlsx|txt|ppt|pptx|pps|ppxs)/),
|
isElseFileReg: new RegExp(/\.(doc|docx|xls|xlsx|txt|ppt|pptx|pps|ppxs)/),
|
||||||
isIdentityCard: /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/, // 验证身份证号
|
isIdentityCard: /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/, // 验证身份证号
|
||||||
isChinese: /^[\u4e00-\u9fa5]+$/gi, // 验证是否汉字
|
isChinese: /^[\u4e00-\u9fa5]+$/gi, // 验证是否汉字
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<!--
|
||||||
|
* 定期强制修改密码
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任:卓大
|
||||||
|
* @Date: 2024-08-06 20:40:16
|
||||||
|
* @Wechat: zhuda1024
|
||||||
|
* @Email: lab1024@163.com
|
||||||
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<a-modal :open="visible" width="620px" :footer="null" :bodyStyle="{ height: '420px' }" title="" :closable="false" :maskClosable="true">
|
||||||
|
<a-alert style="width: 550px" message="根据《网络安全法》和《数据安全法》要求,需要定期修改密码保障数据安全!" type="warning" show-icon />
|
||||||
|
<Password @on-success="refresh" />
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import Password from '/@/views/system/account/components/password/index.vue';
|
||||||
|
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||||
|
import { loginApi } from '/@/api/system/login-api.js';
|
||||||
|
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||||
|
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码弹窗
|
||||||
|
*/
|
||||||
|
const visible = computed(() => {
|
||||||
|
return useUserStore().$state.needUpdatePwdFlag;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新
|
||||||
|
*/
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
SmartLoading.show();
|
||||||
|
//获取登录用户信息
|
||||||
|
const res = await loginApi.getLoginInfo();
|
||||||
|
//更新用户信息到pinia
|
||||||
|
useUserStore().setUserLoginInfo(res.data);
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const tips = '密码长度8-20位且包含大写字母、小写字母、数字三种'; //校验规则
|
const tips = '密码必须为长度8-20位且包含大小写字母、数字、特殊符号三种及以上组合'; //校验规则
|
||||||
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
|
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
|
@ -8,8 +8,10 @@
|
|||||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
|
<div id="smartAdminPageTag">
|
||||||
<DefaultTab v-if="pageTagStyle === PAGE_TAG_ENUM.DEFAULT.value" />
|
<DefaultTab v-if="pageTagStyle === PAGE_TAG_ENUM.DEFAULT.value" />
|
||||||
<AntdTab v-if="pageTagStyle === PAGE_TAG_ENUM.ANTD.value" />
|
<AntdTab v-if="pageTagStyle === PAGE_TAG_ENUM.ANTD.value" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
<SideExpandLayout v-if="layout === LAYOUT_ENUM.SIDE_EXPAND.value" />
|
<SideExpandLayout v-if="layout === LAYOUT_ENUM.SIDE_EXPAND.value" />
|
||||||
<!--顶部菜单 模式-->
|
<!--顶部菜单 模式-->
|
||||||
<TopLayout v-if="layout === LAYOUT_ENUM.TOP.value" />
|
<TopLayout v-if="layout === LAYOUT_ENUM.TOP.value" />
|
||||||
|
<!--定期修改密码-->
|
||||||
|
<RegularChangePasswordModal />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
@ -22,6 +24,7 @@
|
|||||||
import SideLayout from './side-layout.vue';
|
import SideLayout from './side-layout.vue';
|
||||||
import TopLayout from './top-layout.vue';
|
import TopLayout from './top-layout.vue';
|
||||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||||
|
import RegularChangePasswordModal from './components/change-password/regular-change-password-modal.vue';
|
||||||
|
|
||||||
const layout = computed(() => useAppConfigStore().$state.layout);
|
const layout = computed(() => useAppConfigStore().$state.layout);
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,7 +13,7 @@ import { nextTick } from 'vue';
|
|||||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||||
import { routerArray } from './routers';
|
import { routerArray } from './routers';
|
||||||
import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
|
import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
|
||||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
import { HOME_PAGE_NAME, HOME_PAGE_PATH } from '/@/constants/system/home-const';
|
||||||
import SmartLayout from '../layout/index.vue';
|
import SmartLayout from '../layout/index.vue';
|
||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import { useUserStore } from '/@/store/modules/system/user';
|
||||||
import { localClear, localRead } from '/@/utils/local-util';
|
import { localClear, localRead } from '/@/utils/local-util';
|
||||||
@ -33,7 +33,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
nProgress.start();
|
nProgress.start();
|
||||||
|
|
||||||
// 公共页面,任何时候都可以跳转
|
// 公共页面,任何时候都可以跳转
|
||||||
if (to.path === PAGE_PATH_404 || to.path === PAGE_PATH_LOGIN) {
|
if (to.path === PAGE_PATH_404) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,17 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
|
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
localClear();
|
localClear();
|
||||||
|
if (to.path === PAGE_PATH_LOGIN) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
next({ path: PAGE_PATH_LOGIN });
|
next({ path: PAGE_PATH_LOGIN });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录页,则跳转到首页
|
||||||
|
if (to.path === PAGE_PATH_LOGIN) {
|
||||||
|
next({ path: HOME_PAGE_PATH });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ export const useUserStore = defineStore({
|
|||||||
departmentId: '',
|
departmentId: '',
|
||||||
//部门名词
|
//部门名词
|
||||||
departmentName: '',
|
departmentName: '',
|
||||||
|
//是否需要修改密码
|
||||||
|
needUpdatePwdFlag: false,
|
||||||
//是否为超级管理员
|
//是否为超级管理员
|
||||||
administratorFlag: true,
|
administratorFlag: true,
|
||||||
//上次登录ip
|
//上次登录ip
|
||||||
@ -69,6 +71,9 @@ export const useUserStore = defineStore({
|
|||||||
}
|
}
|
||||||
return localRead(LocalStorageKeyConst.USER_TOKEN);
|
return localRead(LocalStorageKeyConst.USER_TOKEN);
|
||||||
},
|
},
|
||||||
|
getNeedUpdatePwdFlag(state){
|
||||||
|
return state.needUpdatePwdFlag;
|
||||||
|
},
|
||||||
//是否初始化了 路由
|
//是否初始化了 路由
|
||||||
getMenuRouterInitFlag(state) {
|
getMenuRouterInitFlag(state) {
|
||||||
return state.menuRouterInitFlag;
|
return state.menuRouterInitFlag;
|
||||||
@ -137,6 +142,7 @@ export const useUserStore = defineStore({
|
|||||||
this.phone = data.phone;
|
this.phone = data.phone;
|
||||||
this.departmentId = data.departmentId;
|
this.departmentId = data.departmentId;
|
||||||
this.departmentName = data.departmentName;
|
this.departmentName = data.departmentName;
|
||||||
|
this.needUpdatePwdFlag = data.needUpdatePwdFlag;
|
||||||
this.administratorFlag = data.administratorFlag;
|
this.administratorFlag = data.administratorFlag;
|
||||||
this.lastLoginIp = data.lastLoginIp;
|
this.lastLoginIp = data.lastLoginIp;
|
||||||
this.lastLoginIpRegion = data.lastLoginIpRegion;
|
this.lastLoginIpRegion = data.lastLoginIpRegion;
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
<!--
|
||||||
|
* 数据脱敏
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任:卓大
|
||||||
|
* @Date: 2024-08-02 20:23:08
|
||||||
|
* @Wechat: zhuda1024
|
||||||
|
* @Email: lab1024@163.com
|
||||||
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<a-card size="small" :bordered="false" :hoverable="true">
|
||||||
|
<a-alert>
|
||||||
|
<template v-slot:message>
|
||||||
|
<h4>数据脱敏 Data Masking介绍:</h4>
|
||||||
|
</template>
|
||||||
|
<template v-slot:description>
|
||||||
|
<pre>
|
||||||
|
简介:《信息安全技术 网络安全等级保护基本要求》明确规定,二级以上保护则需要对敏感数据进行脱敏处理。
|
||||||
|
原理:数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。
|
||||||
|
举例:在不违反系统规则条件下,身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
1)脱敏注解 @DataMasking ,支持数据类型如:用户ID、手机号、密码、地址、银行卡、车牌号等;
|
||||||
|
2)脱敏工具类: SmartDataMaskingUtil ;
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
|
||||||
|
<a-form class="smart-query-form">
|
||||||
|
<a-row class="smart-query-form-row">
|
||||||
|
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||||
|
<a-button-group>
|
||||||
|
<a-button type="primary" @click="onSearch">
|
||||||
|
<template #icon>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</template>
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</a-button-group>
|
||||||
|
</a-form-item>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
:scroll="{ x: 1100 }"
|
||||||
|
:loading="tableLoading"
|
||||||
|
class="smart-margin-top10"
|
||||||
|
:dataSource="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="false"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { heartBeatApi } from '/@/api/support/heart-beat-api';
|
||||||
|
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||||
|
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||||
|
import { smartSentry } from '/@/lib/smart-sentry';
|
||||||
|
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||||
|
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||||
|
import { dataMaskingApi } from '/@/api/support/data-masking-api.js';
|
||||||
|
|
||||||
|
//------------------------ 表格渲染 ---------------------
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '用户ID',
|
||||||
|
dataIndex: 'userId',
|
||||||
|
width: 70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '默认',
|
||||||
|
dataIndex: 'other',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'phone',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '身份证',
|
||||||
|
dataIndex: 'idCard',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '密码',
|
||||||
|
dataIndex: 'password',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'email',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '车牌号',
|
||||||
|
dataIndex: 'carLicense',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '银行卡',
|
||||||
|
dataIndex: 'bankCard',
|
||||||
|
width: 170,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '地址',
|
||||||
|
dataIndex: 'address',
|
||||||
|
width: 210,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tableLoading = ref(false);
|
||||||
|
const tableData = ref([]);
|
||||||
|
|
||||||
|
function onSearch() {
|
||||||
|
ajaxQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ajaxQuery() {
|
||||||
|
try {
|
||||||
|
tableLoading.value = true;
|
||||||
|
let responseModel = await dataMaskingApi.query();
|
||||||
|
tableData.value = responseModel.data;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
tableLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(ajaxQuery);
|
||||||
|
</script>
|
@ -0,0 +1,254 @@
|
|||||||
|
<!--
|
||||||
|
* 三级等保配置
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任-卓大
|
||||||
|
* @Date: 2024-07-31 22:02:37
|
||||||
|
* @Copyright 1024创新实验室
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<a-alert closable>
|
||||||
|
<template v-slot:message>
|
||||||
|
<h4>三级等保:</h4>
|
||||||
|
</template>
|
||||||
|
<template v-slot:description>
|
||||||
|
<pre>
|
||||||
|
1.三级等保是中国国家等级保护认证中的最高级别认证,该认证包含了五个等级保护安全技术要求和五个安全管理要求,共涉及测评分类73类,要求非常严格。
|
||||||
|
2.三级等保是地市级以上国家机关、重要企事业单位需要达成的认证,在金融行业中,可以看作是除了银行机构以外最高级别的信息安全等级保护。
|
||||||
|
3.具体三级等保要求,请查看“1024创新实验室”写的相关文档 <a href="https://smartadmin.vip/asd">三级等保文档</a></pre>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
<br />
|
||||||
|
<!---------- 三级等保配置表单 begin ----------->
|
||||||
|
<a-card title="三级等保配置">
|
||||||
|
<a-form
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
ref="formRef"
|
||||||
|
style="width: 800px"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 18 }"
|
||||||
|
autocomplete="off"
|
||||||
|
class="smart-query-form"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="配置双因子登录模式"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="在用户登录时,需要同时提供用户名和密码以及其他形式的身份验证信息,例如短信验证码等"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="form.twoFactorLoginEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="最大连续登录失败次数"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="连续登录失败超过一定次数,则需要锁定;默认5次;0则不锁定;"
|
||||||
|
name="loginFailMaxTimes"
|
||||||
|
>
|
||||||
|
<a-input-number :min="0" :max="10" v-model:value="form.loginFailMaxTimes" placeholder="最大连续登录失败次数" addon-after="次" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="loginFailLockMinutes"
|
||||||
|
label="连续登录失败锁定分钟"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="连续登录失败锁定的时间;默认30分钟,0则不锁定"
|
||||||
|
>
|
||||||
|
<a-input-number :min="0" v-model:value="form.loginFailLockMinutes" placeholder="连续登录失败锁定时分钟" addon-after="分钟" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="loginActiveTimeoutMinutes"
|
||||||
|
label="登录后无操作自动退出的分钟"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="如:登录1小时没操作自动退出当前登录状态;默认30分钟"
|
||||||
|
>
|
||||||
|
<a-input-number :min="-1" v-model:value="form.loginActiveTimeoutMinutes" placeholder="登录后无操作自动退出的分钟" addon-after="分钟" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="开启密码复杂度"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="密码长度为8-20位且必须包含字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="form.passwordComplexityEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="regularChangePasswordMonths"
|
||||||
|
label="定期修改密码时间间隔"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="定期修改密码时间间隔,默认3个月"
|
||||||
|
>
|
||||||
|
<a-input-number :min="-1" :max="6" v-model:value="form.regularChangePasswordMonths" placeholder="定期修改密码时间间隔" addon-after="月" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="regularChangePasswordNotAllowRepeatTimes"
|
||||||
|
label="定期修改密码不允许重复次数"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="定期修改密码不允许重复次数,默认:3次以内密码不能相同"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="-1"
|
||||||
|
:max="6"
|
||||||
|
v-model:value="form.regularChangePasswordNotAllowRepeatTimes"
|
||||||
|
placeholder="相同密码不允许重复次数"
|
||||||
|
addon-after="次"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="文件安全检测"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="对文件类型、恶意文件进行检测;(具体请看后端: SecurityFileService 类 checkFile 方法 )"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="form.fileDetectFlag" checked-children="开启 " un-checked-children="关闭 " />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="maxUploadFileSizeMb"
|
||||||
|
label="上传文件大小限制"
|
||||||
|
class="smart-query-form-item"
|
||||||
|
extra="上传文件大小限制,默认 50 mb ( 0 表示不限制)"
|
||||||
|
>
|
||||||
|
<a-input-number :min="0" v-model:value="form.maxUploadFileSizeMb" placeholder="上传文件大小限制" addon-after="mb(兆)" />
|
||||||
|
</a-form-item>
|
||||||
|
<br />
|
||||||
|
<a-form-item :wrapper-col="{ span: 14, offset: 6 }">
|
||||||
|
<a-button type="primary" style="margin-right: 20px" @click.prevent="onSubmit">保存配置</a-button>
|
||||||
|
<a-button style="margin-right: 20px" @click="reset">恢复三级等保默认配置</a-button>
|
||||||
|
<a-button danger @click="clear">清除所有配置</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
<!---------- 请求参数加密 end ----------->
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { level3ProtectApi } from '/@/api/support/level3-protect-api.js';
|
||||||
|
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||||
|
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||||
|
import { message, Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
|
// 三级等保的默认值
|
||||||
|
const protectDefaultValues = {
|
||||||
|
// 连续登录失败次数则锁定
|
||||||
|
loginFailMaxTimes: 5,
|
||||||
|
// 连续登录失败锁定分钟
|
||||||
|
loginFailLockMinutes: 30,
|
||||||
|
// 最低活跃时间分钟
|
||||||
|
loginActiveTimeoutMinutes: 30,
|
||||||
|
// 密码复杂度
|
||||||
|
passwordComplexityEnabled: true,
|
||||||
|
// 定期修改密码时间间隔 月份
|
||||||
|
regularChangePasswordMonths: 3,
|
||||||
|
// 定期修改密码不允许重复次数,默认:3次以内密码不能相同
|
||||||
|
regularChangePasswordNotAllowRepeatTimes: 3,
|
||||||
|
// 开启双因子登录
|
||||||
|
twoFactorLoginEnabled: true,
|
||||||
|
// 文件检测,默认:不开启
|
||||||
|
fileDetectFlag: true,
|
||||||
|
// 文件大小限制,单位 mb ,(默认:50 mb)
|
||||||
|
maxUploadFileSizeMb: 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 三级等保的不保护的默认值
|
||||||
|
const noProtectDefaultValues = {
|
||||||
|
// 连续登录失败次数则锁定
|
||||||
|
loginFailMaxTimes: 0,
|
||||||
|
// 连续登录失败锁定分钟
|
||||||
|
loginFailLockMinutes: 0,
|
||||||
|
// 最低活跃时间分钟
|
||||||
|
loginActiveTimeoutMinutes: 0,
|
||||||
|
// 密码复杂度
|
||||||
|
passwordComplexityEnabled: false,
|
||||||
|
// 定期修改密码时间间隔 月份
|
||||||
|
regularChangePasswordMonths: 0,
|
||||||
|
// 定期修改密码不允许重复次数,
|
||||||
|
regularChangePasswordNotAllowRepeatTimes: 0,
|
||||||
|
// 开启双因子登录
|
||||||
|
twoFactorLoginEnabled: false,
|
||||||
|
// 文件大小限制,单位 mb ,
|
||||||
|
maxUploadFileSizeMb: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 三级等保配置表单
|
||||||
|
const form = reactive({
|
||||||
|
...protectDefaultValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
loginFailMaxTimes: [{ required: true, message: '请输入 最大连续登录失败次数' }],
|
||||||
|
loginFailLockMinutes: [{ required: true, message: '请输入 连续登录失败锁定分钟' }],
|
||||||
|
loginActiveTimeoutMinutes: [{ required: true, message: '请输入 最低活跃时间分钟' }],
|
||||||
|
regularChangePasswordMonths: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||||
|
regularChangePasswordNotAllowRepeatTimes: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||||
|
maxUploadFileSizeMb: [{ required: true, message: '请输入 上传文件大小限制' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
//获取配置
|
||||||
|
async function getConfig() {
|
||||||
|
SmartLoading.show();
|
||||||
|
try {
|
||||||
|
let res = await level3ProtectApi.getConfig();
|
||||||
|
if (!res.data) {
|
||||||
|
message.warn('当前未配置三级等保');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let json = JSON.parse(res.data);
|
||||||
|
form.loginFailMaxTimes = json.loginFailMaxTimes;
|
||||||
|
form.loginFailLockMinutes = json.loginFailLockMinutes;
|
||||||
|
form.loginActiveTimeoutMinutes = json.loginActiveTimeoutMinutes;
|
||||||
|
form.passwordComplexityEnabled = json.passwordComplexityEnabled;
|
||||||
|
form.regularChangePasswordMonths = json.regularChangePasswordMonths;
|
||||||
|
form.regularChangePasswordNotAllowRepeatTimes = json.regularChangePasswordNotAllowRepeatTimes;
|
||||||
|
form.twoFactorLoginEnabled = json.twoFactorLoginEnabled;
|
||||||
|
form.maxUploadFileSizeMb = json.maxUploadFileSizeMb;
|
||||||
|
form.fileDetectFlag = json.fileDetectFlag;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(getConfig);
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
// 提交修改
|
||||||
|
function onSubmit() {
|
||||||
|
formRef.value
|
||||||
|
.validate()
|
||||||
|
.then(save)
|
||||||
|
.catch((error) => {
|
||||||
|
message.error('参数验证错误,请仔细填写表单数据!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交修改配置
|
||||||
|
async function save() {
|
||||||
|
SmartLoading.show();
|
||||||
|
try {
|
||||||
|
let res = await level3ProtectApi.updateConfig(form);
|
||||||
|
message.success(res.msg);
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
function reset() {
|
||||||
|
Object.assign(form, protectDefaultValues);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有配置
|
||||||
|
function clear() {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要清除三级等保配置吗?这样系统不安全哦',
|
||||||
|
okText: '清除三级等保配置',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk() {
|
||||||
|
Object.assign(form, noProtectDefaultValues);
|
||||||
|
save();
|
||||||
|
},
|
||||||
|
cancelText: '取消',
|
||||||
|
onCancel() {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
@ -10,8 +10,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-form class="smart-query-form" v-privilege="'support:operateLog:query'">
|
<a-form class="smart-query-form" v-privilege="'support:operateLog:query'">
|
||||||
<a-row class="smart-query-form-row">
|
<a-row class="smart-query-form-row">
|
||||||
|
<a-form-item label="操作关键字" class="smart-query-form-item">
|
||||||
|
<a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="请求关键字" class="smart-query-form-item">
|
||||||
|
<a-input style="width: 220px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数" />
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="用户名称" class="smart-query-form-item">
|
<a-form-item label="用户名称" class="smart-query-form-item">
|
||||||
<a-input style="width: 300px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
<a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="请求时间" class="smart-query-form-item">
|
<a-form-item label="请求时间" class="smart-query-form-item">
|
||||||
@ -168,6 +174,8 @@
|
|||||||
|
|
||||||
const queryFormState = {
|
const queryFormState = {
|
||||||
userName: '',
|
userName: '',
|
||||||
|
requestKeywords: '',
|
||||||
|
keywords: '',
|
||||||
successFlag: undefined,
|
successFlag: undefined,
|
||||||
startDate: undefined,
|
startDate: undefined,
|
||||||
endDate: undefined,
|
endDate: undefined,
|
||||||
|
@ -95,6 +95,8 @@
|
|||||||
departmentId: undefined,
|
departmentId: undefined,
|
||||||
// 是否启用
|
// 是否启用
|
||||||
disabledFlag: undefined,
|
disabledFlag: undefined,
|
||||||
|
// 邮箱
|
||||||
|
email: undefined,
|
||||||
// 备注
|
// 备注
|
||||||
remark: '',
|
remark: '',
|
||||||
};
|
};
|
||||||
@ -126,6 +128,7 @@
|
|||||||
form.employeeId = data.employeeId;
|
form.employeeId = data.employeeId;
|
||||||
form.loginName = data.loginName;
|
form.loginName = data.loginName;
|
||||||
form.actualName = data.actualName;
|
form.actualName = data.actualName;
|
||||||
|
form.email = data.email;
|
||||||
form.gender = data.gender;
|
form.gender = data.gender;
|
||||||
form.phone = data.phone;
|
form.phone = data.phone;
|
||||||
form.departmentId = data.departmentId;
|
form.departmentId = data.departmentId;
|
||||||
|
@ -1,40 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="password-container">
|
<div class="password-container">
|
||||||
<!-- 页面标题-->
|
|
||||||
<div class="header-title">修改密码</div>
|
|
||||||
<!-- 内容区域-->
|
<!-- 内容区域-->
|
||||||
<div class="password-form-area">
|
<div class="password-form-area">
|
||||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||||
<a-form-item label="原密码" name="oldPassword">
|
<a-form-item label="原密码" name="oldPassword">
|
||||||
<a-input class="form-item" v-model:value.trim="form.oldPassword" type="password" placeholder="请输入原密码" />
|
<a-input-password class="form-item" v-model:value.trim="form.oldPassword" type="password" placeholder="请输入原密码" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="新密码" name="newPassword" :help="tips">
|
<a-form-item label="新密码" name="newPassword" :help="tips">
|
||||||
<a-input class="form-item" v-model:value.trim="form.newPassword" type="password" placeholder="请输入新密码" />
|
<a-input-password class="form-item" v-model:value.trim="form.newPassword" type="password" placeholder="请输入新密码" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="确认密码" name="confirmPwd" :help="tips">
|
<a-form-item label="确认密码" name="confirmPwd" :help="tips">
|
||||||
<a-input class="form-item" v-model:value.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" />
|
<a-input-password class="form-item" v-model:value.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-button type="primary" @click="onSubmit">修改密码</a-button>
|
<a-button type="primary" style="margin: 20px 0 0 250px" @click="onSubmit">修改密码</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||||
import { employeeApi } from '/@/api/system/employee-api.js';
|
import { employeeApi } from '/@/api/system/employee-api.js';
|
||||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||||
|
|
||||||
const formRef = ref();
|
const emits = defineEmits(['onSuccess']);
|
||||||
const tips = '密码长度8-20位且包含大写字母、小写字母、数字三种'; //校验规则
|
|
||||||
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
|
|
||||||
|
|
||||||
const rules = {
|
const formRef = ref();
|
||||||
|
const passwordComplexityEnabledTips = '密码长度8-20位,必须包含字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符'; //校验规则
|
||||||
|
const passwordTips = '密码长度至少8位';
|
||||||
|
const tips = ref(passwordTips);
|
||||||
|
const reg =
|
||||||
|
/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]{8,20}$/;
|
||||||
|
|
||||||
|
// 获取系统的密码复杂度
|
||||||
|
const passwordComplexityEnabledFlag = ref(false);
|
||||||
|
|
||||||
|
async function getPasswordComplexityEnabled() {
|
||||||
|
try {
|
||||||
|
SmartLoading.show();
|
||||||
|
let res = await employeeApi.getPasswordComplexityEnabled();
|
||||||
|
passwordComplexityEnabledFlag.value = res.data;
|
||||||
|
tips.value = passwordComplexityEnabledFlag.value ? passwordComplexityEnabledTips : passwordTips;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getPasswordComplexityEnabled);
|
||||||
|
|
||||||
|
const passwordComplexityEnabledRules = {
|
||||||
oldPassword: [{ required: true, message: '请输入原密码' }],
|
oldPassword: [{ required: true, message: '请输入原密码' }],
|
||||||
newPassword: [{ required: true, type: 'string', pattern: reg, message: '密码格式错误' }],
|
newPassword: [{ required: true, type: 'string', pattern: reg, message: '密码格式错误' }],
|
||||||
confirmPwd: [{ required: true, type: 'string', pattern: reg, message: '请输入确认密码' }],
|
confirmPwd: [{ required: true, type: 'string', pattern: reg, message: '请输入确认密码' }],
|
||||||
};
|
};
|
||||||
|
const commonRules = {
|
||||||
|
oldPassword: [{ required: true, message: '请输入原密码' }],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '密码格式错误' },
|
||||||
|
{ min: 8, message: '密码长度至少8位' },
|
||||||
|
],
|
||||||
|
confirmPwd: [
|
||||||
|
{ required: true, message: '密码格式错误' },
|
||||||
|
{ min: 8, message: '密码长度至少8位' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const rules = computed(() => {
|
||||||
|
return passwordComplexityEnabledFlag.value ? passwordComplexityEnabledRules : commonRules;
|
||||||
|
});
|
||||||
|
|
||||||
const formDefault = {
|
const formDefault = {
|
||||||
oldPassword: '',
|
oldPassword: '',
|
||||||
@ -56,9 +91,12 @@
|
|||||||
try {
|
try {
|
||||||
await employeeApi.updateEmployeePassword(form);
|
await employeeApi.updateEmployeePassword(form);
|
||||||
message.success('修改成功');
|
message.success('修改成功');
|
||||||
|
|
||||||
form.oldPassword = '';
|
form.oldPassword = '';
|
||||||
form.newPassword = '';
|
form.newPassword = '';
|
||||||
form.confirmPwd = '';
|
form.confirmPwd = '';
|
||||||
|
|
||||||
|
emits('onSuccess');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
smartSentry.captureError(error);
|
smartSentry.captureError(error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -81,7 +119,7 @@
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
|
|
||||||
.form-item {
|
.form-item {
|
||||||
width: 500px !important;
|
width: 550px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,9 @@
|
|||||||
<a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
|
<a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
|
||||||
<p class="hint">初始密码默认为:随机</p>
|
<p class="hint">初始密码默认为:随机</p>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="邮箱" name="email">
|
||||||
|
<a-input v-model:value.trim="form.email" placeholder="请输入邮箱" />
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="性别" name="gender">
|
<a-form-item label="性别" name="gender">
|
||||||
<smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
|
<smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -143,6 +146,7 @@
|
|||||||
departmentId: [{ required: true, message: '部门不能为空' }],
|
departmentId: [{ required: true, message: '部门不能为空' }],
|
||||||
disabledFlag: [{ required: true, message: '状态不能为空' }],
|
disabledFlag: [{ required: true, message: '状态不能为空' }],
|
||||||
leaveFlag: [{ required: true, message: '在职状态不能为空' }],
|
leaveFlag: [{ required: true, message: '在职状态不能为空' }],
|
||||||
|
email: [{ required: true, message: '请输入邮箱' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// 校验表单
|
// 校验表单
|
||||||
|
@ -155,6 +155,11 @@
|
|||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
width: 85,
|
width: 85,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'email',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '超管',
|
title: '超管',
|
||||||
dataIndex: 'administratorFlag',
|
dataIndex: 'administratorFlag',
|
||||||
@ -237,6 +242,9 @@
|
|||||||
params.pageNum = 1;
|
params.pageNum = 1;
|
||||||
params.departmentId = allDepartment ? undefined : props.departmentId;
|
params.departmentId = allDepartment ? undefined : props.departmentId;
|
||||||
let res = await employeeApi.queryEmployee(params);
|
let res = await employeeApi.queryEmployee(params);
|
||||||
|
for (const item of res.data.list) {
|
||||||
|
item.roleNameList = _.join(item.roleNameList, ',');
|
||||||
|
}
|
||||||
tableData.value = res.data.list;
|
tableData.value = res.data.list;
|
||||||
total.value = res.data.total;
|
total.value = res.data.total;
|
||||||
// 清除选中
|
// 清除选中
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
<!--
|
||||||
|
* 客服人员弹窗
|
||||||
|
*
|
||||||
|
* @Author: 1024创新实验室-主任:卓大
|
||||||
|
* @Date: 2022-09-06 20:40:16
|
||||||
|
* @Wechat: zhuda1024
|
||||||
|
* @Email: lab1024@163.com
|
||||||
|
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<a-modal :open="visible" width="600px" :bodyStyle="{height:'480px'}" title="" :closable="false" :maskClosable="true">
|
||||||
|
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px">助力卓大抖音1000个粉丝,开播写代码🎉🎉</div> </a-row>
|
||||||
|
<a-row><div style="font-weight:bolder;margin: 20px auto;font-size: 15px">和1024创新实验室一起,热爱代码,热爱生活,永远年轻,永远前行🎉🎉</div> </a-row>
|
||||||
|
<br />
|
||||||
|
<div class="app-qr-box">
|
||||||
|
<div class="app-qr">
|
||||||
|
<a-image
|
||||||
|
:width="300"
|
||||||
|
style="border-radius: 15px;"
|
||||||
|
src="https://img.smartadmin.1024lab.net/wechat/douyin.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="qr-desc strong"> 打开【抖音APP】-点击【左上角侧边栏】-【点击扫一扫】-【进行关注】</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<a-button type="primary" @click="hide">知道了</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue';
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
});
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
function show() {
|
||||||
|
visible.value = true;
|
||||||
|
}
|
||||||
|
function hide() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.app-qr-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
.app-qr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
> img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.qr-desc {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
overflow-x: hidden;
|
||||||
|
> img {
|
||||||
|
width: 15px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-carousel :deep(.slick-slide) {
|
||||||
|
text-align: center;
|
||||||
|
height: 120px;
|
||||||
|
line-height: 120px;
|
||||||
|
width: 120px;
|
||||||
|
background: #364d79;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-carousel :deep(.slick-slide h3) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
@ -68,21 +68,17 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import HomeHeader from './home-header.vue';
|
import HomeHeader from './home-header.vue';
|
||||||
import HomeNotice from './home-notice.vue';
|
import HomeNotice from './home-notice.vue';
|
||||||
import HomeQuickEntry from './components/quick-entry/home-quick-entry.vue';
|
|
||||||
import OfficialAccountCard from './components/official-account-card.vue';
|
import OfficialAccountCard from './components/official-account-card.vue';
|
||||||
import ToBeDoneCard from './components/to-be-done-card.vue';
|
import ToBeDoneCard from './components/to-be-done-card.vue';
|
||||||
import ChangelogCard from './components/changelog-card.vue';
|
import ChangelogCard from './components/changelog-card.vue';
|
||||||
import Gauge from './components/echarts/gauge.vue';
|
|
||||||
import Category from './components/echarts/category.vue';
|
import Category from './components/echarts/category.vue';
|
||||||
import Pie from './components/echarts/pie.vue';
|
import Pie from './components/echarts/pie.vue';
|
||||||
import Gradient from './components/echarts/gradient.vue';
|
import Gradient from './components/echarts/gradient.vue';
|
||||||
import { Modal } from 'ant-design-vue';
|
|
||||||
|
|
||||||
// 业绩完成百分比
|
// 业绩完成百分比
|
||||||
const saleTargetPercent = computed(() => {
|
const saleTargetPercent = computed(() => {
|
||||||
return 75;
|
return 75;
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './index.less';
|
@import './index.less';
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
.box-item {
|
.box-item {
|
||||||
width: 444px;
|
width: 444px;
|
||||||
height: 570px;
|
height: 600px;
|
||||||
&.desc {
|
&.desc {
|
||||||
background: #003b94;
|
background: #003b94;
|
||||||
border-radius: 12px 0px 0px 12px;
|
border-radius: 12px 0px 0px 12px;
|
||||||
@ -148,6 +148,12 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-btn{
|
||||||
|
height: 44px;
|
||||||
|
padding: 4px 5px;
|
||||||
|
width: 108px;
|
||||||
|
}
|
||||||
|
|
||||||
.eye-box {
|
.eye-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
|
@ -59,12 +59,20 @@
|
|||||||
<a-form-item name="loginName">
|
<a-form-item name="loginName">
|
||||||
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
|
||||||
|
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
|
||||||
|
{{ emailCodeTips }}
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item name="password">
|
<a-form-item name="password">
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model:value="loginForm.password"
|
v-model:value="loginForm.password"
|
||||||
autocomplete="on"
|
autocomplete="on"
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
placeholder="请输入密码:至少三种字符,最小 8 位"
|
placeholder="请输入密码"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="captchaCode">
|
<a-form-item name="captchaCode">
|
||||||
@ -146,7 +154,7 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode === 13) {
|
||||||
onLogin();
|
onLogin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -185,7 +193,7 @@
|
|||||||
password: encryptData(loginForm.password),
|
password: encryptData(loginForm.password),
|
||||||
});
|
});
|
||||||
const res = await loginApi.login(encryptPasswordForm);
|
const res = await loginApi.login(encryptPasswordForm);
|
||||||
stopRefrestCaptchaInterval();
|
stopRefreshCaptchaInterval();
|
||||||
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
//更新用户信息到pinia
|
//更新用户信息到pinia
|
||||||
@ -213,27 +221,78 @@
|
|||||||
let captchaResult = await loginApi.getCaptcha();
|
let captchaResult = await loginApi.getCaptcha();
|
||||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||||
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
||||||
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
|
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refrestCaptchaInterval = null;
|
let refreshCaptchaInterval = null;
|
||||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
function beginRefreshCaptchaInterval(expireSeconds) {
|
||||||
if (refrestCaptchaInterval === null) {
|
if (refreshCaptchaInterval === null) {
|
||||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRefrestCaptchaInterval() {
|
function stopRefreshCaptchaInterval() {
|
||||||
if (refrestCaptchaInterval != null) {
|
if (refreshCaptchaInterval != null) {
|
||||||
clearInterval(refrestCaptchaInterval);
|
clearInterval(refreshCaptchaInterval);
|
||||||
refrestCaptchaInterval = null;
|
refreshCaptchaInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getCaptcha);
|
onMounted(() => {
|
||||||
|
getCaptcha();
|
||||||
|
getTwoFactorLoginFlag();
|
||||||
|
});
|
||||||
|
|
||||||
|
//--------------------- 邮箱验证码 ---------------------------------
|
||||||
|
|
||||||
|
const emailCodeShowFlag = ref(false);
|
||||||
|
let emailCodeTips = ref('获取邮箱验证码');
|
||||||
|
let emailCodeButtonDisabled = ref(false);
|
||||||
|
// 定时器
|
||||||
|
let countDownTimer = null;
|
||||||
|
// 开始倒计时
|
||||||
|
function runCountDown() {
|
||||||
|
emailCodeButtonDisabled.value = true;
|
||||||
|
let countDown = 60;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
countDownTimer = setInterval(() => {
|
||||||
|
if (countDown > 1) {
|
||||||
|
countDown--;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
} else {
|
||||||
|
clearInterval(countDownTimer);
|
||||||
|
emailCodeButtonDisabled.value = false;
|
||||||
|
emailCodeTips.value = '获取验证码';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取双因子登录标识
|
||||||
|
async function getTwoFactorLoginFlag() {
|
||||||
|
try {
|
||||||
|
let result = await loginApi.getTwoFactorLoginFlag();
|
||||||
|
emailCodeShowFlag.value = result.data;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
async function sendSmsCode() {
|
||||||
|
try {
|
||||||
|
SmartLoading.show();
|
||||||
|
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
|
||||||
|
message.success('验证码发送成功!请登录邮箱查看验证码~');
|
||||||
|
runCountDown();
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './login.less';
|
@import './login.less';
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
.box-item {
|
.box-item {
|
||||||
width: 444px;
|
width: 444px;
|
||||||
height: 570px;
|
height: 600px;
|
||||||
&.desc {
|
&.desc {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 12px 0px 0px 12px;
|
border-radius: 12px 0px 0px 12px;
|
||||||
@ -135,6 +135,11 @@
|
|||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.code-btn{
|
||||||
|
height: 44px;
|
||||||
|
padding: 4px 5px;
|
||||||
|
width: 108px;
|
||||||
|
}
|
||||||
|
|
||||||
.eye-box {
|
.eye-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -23,12 +23,20 @@
|
|||||||
<a-form-item name="loginName">
|
<a-form-item name="loginName">
|
||||||
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
|
||||||
|
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
|
||||||
|
{{ emailCodeTips }}
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item name="password">
|
<a-form-item name="password">
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model:value="loginForm.password"
|
v-model:value="loginForm.password"
|
||||||
autocomplete="on"
|
autocomplete="on"
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
placeholder="请输入密码:至少三种字符,最小 8 位"
|
placeholder="请输入密码"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="captchaCode">
|
<a-form-item name="captchaCode">
|
||||||
@ -107,7 +115,7 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode === 13) {
|
||||||
onLogin();
|
onLogin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -127,7 +135,7 @@
|
|||||||
password: encryptData(loginForm.password),
|
password: encryptData(loginForm.password),
|
||||||
});
|
});
|
||||||
const res = await loginApi.login(encryptPasswordForm);
|
const res = await loginApi.login(encryptPasswordForm);
|
||||||
stopRefrestCaptchaInterval();
|
stopRefreshCaptchaInterval();
|
||||||
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
//更新用户信息到pinia
|
//更新用户信息到pinia
|
||||||
@ -155,27 +163,78 @@
|
|||||||
let captchaResult = await loginApi.getCaptcha();
|
let captchaResult = await loginApi.getCaptcha();
|
||||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||||
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
||||||
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
|
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refrestCaptchaInterval = null;
|
let refreshCaptchaInterval = null;
|
||||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
function beginRefreshCaptchaInterval(expireSeconds) {
|
||||||
if (refrestCaptchaInterval === null) {
|
if (refreshCaptchaInterval === null) {
|
||||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRefrestCaptchaInterval() {
|
function stopRefreshCaptchaInterval() {
|
||||||
if (refrestCaptchaInterval != null) {
|
if (refreshCaptchaInterval != null) {
|
||||||
clearInterval(refrestCaptchaInterval);
|
clearInterval(refreshCaptchaInterval);
|
||||||
refrestCaptchaInterval = null;
|
refreshCaptchaInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getCaptcha);
|
onMounted(() => {
|
||||||
|
getCaptcha();
|
||||||
|
getTwoFactorLoginFlag();
|
||||||
|
});
|
||||||
|
|
||||||
|
//--------------------- 邮箱验证码 ---------------------------------
|
||||||
|
|
||||||
|
const emailCodeShowFlag = ref(false);
|
||||||
|
let emailCodeTips = ref('获取邮箱验证码');
|
||||||
|
let emailCodeButtonDisabled = ref(false);
|
||||||
|
// 定时器
|
||||||
|
let countDownTimer = null;
|
||||||
|
// 开始倒计时
|
||||||
|
function runCountDown() {
|
||||||
|
emailCodeButtonDisabled.value = true;
|
||||||
|
let countDown = 60;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
countDownTimer = setInterval(() => {
|
||||||
|
if (countDown > 1) {
|
||||||
|
countDown--;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
} else {
|
||||||
|
clearInterval(countDownTimer);
|
||||||
|
emailCodeButtonDisabled.value = false;
|
||||||
|
emailCodeTips.value = '获取验证码';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取双因子登录标识
|
||||||
|
async function getTwoFactorLoginFlag() {
|
||||||
|
try {
|
||||||
|
let result = await loginApi.getTwoFactorLoginFlag();
|
||||||
|
emailCodeShowFlag.value = result.data;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
async function sendSmsCode() {
|
||||||
|
try {
|
||||||
|
SmartLoading.show();
|
||||||
|
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
|
||||||
|
message.success('验证码发送成功!请登录邮箱查看验证码~');
|
||||||
|
runCountDown();
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './login.less';
|
@import './login.less';
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
.box-item {
|
.box-item {
|
||||||
width: 444px;
|
width: 444px;
|
||||||
height: 570px;
|
height: 600px;
|
||||||
&.desc {
|
&.desc {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 12px 0px 0px 12px;
|
border-radius: 12px 0px 0px 12px;
|
||||||
@ -143,7 +143,11 @@
|
|||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.code-btn{
|
||||||
|
height: 44px;
|
||||||
|
padding: 4px 5px;
|
||||||
|
width: 108px;
|
||||||
|
}
|
||||||
.eye-box {
|
.eye-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
|
@ -24,6 +24,14 @@
|
|||||||
<a-form-item name="loginName">
|
<a-form-item name="loginName">
|
||||||
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
|
||||||
|
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
|
||||||
|
{{ emailCodeTips }}
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item name="password">
|
<a-form-item name="password">
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model:value="loginForm.password"
|
v-model:value="loginForm.password"
|
||||||
@ -109,7 +117,7 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode === 13) {
|
||||||
onLogin();
|
onLogin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -129,7 +137,7 @@
|
|||||||
password: encryptData(loginForm.password),
|
password: encryptData(loginForm.password),
|
||||||
});
|
});
|
||||||
const res = await loginApi.login(encryptPasswordForm);
|
const res = await loginApi.login(encryptPasswordForm);
|
||||||
stopRefrestCaptchaInterval();
|
stopRefreshCaptchaInterval();
|
||||||
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
//更新用户信息到pinia
|
//更新用户信息到pinia
|
||||||
@ -157,27 +165,78 @@
|
|||||||
let captchaResult = await loginApi.getCaptcha();
|
let captchaResult = await loginApi.getCaptcha();
|
||||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||||
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
||||||
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
|
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refrestCaptchaInterval = null;
|
let refreshCaptchaInterval = null;
|
||||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
function beginRefreshCaptchaInterval(expireSeconds) {
|
||||||
if (refrestCaptchaInterval === null) {
|
if (refreshCaptchaInterval === null) {
|
||||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRefrestCaptchaInterval() {
|
function stopRefreshCaptchaInterval() {
|
||||||
if (refrestCaptchaInterval != null) {
|
if (refreshCaptchaInterval != null) {
|
||||||
clearInterval(refrestCaptchaInterval);
|
clearInterval(refreshCaptchaInterval);
|
||||||
refrestCaptchaInterval = null;
|
refreshCaptchaInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getCaptcha);
|
onMounted(() => {
|
||||||
|
getCaptcha();
|
||||||
|
getTwoFactorLoginFlag();
|
||||||
|
});
|
||||||
|
|
||||||
|
//--------------------- 邮箱验证码 ---------------------------------
|
||||||
|
|
||||||
|
const emailCodeShowFlag = ref(false);
|
||||||
|
let emailCodeTips = ref('获取邮箱验证码');
|
||||||
|
let emailCodeButtonDisabled = ref(false);
|
||||||
|
// 定时器
|
||||||
|
let countDownTimer = null;
|
||||||
|
// 开始倒计时
|
||||||
|
function runCountDown() {
|
||||||
|
emailCodeButtonDisabled.value = true;
|
||||||
|
let countDown = 60;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
countDownTimer = setInterval(() => {
|
||||||
|
if (countDown > 1) {
|
||||||
|
countDown--;
|
||||||
|
emailCodeTips.value = `${countDown}秒后重新获取`;
|
||||||
|
} else {
|
||||||
|
clearInterval(countDownTimer);
|
||||||
|
emailCodeButtonDisabled.value = false;
|
||||||
|
emailCodeTips.value = '获取验证码';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取双因子登录标识
|
||||||
|
async function getTwoFactorLoginFlag() {
|
||||||
|
try {
|
||||||
|
let result = await loginApi.getTwoFactorLoginFlag();
|
||||||
|
emailCodeShowFlag.value = result.data;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
async function sendSmsCode() {
|
||||||
|
try {
|
||||||
|
SmartLoading.show();
|
||||||
|
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
|
||||||
|
message.success('验证码发送成功!请登录邮箱查看验证码~');
|
||||||
|
runCountDown();
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
} finally {
|
||||||
|
SmartLoading.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './login.less';
|
@import './login.less';
|
||||||
|
Loading…
Reference in New Issue
Block a user