mirror of
				https://gitee.com/lab1024/smart-admin.git
				synced 2025-11-04 18:33:43 +08:00 
			
		
		
		
	v3.6.0 三级等保重磅更新:1、【新增】双因子方式登录;2、【新增】定期修改密码;3、【新增】最大活跃时间;4、【新增】敏感数据脱敏;5、【新增】登录锁定配置;6、【新增】密码复杂度配置;7、【新增】三级等保可配置
This commit is contained in:
		@@ -19,9 +19,10 @@
 | 
			
		||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
			
		||||
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 | 
			
		||||
        <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>
 | 
			
		||||
        <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>
 | 
			
		||||
        <springdoc-openapi-ui.version>1.7.0</springdoc-openapi-ui.version>
 | 
			
		||||
        <knife4j.version>4.3.0</knife4j.version>
 | 
			
		||||
@@ -42,7 +43,7 @@
 | 
			
		||||
        <poi.version>5.2.4</poi.version>
 | 
			
		||||
        <ooxml-schemas.version>1.4</ooxml-schemas.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>
 | 
			
		||||
        <velocity-engine-core.version>2.3</velocity-engine-core.version>
 | 
			
		||||
        <jjwt.version>0.9.1</jjwt.version>
 | 
			
		||||
@@ -52,8 +53,12 @@
 | 
			
		||||
        <ip2region.version>2.7.0</ip2region.version>
 | 
			
		||||
        <bcprov.version>1.59</bcprov.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>
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
    <dependencyManagement>
 | 
			
		||||
@@ -81,6 +86,12 @@
 | 
			
		||||
                </exclusions>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.mysql</groupId>
 | 
			
		||||
                <artifactId>mysql-connector-j</artifactId>
 | 
			
		||||
                <version>${mysql-connector-j.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.baomidou</groupId>
 | 
			
		||||
                <artifactId>mybatis-plus-boot-starter</artifactId>
 | 
			
		||||
@@ -201,12 +212,6 @@
 | 
			
		||||
                <version>${commons-text.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.apache.logging.log4j</groupId>
 | 
			
		||||
                <artifactId>log4j-spring-boot</artifactId>
 | 
			
		||||
                <version>${log4j-spring-boot.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>cn.hutool</groupId>
 | 
			
		||||
                <artifactId>hutool-all</artifactId>
 | 
			
		||||
@@ -309,6 +314,12 @@
 | 
			
		||||
                <version>${jackson-datatype-jsr310.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.fasterxml.jackson.dataformat</groupId>
 | 
			
		||||
                <artifactId>jackson-dataformat-yaml</artifactId>
 | 
			
		||||
                <version>${jackson-dataformat-yaml.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>net.1024lab</groupId>
 | 
			
		||||
                <artifactId>smartdb</artifactId>
 | 
			
		||||
@@ -341,6 +352,24 @@
 | 
			
		||||
                <version>${redisson.version}</version>
 | 
			
		||||
            </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>
 | 
			
		||||
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 
 | 
			
		||||
@@ -50,9 +50,6 @@ public class AdminInterceptor implements HandlerInterceptor {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SystemEnvironment systemEnvironment;
 | 
			
		||||
 | 
			
		||||
    @Value("${sa-token.active-timeout}")
 | 
			
		||||
    private long tokenActiveTimeout;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 | 
			
		||||
 | 
			
		||||
@@ -158,11 +155,6 @@ public class AdminInterceptor implements HandlerInterceptor {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 小于1 ,也不需要检测
 | 
			
		||||
        if (tokenActiveTimeout < 1) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        StpUtil.checkActiveTimeout();
 | 
			
		||||
        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.ResponseDTO;
 | 
			
		||||
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 javax.annotation.Resource;
 | 
			
		||||
@@ -23,7 +25,7 @@ import java.util.List;
 | 
			
		||||
 * @Date 2021-12-09 22:57:49
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_EMPLOYEE)
 | 
			
		||||
@@ -32,6 +34,9 @@ public class EmployeeController {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private EmployeeService employeeService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private Level3ProtectConfigService level3ProtectConfigService;
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/employee/query")
 | 
			
		||||
    @Operation(summary = "员工管理查询 @author 卓大")
 | 
			
		||||
    public ResponseDTO<PageResult<EmployeeVO>> query(@Valid @RequestBody EmployeeQueryForm query) {
 | 
			
		||||
@@ -89,9 +94,17 @@ public class EmployeeController {
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "修改密码 @author 卓大")
 | 
			
		||||
    @PostMapping("/employee/update/password")
 | 
			
		||||
    @ApiDecrypt
 | 
			
		||||
    public ResponseDTO<String> updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) {
 | 
			
		||||
        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 卓大")
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,11 @@ public class EmployeeEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private String phone;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    private String email;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门id
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,9 @@ public class EmployeeAddForm {
 | 
			
		||||
    @Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确")
 | 
			
		||||
    private String phone;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "邮箱")
 | 
			
		||||
    private String email;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "角色列表")
 | 
			
		||||
    private List<Long> roleIdList;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,9 @@ public class EmployeeUpdatePasswordForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "原密码")
 | 
			
		||||
    @NotBlank(message = "原密码不能为空哦")
 | 
			
		||||
    @Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "原密码请输入6-15位(数字|大小写字母|小数点)")
 | 
			
		||||
    private String oldPassword;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "新密码")
 | 
			
		||||
    @NotBlank(message = "新密码不能为空哦")
 | 
			
		||||
    @Pattern(regexp = SmartVerificationUtil.PWD_REGEXP, message = "新密码请输入6-15位(数字|大小写字母|小数点)")
 | 
			
		||||
    private String newPassword;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,4 +62,7 @@ public class EmployeeVO {
 | 
			
		||||
    @Schema(description = "职务名称")
 | 
			
		||||
    private String positionName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "邮箱")
 | 
			
		||||
    private String email;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 * @Date 2021-12-29 21:52:46
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
 | 
			
		||||
@@ -38,7 +38,6 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @Transactional(rollbackFor = Throwable.class)
 | 
			
		||||
    public void saveEmployee(EmployeeEntity employee, List<Long> roleIdList) {
 | 
			
		||||
@@ -53,17 +52,20 @@ public class EmployeeManager extends ServiceImpl<EmployeeDao, EmployeeEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @Transactional(rollbackFor = Throwable.class)
 | 
			
		||||
    public void updateEmployee(EmployeeEntity employee, List<Long> roleIdList) {
 | 
			
		||||
        // 保存员工 获得id
 | 
			
		||||
        employeeDao.updateById(employee);
 | 
			
		||||
 | 
			
		||||
        if (CollectionUtils.isNotEmpty(roleIdList)) {
 | 
			
		||||
            List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
 | 
			
		||||
            this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList);
 | 
			
		||||
        // 若为空,则删除所有角色
 | 
			
		||||
        if (CollectionUtils.isEmpty(roleIdList)) {
 | 
			
		||||
            roleEmployeeDao.deleteByEmployeeId(employee.getEmployeeId());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<RoleEmployeeEntity> roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList());
 | 
			
		||||
        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.constant.StringConst;
 | 
			
		||||
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.enumeration.UserTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartBeanUtil;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartPageUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
 | 
			
		||||
import org.apache.commons.codec.digest.DigestUtils;
 | 
			
		||||
import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService;
 | 
			
		||||
import org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
@@ -46,8 +47,6 @@ import java.util.stream.Collectors;
 | 
			
		||||
@Service
 | 
			
		||||
public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private EmployeeDao employeeDao;
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +63,7 @@ public class EmployeeService {
 | 
			
		||||
    private DepartmentService departmentService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectPasswordService protectPasswordService;
 | 
			
		||||
    private SecurityPasswordService securityPasswordService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    @Lazy
 | 
			
		||||
@@ -121,16 +120,11 @@ public class EmployeeService {
 | 
			
		||||
     * 新增员工
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
 | 
			
		||||
        // 校验名称是否重复
 | 
			
		||||
        // 校验登录名是否重复
 | 
			
		||||
        EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null);
 | 
			
		||||
        if (null != employeeEntity) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("登录名重复");
 | 
			
		||||
        }
 | 
			
		||||
        // 校验姓名是否重复
 | 
			
		||||
        employeeEntity = employeeDao.getByActualName(employeeAddForm.getActualName(), null);
 | 
			
		||||
        if (null != employeeEntity) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("姓名重复");
 | 
			
		||||
        }
 | 
			
		||||
        // 校验电话是否存在
 | 
			
		||||
        employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null);
 | 
			
		||||
        if (null != employeeEntity) {
 | 
			
		||||
@@ -146,8 +140,8 @@ public class EmployeeService {
 | 
			
		||||
        EmployeeEntity entity = SmartBeanUtil.copy(employeeAddForm, EmployeeEntity.class);
 | 
			
		||||
 | 
			
		||||
        // 设置密码 默认密码
 | 
			
		||||
        String password = protectPasswordService.randomPassword();
 | 
			
		||||
        entity.setLoginPwd(getEncryptPwd(password));
 | 
			
		||||
        String password = securityPasswordService.randomPassword();
 | 
			
		||||
        entity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password));
 | 
			
		||||
 | 
			
		||||
        // 保存数据
 | 
			
		||||
        entity.setDeletedFlag(Boolean.FALSE);
 | 
			
		||||
@@ -185,11 +179,6 @@ public class EmployeeService {
 | 
			
		||||
            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);
 | 
			
		||||
        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();
 | 
			
		||||
        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
 | 
			
		||||
        if (employeeEntity == null) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验原始密码
 | 
			
		||||
        String encryptPwd = getEncryptPwd(updatePasswordForm.getOldPassword());
 | 
			
		||||
        if (!Objects.equals(encryptPwd, employeeEntity.getLoginPwd())) {
 | 
			
		||||
        String oldPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getOldPassword());
 | 
			
		||||
        if (!Objects.equals(oldPassword, employeeEntity.getLoginPwd())) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("原密码有误,请重新输入");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验密码复杂度
 | 
			
		||||
        ResponseDTO<String> validatePassComplexity = securityPasswordService.validatePasswordComplexity(updatePasswordForm.getNewPassword());
 | 
			
		||||
        if (!validatePassComplexity.getOk()) {
 | 
			
		||||
            return validatePassComplexity;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 新旧密码相同
 | 
			
		||||
        String newPassword = updatePasswordForm.getNewPassword();
 | 
			
		||||
        if (Objects.equals(updatePasswordForm.getOldPassword(), newPassword)) {
 | 
			
		||||
        String newPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword());
 | 
			
		||||
        if (Objects.equals(oldPassword, newPassword)) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验密码复杂度
 | 
			
		||||
        ResponseDTO<String> validatePassComplexity = protectPasswordService.validatePassComplexity(newPassword);
 | 
			
		||||
        if (!validatePassComplexity.getOk()) {
 | 
			
		||||
            return validatePassComplexity;
 | 
			
		||||
        // 根据三级等保规则,校验密码是否重复
 | 
			
		||||
        ResponseDTO<String> passwordRepeatTimes = securityPasswordService.validatePasswordRepeatTimes(requestUser, updatePasswordForm.getNewPassword());
 | 
			
		||||
        if (!passwordRepeatTimes.getOk()) {
 | 
			
		||||
            return ResponseDTO.error(passwordRepeatTimes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 更新密码
 | 
			
		||||
        EmployeeEntity updateEntity = new EmployeeEntity();
 | 
			
		||||
        updateEntity.setEmployeeId(employeeId);
 | 
			
		||||
        updateEntity.setLoginPwd(getEncryptPwd(newPassword));
 | 
			
		||||
        updateEntity.setLoginPwd(newPassword);
 | 
			
		||||
        employeeDao.updateById(updateEntity);
 | 
			
		||||
 | 
			
		||||
        // 保存修改密码密码记录
 | 
			
		||||
        securityPasswordService.saveUserChangePasswordLog(requestUser, newPassword, oldPassword);
 | 
			
		||||
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -364,18 +363,11 @@ public class EmployeeService {
 | 
			
		||||
     * 重置密码
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> resetPassword(Integer employeeId) {
 | 
			
		||||
        String password = protectPasswordService.randomPassword();
 | 
			
		||||
        employeeDao.updatePassword(employeeId, getEncryptPwd(password));
 | 
			
		||||
        String password = securityPasswordService.randomPassword();
 | 
			
		||||
        employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(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.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.tags.Tag;
 | 
			
		||||
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.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.util.SmartRequestUtil;
 | 
			
		||||
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 javax.annotation.Resource;
 | 
			
		||||
@@ -27,7 +28,7 @@ import javax.validation.Valid;
 | 
			
		||||
 * @Date 2021-12-15 21:05:46
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_LOGIN)
 | 
			
		||||
@@ -36,6 +37,9 @@ public class LoginController {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private LoginService loginService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private Level3ProtectConfigService level3ProtectConfigService;
 | 
			
		||||
 | 
			
		||||
    @NoNeedLogin
 | 
			
		||||
    @PostMapping("/login")
 | 
			
		||||
    @Operation(summary = "登录 @author 卓大")
 | 
			
		||||
@@ -48,8 +52,8 @@ public class LoginController {
 | 
			
		||||
    @GetMapping("/login/getLoginInfo")
 | 
			
		||||
    @Operation(summary = "获取登录结果信息  @author 卓大")
 | 
			
		||||
    public ResponseDTO<LoginResultVO> getLoginInfo() {
 | 
			
		||||
        LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser());
 | 
			
		||||
        String tokenValue = StpUtil.getTokenValue();
 | 
			
		||||
        LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), tokenValue);
 | 
			
		||||
        loginResult.setToken(tokenValue);
 | 
			
		||||
        return ResponseDTO.ok(loginResult);
 | 
			
		||||
    }
 | 
			
		||||
@@ -67,4 +71,20 @@ public class LoginController {
 | 
			
		||||
        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)
 | 
			
		||||
    @CheckEnum(value = LoginDeviceEnum.class, required = true, message = "此终端不允许登录")
 | 
			
		||||
    private Integer loginDevice;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "邮箱验证码")
 | 
			
		||||
    private String emailCode;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,9 @@ public class LoginResultVO extends RequestEmployee {
 | 
			
		||||
    @Schema(description = "菜单列表")
 | 
			
		||||
    private List<MenuVO> menuList;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否需要修改密码")
 | 
			
		||||
    private Boolean needUpdatePwdFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "上次登录ip")
 | 
			
		||||
    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.StpUtil;
 | 
			
		||||
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 com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
 | 
			
		||||
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.SmartEnumUtil;
 | 
			
		||||
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.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.domain.CaptchaVO;
 | 
			
		||||
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.domain.LoginLogEntity;
 | 
			
		||||
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.service.ProtectLoginService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectPasswordService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService;
 | 
			
		||||
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.StringUtils;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
@@ -107,14 +116,26 @@ public class LoginService implements StpInterface {
 | 
			
		||||
    private RoleMenuService roleMenuService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectLoginService protectLoginService;
 | 
			
		||||
    private SecurityLoginService securityLoginService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectPasswordService profectPasswordService;
 | 
			
		||||
    private SecurityPasswordService protectPasswordService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    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);
 | 
			
		||||
        boolean superPasswordFlag = superPassword.equals(requestPassword);
 | 
			
		||||
 | 
			
		||||
        // 校验双因子登录
 | 
			
		||||
        ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
 | 
			
		||||
        if (!validateEmailCode.getOk()) {
 | 
			
		||||
            return ResponseDTO.error(validateEmailCode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 万能密码特殊操作
 | 
			
		||||
        if (superPasswordFlag) {
 | 
			
		||||
 | 
			
		||||
@@ -170,23 +197,27 @@ public class LoginService implements StpInterface {
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            // 按照等保登录要求,进行登录失败次数校验
 | 
			
		||||
            ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = protectLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
 | 
			
		||||
            ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
 | 
			
		||||
            if (!loginFailEntityResponseDTO.getOk()) {
 | 
			
		||||
                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);
 | 
			
		||||
                // 记录等级保护次数
 | 
			
		||||
                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);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
 | 
			
		||||
 | 
			
		||||
            // 登录
 | 
			
		||||
            StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
 | 
			
		||||
 | 
			
		||||
            // 移除邮箱验证码
 | 
			
		||||
            deleteEmailCode(employeeEntity.getEmployeeId());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取员工信息
 | 
			
		||||
@@ -196,16 +227,17 @@ public class LoginService implements StpInterface {
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        // 设置 token
 | 
			
		||||
        loginResultVO.setToken(StpUtil.getTokenValue());
 | 
			
		||||
        loginResultVO.setToken(token);
 | 
			
		||||
 | 
			
		||||
        // 清除权限缓存
 | 
			
		||||
        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);
 | 
			
		||||
@@ -240,6 +272,16 @@ public class LoginService implements StpInterface {
 | 
			
		||||
            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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -259,9 +301,9 @@ public class LoginService implements StpInterface {
 | 
			
		||||
 | 
			
		||||
        // 头像信息
 | 
			
		||||
        String avatar = employeeEntity.getAvatar();
 | 
			
		||||
        if(StringUtils.isNotBlank(avatar)){
 | 
			
		||||
        if (StringUtils.isNotBlank(avatar)) {
 | 
			
		||||
            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
 | 
			
		||||
            if(BooleanUtils.isTrue(getFileUrl.getOk())){
 | 
			
		||||
            if (BooleanUtils.isTrue(getFileUrl.getOk())) {
 | 
			
		||||
                requestEmployee.setAvatar(getFileUrl.getData());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -357,9 +399,8 @@ public class LoginService implements StpInterface {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清除员工登录缓存
 | 
			
		||||
     * @param employeeId
 | 
			
		||||
     */
 | 
			
		||||
    public void clearLoginEmployeeCache(Long employeeId){
 | 
			
		||||
    public void clearLoginEmployeeCache(Long employeeId) {
 | 
			
		||||
        // 清空登录信息缓存
 | 
			
		||||
        loginEmployeeCache.remove(employeeId);
 | 
			
		||||
    }
 | 
			
		||||
@@ -451,4 +492,94 @@ public class LoginService implements StpInterface {
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
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.domain.PageResult;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ValidateList;
 | 
			
		||||
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.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.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
@@ -18,14 +23,13 @@ import javax.annotation.Resource;
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * 网络安全
 | 
			
		||||
 *
 | 
			
		||||
 * @Author 1024创新实验室-主任:卓大
 | 
			
		||||
 * @Date 2023/10/17 19:07:27
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@@ -33,20 +37,37 @@ import javax.validation.Valid;
 | 
			
		||||
public class AdminProtectController extends SupportBaseController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectLoginService protectLoginService;
 | 
			
		||||
    private SecurityLoginService securityLoginService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private Level3ProtectConfigService level3ProtectConfigService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ConfigService configService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大")
 | 
			
		||||
    @PostMapping("/protect/loginFail/queryPage")
 | 
			
		||||
    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创新实验室-主任-卓大")
 | 
			
		||||
    @PostMapping("/protect/loginFail/batchDelete")
 | 
			
		||||
    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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,27 +19,4 @@ server:
 | 
			
		||||
# 环境
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    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
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
@@ -19,27 +19,4 @@ server:
 | 
			
		||||
# 环境
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    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
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
@@ -19,27 +19,4 @@ server:
 | 
			
		||||
# 环境
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    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
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
# 项目配置: 名称、日志目录
 | 
			
		||||
project:
 | 
			
		||||
  name: sa-admin
 | 
			
		||||
  log-directory: /home/project/smartadmin/sit/log
 | 
			
		||||
  log-directory: /home/project/smartadmin/test/log
 | 
			
		||||
 | 
			
		||||
# 项目端口和url根路径
 | 
			
		||||
server:
 | 
			
		||||
@@ -19,27 +19,4 @@ server:
 | 
			
		||||
# 环境
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    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
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
@@ -88,8 +88,8 @@
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>mysql</groupId>
 | 
			
		||||
            <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
            <groupId>com.mysql</groupId>
 | 
			
		||||
            <artifactId>mysql-connector-j</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
@@ -263,6 +263,11 @@
 | 
			
		||||
            <artifactId>jackson-datatype-jsr310</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.fasterxml.jackson.dataformat</groupId>
 | 
			
		||||
            <artifactId>jackson-dataformat-yaml</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>net.1024lab</groupId>
 | 
			
		||||
            <artifactId>smartdb</artifactId>
 | 
			
		||||
@@ -278,6 +283,27 @@
 | 
			
		||||
            <artifactId>redisson-spring-data-27</artifactId>
 | 
			
		||||
        </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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 配置
 | 
			
		||||
 * nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
 | 
			
		||||
 *  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创新实验室-主任: 卓大
 | 
			
		||||
 * @Date 2020-03-25 22:54:46
 | 
			
		||||
@@ -43,7 +43,7 @@ public class SwaggerConfig {
 | 
			
		||||
    /**
 | 
			
		||||
     * 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
 | 
			
		||||
     */
 | 
			
		||||
    @Value("${springdoc.swagger-ui.server-base-url:''}")
 | 
			
		||||
    @Value("${springdoc.swagger-ui.server-base-url}")
 | 
			
		||||
    private String serverBaseUrl;
 | 
			
		||||
 | 
			
		||||
    public static final String[] SWAGGER_WHITELIST = {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,5 +23,7 @@ public class RedisKeyConst {
 | 
			
		||||
 | 
			
		||||
        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 DATA_MASKING = "业务支撑-数据脱敏";
 | 
			
		||||
 | 
			
		||||
        public static final String JOB = "业务支撑-定时任务";
 | 
			
		||||
 | 
			
		||||
        public static final String MESSAGE = "业务支撑-消息";
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,10 @@ public class CaptchaService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private DefaultKaptcha defaultKaptcha;
 | 
			
		||||
    @Autowired
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SystemEnvironment systemEnvironment;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private RedisService redisService;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ public enum ConfigKeyEnum implements BaseEnum {
 | 
			
		||||
     */
 | 
			
		||||
    SUPER_PASSWORD("super_password", "万能密码"),
 | 
			
		||||
 | 
			
		||||
    LEVEL3_PROTECT_CONFIG("level3_protect_config", "三级等保配置"),
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    private final String value;
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,8 @@ public class ConfigService {
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    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) {
 | 
			
		||||
        ConfigEntity entity = configDao.selectByKey(configAddDTO.getConfigKey());
 | 
			
		||||
    public ResponseDTO<String> add(ConfigAddForm configAddForm) {
 | 
			
		||||
        ConfigEntity entity = configDao.selectByKey(configAddForm.getConfigKey());
 | 
			
		||||
        if (null != entity) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.ALREADY_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        entity = SmartBeanUtil.copy(configAddDTO, ConfigEntity.class);
 | 
			
		||||
        entity = SmartBeanUtil.copy(configAddForm, ConfigEntity.class);
 | 
			
		||||
        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.FileVO;
 | 
			
		||||
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.lang3.StringUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
@@ -39,7 +39,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 * @Date 2019年10月11日 15:34:47
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class FileService {
 | 
			
		||||
@@ -58,9 +58,8 @@ public class FileService {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private RedisService redisService;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.servlet.multipart.max-file-size}")
 | 
			
		||||
    private String maxFileSize;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SecurityFileService securityFileService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 文件上传服务
 | 
			
		||||
@@ -89,11 +88,10 @@ public class FileService {
 | 
			
		||||
            return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验文件大小
 | 
			
		||||
        String maxSizeStr = maxFileSize.toLowerCase().replace("mb", "");
 | 
			
		||||
        long maxSize = Integer.parseInt(maxSizeStr) * 1024 * 1024L;
 | 
			
		||||
        if (file.getSize() > maxSize) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("上传文件最大为:" + maxSize);
 | 
			
		||||
        // 校验文件大小以及安全性
 | 
			
		||||
        ResponseDTO<String> validateFile = securityFileService.checkFile(file);
 | 
			
		||||
        if (!validateFile.getOk()) {
 | 
			
		||||
            return ResponseDTO.error(validateFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 进行上传
 | 
			
		||||
@@ -192,7 +190,7 @@ public class FileService {
 | 
			
		||||
 | 
			
		||||
        // 根据文件服务类 获取对应文件服务 查询 url
 | 
			
		||||
        ResponseDTO<FileDownloadVO> download = fileStorageService.download(fileKey);
 | 
			
		||||
        if(download.getOk()){
 | 
			
		||||
        if (download.getOk()) {
 | 
			
		||||
            download.getData().getMetadata().setFileName(fileVO.getFileName());
 | 
			
		||||
        }
 | 
			
		||||
        return download;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = "用户类型")
 | 
			
		||||
    private Integer operateUserType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "关键字:模块、操作内容")
 | 
			
		||||
    private String keywords;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "请求关键字:请求地址、请求方法、请求参数")
 | 
			
		||||
    private String requestKeywords;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "开始日期")
 | 
			
		||||
    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.LoginFailVO;
 | 
			
		||||
import org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
@@ -26,27 +25,18 @@ import java.util.List;
 | 
			
		||||
 * @Date 2023/10/11 19:25:59
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
public class ProtectLoginService {
 | 
			
		||||
public class SecurityLoginService {
 | 
			
		||||
 | 
			
		||||
    private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!";
 | 
			
		||||
 | 
			
		||||
    private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 连续登录失败次数则锁定,-1表示不受限制,可以一直登录
 | 
			
		||||
     */
 | 
			
		||||
    @Value("${classified-protect.login-max-fail-times}")
 | 
			
		||||
    private Integer loginMaxFailTimes;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 连续登录失败锁定时间(单位:秒),-1表示不锁定
 | 
			
		||||
     */
 | 
			
		||||
    @Value("${classified-protect.login-fail-locked-seconds}")
 | 
			
		||||
    private Integer loginFailLockedSeconds;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private Level3ProtectConfigService level3ProtectConfigService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private LoginFailDao loginFailDao;
 | 
			
		||||
@@ -61,8 +51,8 @@ public class ProtectLoginService {
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<LoginFailEntity> checkLogin(Long userId, UserTypeEnum userType) {
 | 
			
		||||
 | 
			
		||||
        // 无需校验
 | 
			
		||||
        if (loginMaxFailTimes < 1) {
 | 
			
		||||
        // 若登录最大失败次数小于1,无需校验
 | 
			
		||||
        if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
 | 
			
		||||
            return ResponseDTO.ok();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -72,19 +62,24 @@ public class ProtectLoginService {
 | 
			
		||||
            return ResponseDTO.ok();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验次数
 | 
			
		||||
        if (loginFailEntity.getLoginFailCount() < loginMaxFailTimes) {
 | 
			
		||||
        // 校验登录失败次数
 | 
			
		||||
        if (loginFailEntity.getLoginFailCount() < level3ProtectConfigService.getLoginFailMaxTimes()) {
 | 
			
		||||
            return ResponseDTO.ok(loginFailEntity);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验是否锁定
 | 
			
		||||
        if (loginFailEntity.getLoginLockBeginTime() == null) {
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds);
 | 
			
		||||
        return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime)));
 | 
			
		||||
        LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds());
 | 
			
		||||
        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) {
 | 
			
		||||
 | 
			
		||||
        // 无需校验
 | 
			
		||||
        if (loginMaxFailTimes < 1) {
 | 
			
		||||
        // 若登录最大失败次数小于1,无需记录
 | 
			
		||||
        if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 登录失败
 | 
			
		||||
        int loginFailCount = loginFailEntity == null ? 1 : loginFailEntity.getLoginFailCount() + 1;
 | 
			
		||||
        boolean lockFlag = loginFailCount >= level3ProtectConfigService.getLoginFailMaxTimes();
 | 
			
		||||
        LocalDateTime lockBeginTime = lockFlag ? LocalDateTime.now() : null;
 | 
			
		||||
 | 
			
		||||
        if (loginFailEntity == null) {
 | 
			
		||||
            loginFailEntity = LoginFailEntity.builder()
 | 
			
		||||
                    .userId(userId)
 | 
			
		||||
                    .userType(userType.getValue())
 | 
			
		||||
                    .loginName(loginName)
 | 
			
		||||
                    .loginFailCount(1)
 | 
			
		||||
                    .lockFlag(false)
 | 
			
		||||
                    .loginLockBeginTime(null).build();
 | 
			
		||||
                    .loginFailCount(loginFailCount)
 | 
			
		||||
                    .lockFlag(lockFlag)
 | 
			
		||||
                    .loginLockBeginTime(lockBeginTime)
 | 
			
		||||
                    .build();
 | 
			
		||||
            loginFailDao.insert(loginFailEntity);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            // 如果是已经锁定状态,则重新计算
 | 
			
		||||
            if(loginFailEntity.getLockFlag()){
 | 
			
		||||
                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.setLoginLockBeginTime(lockBeginTime);
 | 
			
		||||
            loginFailEntity.setLoginFailCount(loginFailCount);
 | 
			
		||||
            loginFailEntity.setLockFlag(lockFlag);
 | 
			
		||||
            loginFailEntity.setLoginName(loginName);
 | 
			
		||||
            loginFailDao.updateById(loginFailEntity);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 提示信息
 | 
			
		||||
        if (loginFailEntity.getLoginFailCount() >= loginMaxFailTimes) {
 | 
			
		||||
            LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(loginFailLockedSeconds);
 | 
			
		||||
            return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), loginFailLockedSeconds / 60, LocalDateTimeUtil.formatNormal(unlockTime));
 | 
			
		||||
        if (lockFlag) {
 | 
			
		||||
            LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds());
 | 
			
		||||
            return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime));
 | 
			
		||||
        } 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
 | 
			
		||||
     */
 | 
			
		||||
    public void removeLoginFail(Long userId, UserTypeEnum userType) {
 | 
			
		||||
        // 无需校验
 | 
			
		||||
        if (loginMaxFailTimes < 1) {
 | 
			
		||||
 | 
			
		||||
        // 若登录最大失败次数小于1,无需校验
 | 
			
		||||
        if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -160,8 +153,7 @@ public class ProtectLoginService {
 | 
			
		||||
    public PageResult<LoginFailVO> queryPage(LoginFailQueryForm queryForm) {
 | 
			
		||||
        Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
 | 
			
		||||
        List<LoginFailVO> list = loginFailDao.queryPage(page, queryForm);
 | 
			
		||||
        PageResult<LoginFailVO> pageResult = SmartPageUtil.convert2PageResult(page, list);
 | 
			
		||||
        return pageResult;
 | 
			
		||||
        return SmartPageUtil.convert2PageResult(page, list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -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:
 | 
			
		||||
  # 数据库连接信息
 | 
			
		||||
  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
 | 
			
		||||
    password: Zhuoda1024lab
 | 
			
		||||
    password: 11024Lab
 | 
			
		||||
    initial-size: 2
 | 
			
		||||
    min-idle: 2
 | 
			
		||||
    max-active: 10
 | 
			
		||||
@@ -22,10 +22,10 @@ spring:
 | 
			
		||||
 | 
			
		||||
  # redis 连接池配置信息
 | 
			
		||||
  redis:
 | 
			
		||||
    database: 1
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 6379
 | 
			
		||||
    password:
 | 
			
		||||
    database: 7
 | 
			
		||||
    host: 47.96.105.74
 | 
			
		||||
    port: 6666
 | 
			
		||||
    password: ASDasd123
 | 
			
		||||
    timeout: 10000ms
 | 
			
		||||
    lettuce:
 | 
			
		||||
      pool:
 | 
			
		||||
@@ -34,11 +34,22 @@ spring:
 | 
			
		||||
        max-idle: 3
 | 
			
		||||
        max-wait: 30000ms
 | 
			
		||||
 | 
			
		||||
  # 上传文件大小配置
 | 
			
		||||
  servlet:
 | 
			
		||||
    multipart:
 | 
			
		||||
      max-file-size: 30MB
 | 
			
		||||
      max-request-size: 30MB
 | 
			
		||||
  # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
 | 
			
		||||
  mail:
 | 
			
		||||
    host: smtp.163.com
 | 
			
		||||
    port: 465
 | 
			
		||||
    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序列化相关配置
 | 
			
		||||
  jackson:
 | 
			
		||||
@@ -76,8 +87,8 @@ file:
 | 
			
		||||
      region: oss-cn-hangzhou
 | 
			
		||||
      endpoint: oss-cn-hangzhou.aliyuncs.com
 | 
			
		||||
      bucket-name: 1024lab-smart-admin
 | 
			
		||||
      access-key:
 | 
			
		||||
      secret-key:
 | 
			
		||||
      access-key: LTAI5tBAbehjXWyAqLhc58e1
 | 
			
		||||
      secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
 | 
			
		||||
      url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
 | 
			
		||||
      private-url-expire-seconds: 3600
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +98,7 @@ springdoc:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
    doc-expansion: none  #关闭展开
 | 
			
		||||
    tags-sorter: alpha
 | 
			
		||||
    server-base-url:
 | 
			
		||||
  api-docs:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
knife4j:
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,12 @@
 | 
			
		||||
            <if test="query.userName != null and query.userName != ''">
 | 
			
		||||
                AND INSTR(operate_user_name,#{query.userName})
 | 
			
		||||
            </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">
 | 
			
		||||
                AND success_flag = #{query.successFlag}
 | 
			
		||||
            </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:
 | 
			
		||||
  # 数据库连接信息
 | 
			
		||||
  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
 | 
			
		||||
    password: Zhuoda1024lab
 | 
			
		||||
    password: 11024Lab
 | 
			
		||||
    initial-size: 2
 | 
			
		||||
    min-idle: 2
 | 
			
		||||
    max-active: 10
 | 
			
		||||
@@ -22,10 +22,10 @@ spring:
 | 
			
		||||
 | 
			
		||||
  # redis 连接池配置信息
 | 
			
		||||
  redis:
 | 
			
		||||
    database: 1
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 6379
 | 
			
		||||
    password:
 | 
			
		||||
    database: 7
 | 
			
		||||
    host: 47.96.105.74
 | 
			
		||||
    port: 6666
 | 
			
		||||
    password: ASDasd123
 | 
			
		||||
    timeout: 10000ms
 | 
			
		||||
    lettuce:
 | 
			
		||||
      pool:
 | 
			
		||||
@@ -34,11 +34,22 @@ spring:
 | 
			
		||||
        max-idle: 3
 | 
			
		||||
        max-wait: 30000ms
 | 
			
		||||
 | 
			
		||||
  # 上传文件大小配置
 | 
			
		||||
  servlet:
 | 
			
		||||
    multipart:
 | 
			
		||||
      max-file-size: 30MB
 | 
			
		||||
      max-request-size: 30MB
 | 
			
		||||
  # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
 | 
			
		||||
  mail:
 | 
			
		||||
    host: smtp.163.com
 | 
			
		||||
    port: 465
 | 
			
		||||
    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序列化相关配置
 | 
			
		||||
  jackson:
 | 
			
		||||
@@ -65,7 +76,6 @@ server:
 | 
			
		||||
      max-days: 7
 | 
			
		||||
      pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 文件上传 配置
 | 
			
		||||
file:
 | 
			
		||||
  storage:
 | 
			
		||||
@@ -77,24 +87,24 @@ file:
 | 
			
		||||
      region: oss-cn-hangzhou
 | 
			
		||||
      endpoint: oss-cn-hangzhou.aliyuncs.com
 | 
			
		||||
      bucket-name: 1024lab-smart-admin
 | 
			
		||||
      access-key:
 | 
			
		||||
      secret-key:
 | 
			
		||||
      access-key: LTAI5tBAbehjXWyAqLhc58e1
 | 
			
		||||
      secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
 | 
			
		||||
      url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
 | 
			
		||||
      private-url-expire-seconds: 3600
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# open api配置
 | 
			
		||||
springdoc:
 | 
			
		||||
  swagger-ui:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
    doc-expansion: none  #关闭展开
 | 
			
		||||
    tags-sorter: alpha
 | 
			
		||||
    server-base-url:
 | 
			
		||||
  api-docs:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
knife4j:
 | 
			
		||||
  enable: true
 | 
			
		||||
  basic:
 | 
			
		||||
    enable: true
 | 
			
		||||
    enable: false
 | 
			
		||||
    username: api # Basic认证用户名
 | 
			
		||||
    password: 1024 # Basic认证密码
 | 
			
		||||
 | 
			
		||||
@@ -112,11 +122,11 @@ access-control-allow-origin: '*'
 | 
			
		||||
 | 
			
		||||
# 心跳配置
 | 
			
		||||
heart-beat:
 | 
			
		||||
  interval-seconds: 60
 | 
			
		||||
  interval-seconds: 300
 | 
			
		||||
 | 
			
		||||
# 热加载配置
 | 
			
		||||
reload:
 | 
			
		||||
  interval-seconds: 60
 | 
			
		||||
  interval-seconds: 300
 | 
			
		||||
 | 
			
		||||
# sa-token 配置
 | 
			
		||||
sa-token:
 | 
			
		||||
@@ -135,9 +145,9 @@ sa-token:
 | 
			
		||||
  # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
 | 
			
		||||
  auto-renew: true
 | 
			
		||||
  # 是否输出操作日志
 | 
			
		||||
  is-log: false
 | 
			
		||||
  is-log: true
 | 
			
		||||
  # 日志等级(trace、debug、info、warn、error、fatal)
 | 
			
		||||
  log-level: warn
 | 
			
		||||
  log-level: debug
 | 
			
		||||
  # 启动时的字符画打印
 | 
			
		||||
  is-print: false
 | 
			
		||||
  # 是否从cookie读取token
 | 
			
		||||
 
 | 
			
		||||
@@ -33,12 +33,22 @@ spring:
 | 
			
		||||
        min-idle: 10
 | 
			
		||||
        max-idle: 50
 | 
			
		||||
        max-wait: 30000ms
 | 
			
		||||
 | 
			
		||||
  # 上传文件大小配置
 | 
			
		||||
  servlet:
 | 
			
		||||
    multipart:
 | 
			
		||||
      max-file-size: 30MB
 | 
			
		||||
      max-request-size: 30MB
 | 
			
		||||
  # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
 | 
			
		||||
  mail:
 | 
			
		||||
    host: smtp.163.com
 | 
			
		||||
    port: 465
 | 
			
		||||
    username: lab1024@163.com
 | 
			
		||||
    password: 1024lab
 | 
			
		||||
    properties:
 | 
			
		||||
      mail:
 | 
			
		||||
        smtp:
 | 
			
		||||
          auth: true
 | 
			
		||||
          ssl:
 | 
			
		||||
            enable: true
 | 
			
		||||
            socketFactory:
 | 
			
		||||
              class: com.sun.mail.util.MailSSLSocketFactory
 | 
			
		||||
              fallback: false
 | 
			
		||||
        debug: false
 | 
			
		||||
 | 
			
		||||
  # json序列化相关配置
 | 
			
		||||
  jackson:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
spring:
 | 
			
		||||
  # 数据库连接信息
 | 
			
		||||
  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
 | 
			
		||||
    password: Zhuoda1024lab
 | 
			
		||||
    password: 11024Lab
 | 
			
		||||
    initial-size: 2
 | 
			
		||||
    min-idle: 2
 | 
			
		||||
    max-active: 10
 | 
			
		||||
@@ -22,10 +22,10 @@ spring:
 | 
			
		||||
 | 
			
		||||
  # redis 连接池配置信息
 | 
			
		||||
  redis:
 | 
			
		||||
    database: 1
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 6379
 | 
			
		||||
    password:
 | 
			
		||||
    database: 7
 | 
			
		||||
    host: 47.96.105.74
 | 
			
		||||
    port: 6666
 | 
			
		||||
    password: ASDasd123
 | 
			
		||||
    timeout: 10000ms
 | 
			
		||||
    lettuce:
 | 
			
		||||
      pool:
 | 
			
		||||
@@ -34,11 +34,22 @@ spring:
 | 
			
		||||
        max-idle: 3
 | 
			
		||||
        max-wait: 30000ms
 | 
			
		||||
 | 
			
		||||
  # 上传文件大小配置
 | 
			
		||||
  servlet:
 | 
			
		||||
    multipart:
 | 
			
		||||
      max-file-size: 30MB
 | 
			
		||||
      max-request-size: 30MB
 | 
			
		||||
  # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
 | 
			
		||||
  mail:
 | 
			
		||||
    host: smtp.163.com
 | 
			
		||||
    port: 465
 | 
			
		||||
    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序列化相关配置
 | 
			
		||||
  jackson:
 | 
			
		||||
@@ -65,7 +76,6 @@ server:
 | 
			
		||||
      max-days: 7
 | 
			
		||||
      pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 文件上传 配置
 | 
			
		||||
file:
 | 
			
		||||
  storage:
 | 
			
		||||
@@ -77,25 +87,24 @@ file:
 | 
			
		||||
      region: oss-cn-hangzhou
 | 
			
		||||
      endpoint: oss-cn-hangzhou.aliyuncs.com
 | 
			
		||||
      bucket-name: 1024lab-smart-admin
 | 
			
		||||
      access-key:
 | 
			
		||||
      secret-key:
 | 
			
		||||
      access-key: LTAI5tBAbehjXWyAqLhc58e1
 | 
			
		||||
      secret-key: asX6ZWutaoTbQL3GxsFs24CmfAcYu3
 | 
			
		||||
      url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
 | 
			
		||||
      private-url-expire-seconds: 3600
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# open api配置
 | 
			
		||||
springdoc:
 | 
			
		||||
  swagger-ui:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
    doc-expansion: none  #关闭展开
 | 
			
		||||
    tags-sorter: alpha
 | 
			
		||||
    server-base-url: http://smartadmin.dev.1024lab.net/api/
 | 
			
		||||
    server-base-url:
 | 
			
		||||
  api-docs:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
knife4j:
 | 
			
		||||
  enable: true
 | 
			
		||||
  basic:
 | 
			
		||||
    enable: true
 | 
			
		||||
    enable: false
 | 
			
		||||
    username: api # Basic认证用户名
 | 
			
		||||
    password: 1024 # Basic认证密码
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
NODE_ENV=development
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { getRequest, postRequest } from '/src/lib/axios';
 | 
			
		||||
import { getRequest, postEncryptRequest, postRequest } from '/src/lib/axios';
 | 
			
		||||
 | 
			
		||||
export const employeeApi = {
 | 
			
		||||
  /**
 | 
			
		||||
@@ -72,11 +72,19 @@ export const employeeApi = {
 | 
			
		||||
    return getRequest(`/employee/update/password/reset/${employeeId}`);
 | 
			
		||||
  },
 | 
			
		||||
  /**
 | 
			
		||||
   * 修改面面
 | 
			
		||||
   * 修改密码
 | 
			
		||||
   */
 | 
			
		||||
  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: () => {
 | 
			
		||||
    return getRequest('/login/getLoginInfo');
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 获取邮箱登录验证码 @author 卓大
 | 
			
		||||
   */
 | 
			
		||||
  sendLoginEmailCode: (loginName) => {
 | 
			
		||||
    return getRequest(`/login/sendEmailCode/${loginName}`);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 获取双因子登录标识 @author 卓大
 | 
			
		||||
   */
 | 
			
		||||
  getTwoFactorLoginFlag: () => {
 | 
			
		||||
    return getRequest('/login/getTwoFactorLoginFlag');
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<!--
 | 
			
		||||
  *  表格列设置
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-08-26 23:45:51 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大
 | 
			
		||||
  * @Date:      2022-08-26 23:45:51
 | 
			
		||||
  * @Wechat:    zhuda1024
 | 
			
		||||
  * @Email:     lab1024@163.com
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
@@ -98,10 +98,12 @@
 | 
			
		||||
      //取消全屏
 | 
			
		||||
      exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
 | 
			
		||||
      fullScreenFlag.value = false;
 | 
			
		||||
      document.querySelector('#smartAdminPageTag').style.visibility = 'visible';
 | 
			
		||||
    } else {
 | 
			
		||||
      //全屏
 | 
			
		||||
      launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
 | 
			
		||||
      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)/),
 | 
			
		||||
  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, // 验证是否汉字
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<!--
 | 
			
		||||
  * 修改密码
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-09-06 20:02:01 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大
 | 
			
		||||
  * @Date:      2022-09-06 20:02:01
 | 
			
		||||
  * @Wechat:    zhuda1024
 | 
			
		||||
  * @Email:     lab1024@163.com
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <a-modal :open="visible" title="修改密码" ok-text="确认" cancel-text="取消" @ok="updatePwd" @cancel="cancelModal">
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
 | 
			
		||||
  const visible = ref(false);
 | 
			
		||||
  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 rules = {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,10 @@
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <DefaultTab v-if="pageTagStyle === PAGE_TAG_ENUM.DEFAULT.value" />
 | 
			
		||||
  <AntdTab v-if="pageTagStyle === PAGE_TAG_ENUM.ANTD.value" />
 | 
			
		||||
  <div id="smartAdminPageTag">
 | 
			
		||||
    <DefaultTab v-if="pageTagStyle === PAGE_TAG_ENUM.DEFAULT.value" />
 | 
			
		||||
    <AntdTab v-if="pageTagStyle === PAGE_TAG_ENUM.ANTD.value" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<!--
 | 
			
		||||
  *  layout 多种模式
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-09-06 20:40:16 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大
 | 
			
		||||
  * @Date:      2022-09-06 20:40:16
 | 
			
		||||
  * @Wechat:    zhuda1024
 | 
			
		||||
  * @Email:     lab1024@163.com
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <!--左侧菜单 模式-->
 | 
			
		||||
@@ -14,6 +14,8 @@
 | 
			
		||||
  <SideExpandLayout v-if="layout === LAYOUT_ENUM.SIDE_EXPAND.value" />
 | 
			
		||||
  <!--顶部菜单 模式-->
 | 
			
		||||
  <TopLayout v-if="layout === LAYOUT_ENUM.TOP.value" />
 | 
			
		||||
  <!--定期修改密码-->
 | 
			
		||||
  <RegularChangePasswordModal />
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { computed } from 'vue';
 | 
			
		||||
@@ -22,6 +24,7 @@
 | 
			
		||||
  import SideLayout from './side-layout.vue';
 | 
			
		||||
  import TopLayout from './top-layout.vue';
 | 
			
		||||
  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);
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import { nextTick } from 'vue';
 | 
			
		||||
import { createRouter, createWebHashHistory } from 'vue-router';
 | 
			
		||||
import { routerArray } from './routers';
 | 
			
		||||
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 { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
import { localClear, localRead } from '/@/utils/local-util';
 | 
			
		||||
@@ -33,7 +33,7 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
  nProgress.start();
 | 
			
		||||
 | 
			
		||||
  // 公共页面,任何时候都可以跳转
 | 
			
		||||
  if (to.path === PAGE_PATH_404 || to.path === PAGE_PATH_LOGIN) {
 | 
			
		||||
  if (to.path === PAGE_PATH_404) {
 | 
			
		||||
    next();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -42,7 +42,17 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
  const token = localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
  if (!token) {
 | 
			
		||||
    localClear();
 | 
			
		||||
    next({ path: PAGE_PATH_LOGIN });
 | 
			
		||||
    if (to.path === PAGE_PATH_LOGIN) {
 | 
			
		||||
      next();
 | 
			
		||||
    } else {
 | 
			
		||||
      next({ path: PAGE_PATH_LOGIN });
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 登录页,则跳转到首页
 | 
			
		||||
  if (to.path === PAGE_PATH_LOGIN) {
 | 
			
		||||
    next({ path: HOME_PAGE_PATH });
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,8 @@ export const useUserStore = defineStore({
 | 
			
		||||
    departmentId: '',
 | 
			
		||||
    //部门名词
 | 
			
		||||
    departmentName: '',
 | 
			
		||||
    //是否需要修改密码
 | 
			
		||||
    needUpdatePwdFlag: false,
 | 
			
		||||
    //是否为超级管理员
 | 
			
		||||
    administratorFlag: true,
 | 
			
		||||
    //上次登录ip
 | 
			
		||||
@@ -69,6 +71,9 @@ export const useUserStore = defineStore({
 | 
			
		||||
      }
 | 
			
		||||
      return localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
    },
 | 
			
		||||
    getNeedUpdatePwdFlag(state){
 | 
			
		||||
      return state.needUpdatePwdFlag;
 | 
			
		||||
    },
 | 
			
		||||
    //是否初始化了 路由
 | 
			
		||||
    getMenuRouterInitFlag(state) {
 | 
			
		||||
      return state.menuRouterInitFlag;
 | 
			
		||||
@@ -137,6 +142,7 @@ export const useUserStore = defineStore({
 | 
			
		||||
      this.phone = data.phone;
 | 
			
		||||
      this.departmentId = data.departmentId;
 | 
			
		||||
      this.departmentName = data.departmentName;
 | 
			
		||||
      this.needUpdatePwdFlag = data.needUpdatePwdFlag;
 | 
			
		||||
      this.administratorFlag = data.administratorFlag;
 | 
			
		||||
      this.lastLoginIp = data.lastLoginIp;
 | 
			
		||||
      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>
 | 
			
		||||
  <a-form class="smart-query-form" v-privilege="'support:operateLog:query'">
 | 
			
		||||
    <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-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 label="请求时间" class="smart-query-form-item">
 | 
			
		||||
@@ -168,6 +174,8 @@
 | 
			
		||||
 | 
			
		||||
  const queryFormState = {
 | 
			
		||||
    userName: '',
 | 
			
		||||
    requestKeywords: '',
 | 
			
		||||
    keywords: '',
 | 
			
		||||
    successFlag: undefined,
 | 
			
		||||
    startDate: undefined,
 | 
			
		||||
    endDate: undefined,
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,8 @@
 | 
			
		||||
    departmentId: undefined,
 | 
			
		||||
    // 是否启用
 | 
			
		||||
    disabledFlag: undefined,
 | 
			
		||||
    // 邮箱
 | 
			
		||||
    email: undefined,
 | 
			
		||||
    // 备注
 | 
			
		||||
    remark: '',
 | 
			
		||||
  };
 | 
			
		||||
@@ -126,6 +128,7 @@
 | 
			
		||||
      form.employeeId = data.employeeId;
 | 
			
		||||
      form.loginName = data.loginName;
 | 
			
		||||
      form.actualName = data.actualName;
 | 
			
		||||
      form.email = data.email;
 | 
			
		||||
      form.gender = data.gender;
 | 
			
		||||
      form.phone = data.phone;
 | 
			
		||||
      form.departmentId = data.departmentId;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,75 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="password-container">
 | 
			
		||||
    <!--  页面标题-->
 | 
			
		||||
    <div class="header-title">修改密码</div>
 | 
			
		||||
    <!--  内容区域-->
 | 
			
		||||
    <div class="password-form-area">
 | 
			
		||||
      <a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
 | 
			
		||||
        <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 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 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>
 | 
			
		||||
      <a-button type="primary" @click="onSubmit">修改密码</a-button>
 | 
			
		||||
      <a-button type="primary" style="margin: 20px 0 0 250px" @click="onSubmit">修改密码</a-button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { reactive, ref } from 'vue';
 | 
			
		||||
  import { computed, onMounted, reactive, ref } from 'vue';
 | 
			
		||||
  import { message } from 'ant-design-vue';
 | 
			
		||||
  import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
 | 
			
		||||
  import { employeeApi } from '/@/api/system/employee-api.js';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry.js';
 | 
			
		||||
 | 
			
		||||
  const formRef = ref();
 | 
			
		||||
  const tips = '密码长度8-20位且包含大写字母、小写字母、数字三种'; //校验规则
 | 
			
		||||
  const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
 | 
			
		||||
  const emits = defineEmits(['onSuccess']);
 | 
			
		||||
 | 
			
		||||
  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: '请输入原密码' }],
 | 
			
		||||
    newPassword: [{ 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 = {
 | 
			
		||||
    oldPassword: '',
 | 
			
		||||
@@ -56,9 +91,12 @@
 | 
			
		||||
        try {
 | 
			
		||||
          await employeeApi.updateEmployeePassword(form);
 | 
			
		||||
          message.success('修改成功');
 | 
			
		||||
 | 
			
		||||
          form.oldPassword = '';
 | 
			
		||||
          form.newPassword = '';
 | 
			
		||||
          form.confirmPwd = '';
 | 
			
		||||
 | 
			
		||||
          emits('onSuccess');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          smartSentry.captureError(error);
 | 
			
		||||
        } finally {
 | 
			
		||||
@@ -81,7 +119,7 @@
 | 
			
		||||
      margin-top: 30px;
 | 
			
		||||
 | 
			
		||||
      .form-item {
 | 
			
		||||
        width: 500px !important;
 | 
			
		||||
        width: 550px !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,9 @@
 | 
			
		||||
        <a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
 | 
			
		||||
        <p class="hint">初始密码默认为:随机</p>
 | 
			
		||||
      </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">
 | 
			
		||||
        <smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
@@ -143,6 +146,7 @@
 | 
			
		||||
    departmentId: [{ required: true, message: '部门不能为空' }],
 | 
			
		||||
    disabledFlag: [{ required: true, message: '状态不能为空' }],
 | 
			
		||||
    leaveFlag: [{ required: true, message: '在职状态不能为空' }],
 | 
			
		||||
    email: [{ required: true, message: '请输入邮箱' }],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // 校验表单
 | 
			
		||||
 
 | 
			
		||||
@@ -155,6 +155,11 @@
 | 
			
		||||
      dataIndex: 'phone',
 | 
			
		||||
      width: 85,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: '邮箱',
 | 
			
		||||
      dataIndex: 'email',
 | 
			
		||||
      width: 100,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: '超管',
 | 
			
		||||
      dataIndex: 'administratorFlag',
 | 
			
		||||
@@ -237,6 +242,9 @@
 | 
			
		||||
      params.pageNum = 1;
 | 
			
		||||
      params.departmentId = allDepartment ? undefined : props.departmentId;
 | 
			
		||||
      let res = await employeeApi.queryEmployee(params);
 | 
			
		||||
      for (const item of res.data.list) {
 | 
			
		||||
        item.roleNameList = _.join(item.roleNameList, ',');
 | 
			
		||||
      }
 | 
			
		||||
      tableData.value = res.data.list;
 | 
			
		||||
      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>
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<!--
 | 
			
		||||
  * 首页
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-09-12 22:34:00 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大
 | 
			
		||||
  * @Date:      2022-09-12 22:34:00
 | 
			
		||||
  * @Wechat:    zhuda1024
 | 
			
		||||
  * @Email:     lab1024@163.com
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
@@ -68,21 +68,17 @@
 | 
			
		||||
  import { computed } from 'vue';
 | 
			
		||||
  import HomeHeader from './home-header.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 ToBeDoneCard from './components/to-be-done-card.vue';
 | 
			
		||||
  import ChangelogCard from './components/changelog-card.vue';
 | 
			
		||||
  import Gauge from './components/echarts/gauge.vue';
 | 
			
		||||
  import Category from './components/echarts/category.vue';
 | 
			
		||||
  import Pie from './components/echarts/pie.vue';
 | 
			
		||||
  import Gradient from './components/echarts/gradient.vue';
 | 
			
		||||
  import { Modal } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
  // 业绩完成百分比
 | 
			
		||||
  const saleTargetPercent = computed(() => {
 | 
			
		||||
    return 75;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './index.less';
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .box-item {
 | 
			
		||||
    width: 444px;
 | 
			
		||||
    height: 570px;
 | 
			
		||||
    height: 600px;
 | 
			
		||||
    &.desc {
 | 
			
		||||
      background: #003b94;
 | 
			
		||||
      border-radius: 12px 0px 0px 12px;
 | 
			
		||||
@@ -148,6 +148,12 @@
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .code-btn{
 | 
			
		||||
      height: 44px;
 | 
			
		||||
      padding: 4px 5px;
 | 
			
		||||
      width: 108px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .eye-box {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      right: 15px;
 | 
			
		||||
 
 | 
			
		||||
@@ -59,12 +59,20 @@
 | 
			
		||||
        <a-form-item name="loginName">
 | 
			
		||||
          <a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
 | 
			
		||||
        </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-input-password
 | 
			
		||||
            v-model:value="loginForm.password"
 | 
			
		||||
            autocomplete="on"
 | 
			
		||||
            :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
            placeholder="请输入密码:至少三种字符,最小 8 位"
 | 
			
		||||
            placeholder="请输入密码"
 | 
			
		||||
          />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="captchaCode">
 | 
			
		||||
@@ -146,7 +154,7 @@
 | 
			
		||||
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    document.onkeyup = (e) => {
 | 
			
		||||
      if (e.keyCode == 13) {
 | 
			
		||||
      if (e.keyCode === 13) {
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
@@ -185,7 +193,7 @@
 | 
			
		||||
          password: encryptData(loginForm.password),
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        stopRefreshCaptchaInterval();
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
@@ -213,27 +221,78 @@
 | 
			
		||||
      let captchaResult = await loginApi.getCaptcha();
 | 
			
		||||
      captchaBase64Image.value = captchaResult.data.captchaBase64Image;
 | 
			
		||||
      loginForm.captchaUuid = captchaResult.data.captchaUuid;
 | 
			
		||||
      beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
      beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let refrestCaptchaInterval = null;
 | 
			
		||||
  function beginRefrestCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refrestCaptchaInterval === null) {
 | 
			
		||||
      refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
  let refreshCaptchaInterval = null;
 | 
			
		||||
  function beginRefreshCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refreshCaptchaInterval === null) {
 | 
			
		||||
      refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopRefrestCaptchaInterval() {
 | 
			
		||||
    if (refrestCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refrestCaptchaInterval);
 | 
			
		||||
      refrestCaptchaInterval = null;
 | 
			
		||||
  function stopRefreshCaptchaInterval() {
 | 
			
		||||
    if (refreshCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refreshCaptchaInterval);
 | 
			
		||||
      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>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './login.less';
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .box-item {
 | 
			
		||||
    width: 444px;
 | 
			
		||||
    height: 570px;
 | 
			
		||||
    height: 600px;
 | 
			
		||||
    &.desc {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 12px 0px 0px 12px;
 | 
			
		||||
@@ -135,6 +135,11 @@
 | 
			
		||||
      border: 1px solid #ededed;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
    .code-btn{
 | 
			
		||||
      height: 44px;
 | 
			
		||||
      padding: 4px 5px;
 | 
			
		||||
      width: 108px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .eye-box {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,12 +23,20 @@
 | 
			
		||||
        <a-form-item name="loginName">
 | 
			
		||||
          <a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
 | 
			
		||||
        </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-input-password
 | 
			
		||||
            v-model:value="loginForm.password"
 | 
			
		||||
            autocomplete="on"
 | 
			
		||||
            :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
            placeholder="请输入密码:至少三种字符,最小 8 位"
 | 
			
		||||
            placeholder="请输入密码"
 | 
			
		||||
          />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="captchaCode">
 | 
			
		||||
@@ -107,7 +115,7 @@
 | 
			
		||||
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    document.onkeyup = (e) => {
 | 
			
		||||
      if (e.keyCode == 13) {
 | 
			
		||||
      if (e.keyCode === 13) {
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
@@ -127,7 +135,7 @@
 | 
			
		||||
          password: encryptData(loginForm.password),
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        stopRefreshCaptchaInterval();
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
@@ -155,27 +163,78 @@
 | 
			
		||||
      let captchaResult = await loginApi.getCaptcha();
 | 
			
		||||
      captchaBase64Image.value = captchaResult.data.captchaBase64Image;
 | 
			
		||||
      loginForm.captchaUuid = captchaResult.data.captchaUuid;
 | 
			
		||||
      beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
      beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let refrestCaptchaInterval = null;
 | 
			
		||||
  function beginRefrestCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refrestCaptchaInterval === null) {
 | 
			
		||||
      refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
  let refreshCaptchaInterval = null;
 | 
			
		||||
  function beginRefreshCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refreshCaptchaInterval === null) {
 | 
			
		||||
      refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopRefrestCaptchaInterval() {
 | 
			
		||||
    if (refrestCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refrestCaptchaInterval);
 | 
			
		||||
      refrestCaptchaInterval = null;
 | 
			
		||||
  function stopRefreshCaptchaInterval() {
 | 
			
		||||
    if (refreshCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refreshCaptchaInterval);
 | 
			
		||||
      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>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './login.less';
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .box-item {
 | 
			
		||||
    width: 444px;
 | 
			
		||||
    height: 570px;
 | 
			
		||||
    height: 600px;
 | 
			
		||||
    &.desc {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 12px 0px 0px 12px;
 | 
			
		||||
@@ -143,7 +143,11 @@
 | 
			
		||||
      border: 1px solid #ededed;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .code-btn{
 | 
			
		||||
      height: 44px;
 | 
			
		||||
      padding: 4px 5px;
 | 
			
		||||
      width: 108px;
 | 
			
		||||
    }
 | 
			
		||||
    .eye-box {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      right: 15px;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,14 @@
 | 
			
		||||
        <a-form-item name="loginName">
 | 
			
		||||
          <a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
 | 
			
		||||
        </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-input-password
 | 
			
		||||
            v-model:value="loginForm.password"
 | 
			
		||||
@@ -109,7 +117,7 @@
 | 
			
		||||
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    document.onkeyup = (e) => {
 | 
			
		||||
      if (e.keyCode == 13) {
 | 
			
		||||
      if (e.keyCode === 13) {
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
@@ -129,7 +137,7 @@
 | 
			
		||||
          password: encryptData(loginForm.password),
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        stopRefreshCaptchaInterval();
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
@@ -157,27 +165,78 @@
 | 
			
		||||
      let captchaResult = await loginApi.getCaptcha();
 | 
			
		||||
      captchaBase64Image.value = captchaResult.data.captchaBase64Image;
 | 
			
		||||
      loginForm.captchaUuid = captchaResult.data.captchaUuid;
 | 
			
		||||
      beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
      beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let refrestCaptchaInterval = null;
 | 
			
		||||
  function beginRefrestCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refrestCaptchaInterval === null) {
 | 
			
		||||
      refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
  let refreshCaptchaInterval = null;
 | 
			
		||||
  function beginRefreshCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refreshCaptchaInterval === null) {
 | 
			
		||||
      refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopRefrestCaptchaInterval() {
 | 
			
		||||
    if (refrestCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refrestCaptchaInterval);
 | 
			
		||||
      refrestCaptchaInterval = null;
 | 
			
		||||
  function stopRefreshCaptchaInterval() {
 | 
			
		||||
    if (refreshCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refreshCaptchaInterval);
 | 
			
		||||
      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>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './login.less';
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user