mirror of
				https://gitee.com/lab1024/smart-admin.git
				synced 2025-11-04 18:33:43 +08:00 
			
		
		
		
	【V3.5.0】1、【新增】轻量级定时任务 SmartJob;2、【新增】站内信;3、【新增】个人中心;4、【新增】岗位管理;5、【优化】部门员工管理
This commit is contained in:
		@@ -36,6 +36,7 @@
 | 
			
		||||
        <commons-lang3.version>3.12.0</commons-lang3.version>
 | 
			
		||||
        <commons-collections4.version>4.4</commons-collections4.version>
 | 
			
		||||
        <commons-codec.version>1.13</commons-codec.version>
 | 
			
		||||
        <commons-text.version>1.9</commons-text.version>
 | 
			
		||||
        <xerces.version>2.12.0</xerces.version>
 | 
			
		||||
        <easy-excel.version>3.3.2</easy-excel.version>
 | 
			
		||||
        <poi.version>5.2.4</poi.version>
 | 
			
		||||
@@ -52,6 +53,7 @@
 | 
			
		||||
        <bcprov.version>1.59</bcprov.version>
 | 
			
		||||
        <jackson-datatype-jsr310.version>2.13.4</jackson-datatype-jsr310.version>
 | 
			
		||||
        <smartdb.version>1.2.0</smartdb.version>
 | 
			
		||||
        <redisson.version>3.25.0</redisson.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencyManagement>
 | 
			
		||||
@@ -193,6 +195,12 @@
 | 
			
		||||
                </exclusions>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.apache.commons</groupId>
 | 
			
		||||
                <artifactId>commons-text</artifactId>
 | 
			
		||||
                <version>${commons-text.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.apache.logging.log4j</groupId>
 | 
			
		||||
                <artifactId>log4j-spring-boot</artifactId>
 | 
			
		||||
@@ -306,6 +314,33 @@
 | 
			
		||||
                <artifactId>smartdb</artifactId>
 | 
			
		||||
                <version>${smartdb.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- redisson 排除和依赖data-27 为了springboot2.x 和 java8 -->
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.redisson</groupId>
 | 
			
		||||
                <artifactId>redisson-spring-boot-starter</artifactId>
 | 
			
		||||
                <version>${redisson.version}</version>
 | 
			
		||||
                <exclusions>
 | 
			
		||||
                    <exclusion>
 | 
			
		||||
                        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                        <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
                    </exclusion>
 | 
			
		||||
                    <exclusion>
 | 
			
		||||
                        <groupId>org.redisson</groupId>
 | 
			
		||||
                        <artifactId>redisson-spring-data-32</artifactId>
 | 
			
		||||
                    </exclusion>
 | 
			
		||||
                    <exclusion>
 | 
			
		||||
                        <artifactId>objenesis</artifactId>
 | 
			
		||||
                        <groupId>org.objenesis</groupId>
 | 
			
		||||
                    </exclusion>
 | 
			
		||||
                </exclusions>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.redisson</groupId>
 | 
			
		||||
                <artifactId>redisson-spring-data-27</artifactId>
 | 
			
		||||
                <version>${redisson.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
        </dependencies>
 | 
			
		||||
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ public class AdminSwaggerTagConst extends SwaggerTagConst {
 | 
			
		||||
        public static final String OA_INVOICE = "OA办公-发票信息";
 | 
			
		||||
 | 
			
		||||
        public static final String OA_NOTICE = "OA办公-通知公告";
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -48,6 +49,8 @@ public class AdminSwaggerTagConst extends SwaggerTagConst {
 | 
			
		||||
 | 
			
		||||
        public static final String SYSTEM_ROLE_MENU = "系统-角色-菜单";
 | 
			
		||||
 | 
			
		||||
        public static final String SYSTEM_POSITION = "系统-职务管理";
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package net.lab1024.sa.admin.module.system.department.domain.vo;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 部门
 | 
			
		||||
 *
 | 
			
		||||
@@ -33,4 +35,10 @@ public class DepartmentVO {
 | 
			
		||||
    @Schema(description = "排序")
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "更新时间")
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "创建时间")
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,20 @@ public class EmployeeController {
 | 
			
		||||
        return employeeService.updateEmployee(employeeUpdateForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "更新登录人信息 @author 善逸")
 | 
			
		||||
    @PostMapping("/employee/update/login")
 | 
			
		||||
    public ResponseDTO<String> updateByLogin(@Valid @RequestBody EmployeeUpdateForm employeeUpdateForm) {
 | 
			
		||||
        employeeUpdateForm.setEmployeeId(SmartRequestUtil.getRequestUserId());
 | 
			
		||||
        return employeeService.updateEmployee(employeeUpdateForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "更新登录人头像 @author 善逸")
 | 
			
		||||
    @PostMapping("/employee/update/avatar")
 | 
			
		||||
    public ResponseDTO<String> updateAvatar(@Valid @RequestBody EmployeeUpdateAvatarForm employeeUpdateAvatarForm) {
 | 
			
		||||
        employeeUpdateAvatarForm.setEmployeeId(SmartRequestUtil.getRequestUserId());
 | 
			
		||||
        return employeeService.updateAvatar(employeeUpdateAvatarForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "更新员工禁用/启用状态 @author 卓大")
 | 
			
		||||
    @GetMapping("/employee/update/disabled/{employeeId}")
 | 
			
		||||
    @SaCheckPermission("system:employee:disabled")
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,11 @@ public class EmployeeEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private String actualName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 头像
 | 
			
		||||
     */
 | 
			
		||||
    private String avatar;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 性别
 | 
			
		||||
     */
 | 
			
		||||
@@ -53,6 +58,11 @@ public class EmployeeEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private Long departmentId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 职务级别ID
 | 
			
		||||
     */
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否为超级管理员: 0 不是,1是
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -53,4 +53,12 @@ public class EmployeeAddForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "角色列表")
 | 
			
		||||
    private List<Long> roleIdList;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    @Length(max = 30, message = "备注最多200字符")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务级别ID")
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.employee.domain.form;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartVerificationUtil;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
import javax.validation.constraints.Pattern;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 修改登录人头像
 | 
			
		||||
 *
 | 
			
		||||
 * @Author 1024创新实验室: 善逸
 | 
			
		||||
 * @Date 2024年6月30日00:26:35
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class EmployeeUpdateAvatarForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private Long employeeId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "头像")
 | 
			
		||||
    @NotBlank(message = "头像不能为空哦")
 | 
			
		||||
    private String avatar;
 | 
			
		||||
}
 | 
			
		||||
@@ -55,4 +55,11 @@ public class EmployeeVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "角色名称列表")
 | 
			
		||||
    private List<String> roleNameList;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务ID")
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务名称")
 | 
			
		||||
    private String positionName;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,9 @@ import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.employee.domain.form.*;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.employee.manager.EmployeeManager;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.login.service.LoginService;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.dao.PositionDao;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.role.domain.vo.RoleEmployeeVO;
 | 
			
		||||
import net.lab1024.sa.base.common.code.UserErrorCode;
 | 
			
		||||
@@ -24,6 +27,7 @@ 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 org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
@@ -37,7 +41,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 EmployeeService {
 | 
			
		||||
@@ -62,6 +66,13 @@ public class EmployeeService {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectPasswordService protectPasswordService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    @Lazy
 | 
			
		||||
    private LoginService loginService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private PositionDao positionDao;
 | 
			
		||||
 | 
			
		||||
    public EmployeeEntity getById(Long employeeId) {
 | 
			
		||||
        return employeeDao.selectById(employeeId);
 | 
			
		||||
    }
 | 
			
		||||
@@ -69,7 +80,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询员工列表
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<PageResult<EmployeeVO>> queryEmployee(EmployeeQueryForm employeeQueryForm) {
 | 
			
		||||
        employeeQueryForm.setDeletedFlag(false);
 | 
			
		||||
@@ -86,16 +96,22 @@ public class EmployeeService {
 | 
			
		||||
            return ResponseDTO.ok(pageResult);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<Long> employeeIdList = employeeList.stream().map(EmployeeVO::getEmployeeId).collect(Collectors.toList());
 | 
			
		||||
        // 查询员工角色
 | 
			
		||||
        List<RoleEmployeeVO> roleEmployeeEntityList = roleEmployeeDao.selectRoleByEmployeeIdList(employeeIdList);
 | 
			
		||||
        List<Long> employeeIdList = employeeList.stream().map(EmployeeVO::getEmployeeId).collect(Collectors.toList());
 | 
			
		||||
        List<RoleEmployeeVO> roleEmployeeEntityList = employeeIdList.isEmpty() ? Collections.emptyList() : roleEmployeeDao.selectRoleByEmployeeIdList(employeeIdList);
 | 
			
		||||
        Map<Long, List<Long>> employeeRoleIdListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleId, Collectors.toList())));
 | 
			
		||||
        Map<Long, List<String>> employeeRoleNameListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleName, Collectors.toList())));
 | 
			
		||||
 | 
			
		||||
        // 查询员工职位
 | 
			
		||||
        List<Long> positionIdList = employeeList.stream().map(EmployeeVO::getPositionId).filter(Objects::nonNull).collect(Collectors.toList());
 | 
			
		||||
        List<PositionEntity> positionEntityList = positionIdList.isEmpty() ? Collections.emptyList() : positionDao.selectBatchIds(positionIdList);
 | 
			
		||||
        Map<Long, String> positionNameMap = positionEntityList.stream().collect(Collectors.toMap(PositionEntity::getPositionId, PositionEntity::getPositionName));
 | 
			
		||||
 | 
			
		||||
        employeeList.forEach(e -> {
 | 
			
		||||
            e.setRoleIdList(employeeRoleIdListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList()));
 | 
			
		||||
            e.setRoleNameList(employeeRoleNameListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList()));
 | 
			
		||||
            e.setDepartmentName(departmentService.getDepartmentPath(e.getDepartmentId()));
 | 
			
		||||
            e.setPositionName(positionNameMap.get(e.getPositionId()));
 | 
			
		||||
        });
 | 
			
		||||
        PageResult<EmployeeVO> pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList);
 | 
			
		||||
        return ResponseDTO.ok(pageResult);
 | 
			
		||||
@@ -103,7 +119,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized ResponseDTO<String> addEmployee(EmployeeAddForm employeeAddForm) {
 | 
			
		||||
        // 校验名称是否重复
 | 
			
		||||
@@ -143,7 +158,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized ResponseDTO<String> updateEmployee(EmployeeUpdateForm employeeUpdateForm) {
 | 
			
		||||
 | 
			
		||||
@@ -183,12 +197,38 @@ public class EmployeeService {
 | 
			
		||||
        // 更新数据
 | 
			
		||||
        employeeManager.updateEmployee(entity, employeeUpdateForm.getRoleIdList());
 | 
			
		||||
 | 
			
		||||
        // 清除员工缓存
 | 
			
		||||
        loginService.clearLoginEmployeeCache(employeeId);
 | 
			
		||||
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新登录人头像
 | 
			
		||||
     *
 | 
			
		||||
     * @param employeeUpdateAvatarForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> updateAvatar(EmployeeUpdateAvatarForm employeeUpdateAvatarForm) {
 | 
			
		||||
        Long employeeId = employeeUpdateAvatarForm.getEmployeeId();
 | 
			
		||||
        EmployeeEntity employeeEntity = employeeDao.selectById(employeeId);
 | 
			
		||||
        if (employeeEntity == null) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        // 更新头像
 | 
			
		||||
        EmployeeEntity updateEntity = new EmployeeEntity();
 | 
			
		||||
        updateEntity.setEmployeeId(employeeId);
 | 
			
		||||
        updateEntity.setAvatar(employeeUpdateAvatarForm.getAvatar());
 | 
			
		||||
        employeeDao.updateById(updateEntity);
 | 
			
		||||
 | 
			
		||||
        // 清除员工缓存
 | 
			
		||||
        loginService.clearLoginEmployeeCache(employeeId);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新禁用/启用状态
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> updateDisableFlag(Long employeeId) {
 | 
			
		||||
        if (null == employeeId) {
 | 
			
		||||
@@ -210,7 +250,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量删除员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> batchUpdateDeleteFlag(List<Long> employeeIdList) {
 | 
			
		||||
        if (CollectionUtils.isEmpty(employeeIdList)) {
 | 
			
		||||
@@ -239,7 +278,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量更新部门
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> batchUpdateDepartment(EmployeeBatchUpdateDepartmentForm batchUpdateDepartmentForm) {
 | 
			
		||||
        List<Long> employeeIdList = batchUpdateDepartmentForm.getEmployeeIdList();
 | 
			
		||||
@@ -262,7 +300,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新密码
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> updatePassword(EmployeeUpdatePasswordForm updatePasswordForm) {
 | 
			
		||||
        Long employeeId = updatePasswordForm.getEmployeeId();
 | 
			
		||||
@@ -299,7 +336,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取某个部门的员工信息
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<List<EmployeeVO>> getAllEmployeeByDepartmentId(Long departmentId, Boolean disabledFlag) {
 | 
			
		||||
        List<EmployeeEntity> employeeEntityList = employeeDao.selectByDepartmentId(departmentId, disabledFlag);
 | 
			
		||||
@@ -326,7 +362,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置密码
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> resetPassword(Integer employeeId) {
 | 
			
		||||
        String password = protectPasswordService.randomPassword();
 | 
			
		||||
@@ -336,7 +371,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 加密后 的密码
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public static String getEncryptPwd(String password) {
 | 
			
		||||
        return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password));
 | 
			
		||||
@@ -345,7 +379,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询全部员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<List<EmployeeVO>> queryAllEmployee(Boolean disabledFlag) {
 | 
			
		||||
        List<EmployeeVO> employeeList = employeeDao.selectEmployeeByDisabledAndDeleted(disabledFlag, Boolean.FALSE);
 | 
			
		||||
@@ -354,7 +387,6 @@ public class EmployeeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据登录名获取员工
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public EmployeeEntity getByLoginName(String loginName) {
 | 
			
		||||
        return employeeDao.getByLoginName(loginName, null);
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,9 @@ public class RequestEmployee implements RequestUser {
 | 
			
		||||
    @Schema(description = "员工名称")
 | 
			
		||||
    private String actualName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "头像")
 | 
			
		||||
    private String avatar;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(GenderEnum.class)
 | 
			
		||||
    private Integer gender;
 | 
			
		||||
 | 
			
		||||
@@ -43,9 +46,15 @@ public class RequestEmployee implements RequestUser {
 | 
			
		||||
    @Schema(description = "部门名称")
 | 
			
		||||
    private String departmentName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否禁用")
 | 
			
		||||
    private Boolean disabledFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否为超管")
 | 
			
		||||
    private Boolean administratorFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "请求ip")
 | 
			
		||||
    private String ip;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ 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;
 | 
			
		||||
import net.lab1024.sa.base.module.support.config.ConfigService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.file.service.IFileStorageService;
 | 
			
		||||
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;
 | 
			
		||||
@@ -39,6 +40,7 @@ import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO;
 | 
			
		||||
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 org.apache.commons.lang3.BooleanUtils;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
@@ -110,6 +112,9 @@ public class LoginService implements StpInterface {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ProtectPasswordService profectPasswordService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IFileStorageService fileStorageService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取验证码
 | 
			
		||||
     */
 | 
			
		||||
@@ -252,6 +257,15 @@ public class LoginService implements StpInterface {
 | 
			
		||||
        DepartmentVO department = departmentService.getDepartmentById(employeeEntity.getDepartmentId());
 | 
			
		||||
        requestEmployee.setDepartmentName(null == department ? StringConst.EMPTY : department.getName());
 | 
			
		||||
 | 
			
		||||
        // 头像信息
 | 
			
		||||
        String avatar = employeeEntity.getAvatar();
 | 
			
		||||
        if(StringUtils.isNotBlank(avatar)){
 | 
			
		||||
            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
 | 
			
		||||
            if(BooleanUtils.isTrue(getFileUrl.getOk())){
 | 
			
		||||
                requestEmployee.setAvatar(getFileUrl.getData());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return requestEmployee;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -341,6 +355,15 @@ public class LoginService implements StpInterface {
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清除员工登录缓存
 | 
			
		||||
     * @param employeeId
 | 
			
		||||
     */
 | 
			
		||||
    public void clearLoginEmployeeCache(Long employeeId){
 | 
			
		||||
        // 清空登录信息缓存
 | 
			
		||||
        loginEmployeeCache.remove(employeeId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存登录日志
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.controller;
 | 
			
		||||
 | 
			
		||||
import net.lab1024.sa.admin.constant.AdminSwaggerTagConst;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionAddForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionUpdateForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.service.PositionService;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ValidateList;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageResult;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 Controller
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_POSITION)
 | 
			
		||||
public class PositionController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private PositionService positionService;
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "分页查询 @author kaiyun")
 | 
			
		||||
    @PostMapping("/position/queryPage")
 | 
			
		||||
    public ResponseDTO<PageResult<PositionVO>> queryPage(@RequestBody @Valid PositionQueryForm queryForm) {
 | 
			
		||||
        return ResponseDTO.ok(positionService.queryPage(queryForm));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "添加 @author kaiyun")
 | 
			
		||||
    @PostMapping("/position/add")
 | 
			
		||||
    public ResponseDTO<String> add(@RequestBody @Valid PositionAddForm addForm) {
 | 
			
		||||
        return positionService.add(addForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "更新 @author kaiyun")
 | 
			
		||||
    @PostMapping("/position/update")
 | 
			
		||||
    public ResponseDTO<String> update(@RequestBody @Valid PositionUpdateForm updateForm) {
 | 
			
		||||
        return positionService.update(updateForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "批量删除 @author kaiyun")
 | 
			
		||||
    @PostMapping("/position/batchDelete")
 | 
			
		||||
    public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
 | 
			
		||||
        return positionService.batchDelete(idList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "单个删除 @author kaiyun")
 | 
			
		||||
    @GetMapping("/position/delete/{positionId}")
 | 
			
		||||
    public ResponseDTO<String> batchDelete(@PathVariable Long positionId) {
 | 
			
		||||
        return positionService.delete(positionId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "不分页查询 @author kaiyun")
 | 
			
		||||
    @GetMapping("/position/queryList")
 | 
			
		||||
    public ResponseDTO<List<PositionVO>> queryList() {
 | 
			
		||||
        return ResponseDTO.ok(positionService.queryList());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.dao;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO;
 | 
			
		||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 Dao
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Mapper
 | 
			
		||||
@Component
 | 
			
		||||
public interface PositionDao extends BaseMapper<PositionEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页 查询
 | 
			
		||||
     *
 | 
			
		||||
     * @param page
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    List<PositionVO> queryPage(Page page, @Param("queryForm") PositionQueryForm queryForm);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询
 | 
			
		||||
     * @param deletedFlag
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    List<PositionVO> queryList(@Param("deletedFlag") Boolean deletedFlag);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.domain.entity;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.IdType;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableId;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableName;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 实体类
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@TableName("t_position")
 | 
			
		||||
public class PositionEntity {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 职务ID
 | 
			
		||||
     */
 | 
			
		||||
    @TableId(type = IdType.AUTO)
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 职务名称
 | 
			
		||||
     */
 | 
			
		||||
    private String positionName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 职级
 | 
			
		||||
     */
 | 
			
		||||
    private String level;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 排序
 | 
			
		||||
     */
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 备注
 | 
			
		||||
     */
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    private Boolean deletedFlag;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.domain.form;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 新建表单
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class PositionAddForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务名称", requiredMode = Schema.RequiredMode.REQUIRED)
 | 
			
		||||
    @NotBlank(message = "职务名称 不能为空")
 | 
			
		||||
    private String positionName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职级")
 | 
			
		||||
    private String level;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "排序")
 | 
			
		||||
    @NotNull(message = "排序不能为空")
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.domain.form;
 | 
			
		||||
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageParam;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 分页查询表单
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class PositionQueryForm extends PageParam{
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "关键字查询")
 | 
			
		||||
    private String keywords;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private Boolean deletedFlag;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.domain.form;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 更新表单
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class PositionUpdateForm extends PositionAddForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务ID", requiredMode = Schema.RequiredMode.REQUIRED)
 | 
			
		||||
    @NotNull(message = "职务ID 不能为空")
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.domain.vo;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 列表VO
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class PositionVO {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务ID")
 | 
			
		||||
    private Long positionId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职务名称")
 | 
			
		||||
    private String positionName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "职级")
 | 
			
		||||
    private String level;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "排序")
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "创建时间")
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "更新时间")
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.manager;
 | 
			
		||||
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.dao.PositionDao;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表  Manager
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class PositionManager extends ServiceImpl<PositionDao, PositionEntity> {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.position.service;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.dao.PositionDao;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionAddForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.form.PositionUpdateForm;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageResult;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartBeanUtil;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartPageUtil;
 | 
			
		||||
import org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 Service
 | 
			
		||||
 *
 | 
			
		||||
 * @Author kaiyun
 | 
			
		||||
 * @Date 2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
public class PositionService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private PositionDao positionDao;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询
 | 
			
		||||
     *
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public PageResult<PositionVO> queryPage(PositionQueryForm queryForm) {
 | 
			
		||||
        queryForm.setDeletedFlag(Boolean.FALSE);
 | 
			
		||||
        Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
 | 
			
		||||
        List<PositionVO> list = positionDao.queryPage(page, queryForm);
 | 
			
		||||
        PageResult<PositionVO> pageResult = SmartPageUtil.convert2PageResult(page, list);
 | 
			
		||||
        return pageResult;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> add(PositionAddForm addForm) {
 | 
			
		||||
        PositionEntity positionEntity = SmartBeanUtil.copy(addForm, PositionEntity.class);
 | 
			
		||||
        positionDao.insert(positionEntity);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新
 | 
			
		||||
     *
 | 
			
		||||
     * @param updateForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> update(PositionUpdateForm updateForm) {
 | 
			
		||||
        PositionEntity positionEntity = SmartBeanUtil.copy(updateForm, PositionEntity.class);
 | 
			
		||||
        positionDao.updateById(positionEntity);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量删除
 | 
			
		||||
     *
 | 
			
		||||
     * @param idList
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> batchDelete(List<Long> idList) {
 | 
			
		||||
        if (CollectionUtils.isEmpty(idList)){
 | 
			
		||||
            return ResponseDTO.ok();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        positionDao.deleteBatchIds(idList);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 单个删除
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> delete(Long positionId) {
 | 
			
		||||
        if (null == positionId){
 | 
			
		||||
            return ResponseDTO.ok();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        positionDao.deleteById(positionId);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public List<PositionVO> queryList() {
 | 
			
		||||
        List<PositionVO> list = positionDao.queryList(Boolean.FALSE);
 | 
			
		||||
        return list;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,10 +2,10 @@ package net.lab1024.sa.admin.module.system.role.manager;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity;
 | 
			
		||||
import org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import net.lab1024.sa.base.common.controller.SupportBaseController;
 | 
			
		||||
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.util.SmartRequestUtil;
 | 
			
		||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.loginlog.LoginLogService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm;
 | 
			
		||||
@@ -39,5 +41,14 @@ public class AdminLoginLogController extends SupportBaseController {
 | 
			
		||||
        return loginLogService.queryByPage(queryForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "分页查询当前登录人信息 @author 善逸")
 | 
			
		||||
    @PostMapping("/loginLog/page/query/login")
 | 
			
		||||
    public ResponseDTO<PageResult<LoginLogVO>> queryByPageLogin(@RequestBody LoginLogQueryForm queryForm) {
 | 
			
		||||
        RequestUser requestUser = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        queryForm.setUserId(requestUser.getUserId());
 | 
			
		||||
        queryForm.setUserType(requestUser.getUserType().getValue());
 | 
			
		||||
        return loginLogService.queryByPage(queryForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import net.lab1024.sa.base.common.controller.SupportBaseController;
 | 
			
		||||
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.util.SmartRequestUtil;
 | 
			
		||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.operatelog.OperateLogService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm;
 | 
			
		||||
@@ -44,4 +46,13 @@ public class AdminOperateLogController extends SupportBaseController {
 | 
			
		||||
        return operateLogService.detail(operateLogId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "分页查询当前登录人信息 @author 善逸")
 | 
			
		||||
    @PostMapping("/operateLog/page/query/login")
 | 
			
		||||
    public ResponseDTO<PageResult<OperateLogVO>> queryByPageLogin(@RequestBody OperateLogQueryForm queryForm) {
 | 
			
		||||
        RequestUser requestUser = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        queryForm.setOperateUserId(requestUser.getUserId());
 | 
			
		||||
        queryForm.setOperateUserType(requestUser.getUserType().getValue());
 | 
			
		||||
        return operateLogService.queryByPage(queryForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
package net.lab1024.sa.admin.module.system.support;
 | 
			
		||||
 | 
			
		||||
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.RequestUser;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
 | 
			
		||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.SmartJobService;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.*;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration;
 | 
			
		||||
import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 管理接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 20:41
 | 
			
		||||
 */
 | 
			
		||||
@Tag(name = SwaggerTagConst.Support.JOB)
 | 
			
		||||
@RestController
 | 
			
		||||
@ConditionalOnBean(SmartJobAutoConfiguration.class)
 | 
			
		||||
public class AdminSmartJobController extends SupportBaseController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private SmartJobService jobService;
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-立即执行 @huke")
 | 
			
		||||
    @PostMapping("/job/execute")
 | 
			
		||||
    @RepeatSubmit
 | 
			
		||||
    public ResponseDTO<String> execute(@RequestBody @Valid SmartJobExecuteForm executeForm) {
 | 
			
		||||
        RequestUser requestUser = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        executeForm.setUpdateName(requestUser.getUserName());
 | 
			
		||||
        return jobService.execute(executeForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-查询详情 @huke")
 | 
			
		||||
    @GetMapping("/job/{jobId}")
 | 
			
		||||
    public ResponseDTO<SmartJobVO> queryJobInfo(@PathVariable Integer jobId) {
 | 
			
		||||
        return jobService.queryJobInfo(jobId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-分页查询 @huke")
 | 
			
		||||
    @PostMapping("/job/query")
 | 
			
		||||
    public ResponseDTO<PageResult<SmartJobVO>> queryJob(@RequestBody @Valid SmartJobQueryForm queryForm) {
 | 
			
		||||
        return jobService.queryJob(queryForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-更新-任务信息 @huke")
 | 
			
		||||
    @PostMapping("/job/update")
 | 
			
		||||
    @RepeatSubmit
 | 
			
		||||
    public ResponseDTO<String> updateJob(@RequestBody @Valid SmartJobUpdateForm updateForm) {
 | 
			
		||||
        RequestUser requestUser = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        updateForm.setUpdateName(requestUser.getUserName());
 | 
			
		||||
        return jobService.updateJob(updateForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-更新-开启状态 @huke")
 | 
			
		||||
    @PostMapping("/job/update/enabled")
 | 
			
		||||
    @RepeatSubmit
 | 
			
		||||
    public ResponseDTO<String> updateJobEnabled(@RequestBody @Valid SmartJobEnabledUpdateForm updateForm) {
 | 
			
		||||
        RequestUser requestUser = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        updateForm.setUpdateName(requestUser.getUserName());
 | 
			
		||||
        return jobService.updateJobEnabled(updateForm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "定时任务-执行记录-分页查询 @huke")
 | 
			
		||||
    @PostMapping("/job/log/query")
 | 
			
		||||
    public ResponseDTO<PageResult<SmartJobLogVO>> queryJobLog(@RequestBody @Valid SmartJobLogQueryForm queryForm) {
 | 
			
		||||
        return jobService.queryJobLog(queryForm);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
# 项目配置: 名称、日志目录
 | 
			
		||||
project:
 | 
			
		||||
  name: sa-admin
 | 
			
		||||
  log-directory: /home/logs/smart_admin_v3/${project.name}/${spring.profiles.active}
 | 
			
		||||
  log-directory: ${localPath:/home}/logs/smart_admin_v3/${project.name}/${spring.profiles.active}
 | 
			
		||||
 | 
			
		||||
# 项目端口和url根路径
 | 
			
		||||
server:
 | 
			
		||||
@@ -21,12 +21,6 @@ spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
 | 
			
		||||
# swagger文档
 | 
			
		||||
swagger:
 | 
			
		||||
  host: localhost:${server.port}
 | 
			
		||||
  tag-class: net.lab1024.sa.admin.constant.AdminSwaggerTagConst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
####################################### 安全等级保护 相关配置 ##################################################
 | 
			
		||||
#                                                                                                            #
 | 
			
		||||
# 建议开启 "三级等保" 所要求的配置,具体如下:                                                                     #
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
<?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.admin.module.system.position.dao.PositionDao">
 | 
			
		||||
 | 
			
		||||
    <!-- 分页查询 -->
 | 
			
		||||
    <select id="queryPage" resultType="net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO">
 | 
			
		||||
        SELECT
 | 
			
		||||
        *
 | 
			
		||||
        FROM t_position
 | 
			
		||||
        <where>
 | 
			
		||||
            deleted_flag = #{queryForm.deletedFlag}
 | 
			
		||||
            <!--关键字查询-->
 | 
			
		||||
            <if test="queryForm.keywords != null and queryForm.keywords != ''">
 | 
			
		||||
                AND INSTR(t_position.position_name,#{queryForm.keywords})
 | 
			
		||||
            </if>
 | 
			
		||||
        </where>
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <select id="queryList" resultType="net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO">
 | 
			
		||||
        SELECT *
 | 
			
		||||
        FROM t_position
 | 
			
		||||
        where deleted_flag = #{deletedFlag}
 | 
			
		||||
    </select>
 | 
			
		||||
</mapper>
 | 
			
		||||
@@ -21,12 +21,6 @@ spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
 | 
			
		||||
# swagger文档
 | 
			
		||||
swagger:
 | 
			
		||||
  host: localhost:${server.port}
 | 
			
		||||
  tag-class: net.lab1024.sa.admin.constant.AdminSwaggerTagConst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
####################################### 安全等级保护 相关配置 ##################################################
 | 
			
		||||
#                                                                                                            #
 | 
			
		||||
# 建议开启 "三级等保" 所要求的配置,具体如下:                                                                     #
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,6 @@ spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
 | 
			
		||||
# swagger文档
 | 
			
		||||
swagger:
 | 
			
		||||
  host: localhost:${server.port}
 | 
			
		||||
  tag-class: net.lab1024.sa.admin.constant.AdminSwaggerTagConst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
####################################### 安全等级保护 相关配置 ##################################################
 | 
			
		||||
#                                                                                                            #
 | 
			
		||||
# 建议开启 "三级等保" 所要求的配置,具体如下:                                                                     #
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@
 | 
			
		||||
# 项目配置: 名称、日志目录
 | 
			
		||||
project:
 | 
			
		||||
  name: sa-admin
 | 
			
		||||
  log-directory: /home/logs/smart_admin_v3/${project.name}/${spring.profiles.active}
 | 
			
		||||
  log-directory: /home/project/smartadmin/sit/log
 | 
			
		||||
 | 
			
		||||
# 项目端口和url根路径
 | 
			
		||||
server:
 | 
			
		||||
  port: 1024
 | 
			
		||||
  port: 11024
 | 
			
		||||
  servlet:
 | 
			
		||||
    context-path: /
 | 
			
		||||
 | 
			
		||||
@@ -21,12 +21,6 @@ spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: '@profiles.active@'
 | 
			
		||||
 | 
			
		||||
# swagger文档
 | 
			
		||||
swagger:
 | 
			
		||||
  host: localhost:${server.port}
 | 
			
		||||
  tag-class: net.lab1024.sa.admin.constant.AdminSwaggerTagConst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
####################################### 安全等级保护 相关配置 ##################################################
 | 
			
		||||
#                                                                                                            #
 | 
			
		||||
# 建议开启 "三级等保" 所要求的配置,具体如下:                                                                     #
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,11 @@
 | 
			
		||||
            <artifactId>commons-pool2</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.apache.commons</groupId>
 | 
			
		||||
            <artifactId>commons-text</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework</groupId>
 | 
			
		||||
            <artifactId>spring-mock</artifactId>
 | 
			
		||||
@@ -264,6 +269,14 @@
 | 
			
		||||
            <version>${smartdb.version}</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.redisson</groupId>
 | 
			
		||||
            <artifactId>redisson-spring-boot-starter</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.redisson</groupId>
 | 
			
		||||
            <artifactId>redisson-spring-data-27</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ public class DictValueVoSerializer extends JsonSerializer<String> {
 | 
			
		||||
        List<DictValueVO> dictValueVOList = Lists.newArrayList();
 | 
			
		||||
        valueCodeList.forEach(e->{
 | 
			
		||||
            if(StringUtils.isNotBlank(e)){
 | 
			
		||||
                DictValueVO dictValueVO = dictCacheService.selectValueByValueCode(value);
 | 
			
		||||
                DictValueVO dictValueVO = dictCacheService.selectValueByValueCode(e);
 | 
			
		||||
                if(dictValueVO != null){
 | 
			
		||||
                    dictValueVOList.add(dictValueVO);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package net.lab1024.sa.base.common.json.serializer.enumeration;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 | 
			
		||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 枚举类 序列化 注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024年6月29日
 | 
			
		||||
 */
 | 
			
		||||
@Target(ElementType.FIELD)
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@JacksonAnnotationsInside
 | 
			
		||||
@JsonSerialize(using = EnumSerializer.class, nullsUsing = EnumSerializer.class)
 | 
			
		||||
public @interface EnumSerialize {
 | 
			
		||||
 | 
			
		||||
    Class<? extends BaseEnum> value();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
package net.lab1024.sa.base.common.json.serializer.enumeration;
 | 
			
		||||
 | 
			
		||||
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.common.constant.StringConst;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartEnumUtil;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartStringUtil;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 枚举 序列化
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024年6月29日
 | 
			
		||||
 */
 | 
			
		||||
public class EnumSerializer extends JsonSerializer<Object> implements ContextualSerializer {
 | 
			
		||||
 | 
			
		||||
    private Class<? extends BaseEnum> enumClazz;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
 | 
			
		||||
        gen.writeObject(value);
 | 
			
		||||
        String fieldName = gen.getOutputContext().getCurrentName() + "Desc";
 | 
			
		||||
        Object desc;
 | 
			
		||||
        // 多个枚举类 逗号分割
 | 
			
		||||
        if (value instanceof String && String.valueOf(value).contains(StringConst.SEPARATOR)) {
 | 
			
		||||
            desc = SmartStringUtil.splitConvertToIntList(String.valueOf(value), StringConst.SEPARATOR)
 | 
			
		||||
                                  .stream().map(e -> SmartEnumUtil.getEnumDescByValue(e, enumClazz)).collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            BaseEnum anEnum = SmartEnumUtil.getEnumByValue(value, enumClazz);
 | 
			
		||||
            desc = null != anEnum ? anEnum.getDesc() : null;
 | 
			
		||||
        }
 | 
			
		||||
        gen.writeObjectField(fieldName, desc);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
 | 
			
		||||
        EnumSerialize annotation = property.getAnnotation(EnumSerialize.class);
 | 
			
		||||
        if (null == annotation) {
 | 
			
		||||
            return prov.findValueSerializer(property.getType(), property);
 | 
			
		||||
        }
 | 
			
		||||
        enumClazz = annotation.value();
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,8 +4,12 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.common.constant.StringConst;
 | 
			
		||||
import org.lionsoul.ip2region.xdb.Searcher;
 | 
			
		||||
 | 
			
		||||
import java.net.InetAddress;
 | 
			
		||||
import java.net.NetworkInterface;
 | 
			
		||||
import java.net.SocketException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Enumeration;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -81,4 +85,39 @@ public class SmartIpUtil {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取本机第一个ip
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static String getLocalFirstIp() {
 | 
			
		||||
        List<String> list = getLocalIp();
 | 
			
		||||
        return list.size() > 0 ? list.get(0) : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取本机ip
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<String> getLocalIp() {
 | 
			
		||||
        List<String> ipList = new ArrayList<>();
 | 
			
		||||
        try {
 | 
			
		||||
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
 | 
			
		||||
            while (networkInterfaces.hasMoreElements()) {
 | 
			
		||||
                NetworkInterface networkInterface = networkInterfaces.nextElement();
 | 
			
		||||
                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
 | 
			
		||||
                while (inetAddresses.hasMoreElements()) {
 | 
			
		||||
                    InetAddress inetAddress = inetAddresses.nextElement();
 | 
			
		||||
                    // 排除回环地址和IPv6地址
 | 
			
		||||
                    if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(StringConst.COLON)) {
 | 
			
		||||
                        ipList.add(inetAddress.getHostAddress());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (SocketException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        return ipList;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package net.lab1024.sa.base.config;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import io.swagger.v3.oas.models.Components;
 | 
			
		||||
import io.swagger.v3.oas.models.OpenAPI;
 | 
			
		||||
import io.swagger.v3.oas.models.info.Contact;
 | 
			
		||||
@@ -10,14 +11,25 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.common.constant.RequestHeaderConst;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SmartOperationCustomizer;
 | 
			
		||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
 | 
			
		||||
import org.springdoc.core.GroupedOpenApi;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.springdoc.core.*;
 | 
			
		||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
 | 
			
		||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
 | 
			
		||||
import org.springdoc.core.providers.JavadocProvider;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Conditional;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
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/;
 | 
			
		||||
 * }
 | 
			
		||||
 * @Author 1024创新实验室-主任: 卓大
 | 
			
		||||
 * @Date 2020-03-25 22:54:46
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
@@ -28,6 +40,11 @@ import org.springframework.context.annotation.Configuration;
 | 
			
		||||
@Configuration
 | 
			
		||||
@Conditional(SystemEnvironmentConfig.class)
 | 
			
		||||
public class SwaggerConfig {
 | 
			
		||||
    /**
 | 
			
		||||
     * 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
 | 
			
		||||
     */
 | 
			
		||||
    @Value("${springdoc.swagger-ui.server-base-url:''}")
 | 
			
		||||
    private String serverBaseUrl;
 | 
			
		||||
 | 
			
		||||
    public static final String[] SWAGGER_WHITELIST = {
 | 
			
		||||
            "/swagger-ui/**",
 | 
			
		||||
@@ -78,4 +95,37 @@ public class SwaggerConfig {
 | 
			
		||||
                .addOperationCustomizer(new SmartOperationCustomizer())
 | 
			
		||||
                .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl
 | 
			
		||||
     * 如果使用knife4j则不需要
 | 
			
		||||
     * @param openAPI
 | 
			
		||||
     * @param securityParser
 | 
			
		||||
     * @param springDocConfigProperties
 | 
			
		||||
     * @param propertyResolverUtils
 | 
			
		||||
     * @param openApiBuilderCustomizers
 | 
			
		||||
     * @param serverBaseUrlCustomizers
 | 
			
		||||
     * @param javadocProvider
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    @Bean
 | 
			
		||||
    public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
 | 
			
		||||
                                         SecurityService securityParser,
 | 
			
		||||
                                         SpringDocConfigProperties springDocConfigProperties,
 | 
			
		||||
                                         PropertyResolverUtils propertyResolverUtils,
 | 
			
		||||
                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
 | 
			
		||||
                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
 | 
			
		||||
                                         Optional<JavadocProvider> javadocProvider) {
 | 
			
		||||
        List<ServerBaseUrlCustomizer> list = Lists.newArrayList(new ServerBaseUrlCustomizer() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public String customize(String baseUrl) {
 | 
			
		||||
                if (StringUtils.isNotBlank(serverBaseUrl)) {
 | 
			
		||||
                    return serverBaseUrl;
 | 
			
		||||
                }
 | 
			
		||||
                return baseUrl;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,
 | 
			
		||||
                propertyResolverUtils, openApiBuilderCustomizers, Optional.of(list), javadocProvider);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,10 @@ public class SwaggerTagConst {
 | 
			
		||||
        public static final String TABLE_COLUMN = "业务支撑-列自定义";
 | 
			
		||||
 | 
			
		||||
        public static final String PROTECT = "业务支撑-网络安全";
 | 
			
		||||
 | 
			
		||||
        public static final String JOB = "业务支撑-定时任务";
 | 
			
		||||
 | 
			
		||||
        public static final String MESSAGE = "业务支撑-消息";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import net.lab1024.sa.base.common.domain.SystemEnvironment;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.exception.BusinessException;
 | 
			
		||||
import org.springframework.beans.TypeMismatchException;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.http.converter.HttpMessageNotReadableException;
 | 
			
		||||
import org.springframework.validation.BindException;
 | 
			
		||||
import org.springframework.validation.FieldError;
 | 
			
		||||
@@ -59,10 +58,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @ExceptionHandler({TypeMismatchException.class, BindException.class})
 | 
			
		||||
    public ResponseDTO<?> paramExceptionHandler(Exception e) {
 | 
			
		||||
        if (!systemEnvironment.isProd()) {
 | 
			
		||||
            log.error("全局参数异常,URL:{}", getCurrentRequestUrl(), e);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (e instanceof BindException) {
 | 
			
		||||
            if (e instanceof MethodArgumentNotValidException) {
 | 
			
		||||
                List<FieldError> fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
 | 
			
		||||
@@ -75,7 +70,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
            String errorMsg = UserErrorCode.PARAM_ERROR.getMsg() + ":" + error;
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.PARAM_ERROR, errorMsg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ResponseDTO.error(UserErrorCode.PARAM_ERROR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,14 +86,15 @@ public class WebServerListener implements ApplicationListener<WebServerInitializ
 | 
			
		||||
     * 初始化reload
 | 
			
		||||
     */
 | 
			
		||||
    private void initReload(WebServerApplicationContext applicationContext) {
 | 
			
		||||
        //将applicationContext转换为ConfigurableApplicationContext
 | 
			
		||||
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
 | 
			
		||||
 | 
			
		||||
        //获取BeanFactory
 | 
			
		||||
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
 | 
			
		||||
 | 
			
		||||
        //动态注册bean
 | 
			
		||||
        SmartReloadManager reloadManager = new SmartReloadManager(applicationContext.getBean(ReloadCommand.class), intervalSeconds);
 | 
			
		||||
        defaultListableBeanFactory.registerSingleton("smartReloadManager", reloadManager);
 | 
			
		||||
//        将applicationContext转换为ConfigurableApplicationContext
 | 
			
		||||
//        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
//        //获取BeanFactory
 | 
			
		||||
//        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
 | 
			
		||||
//
 | 
			
		||||
//        //动态注册bean
 | 
			
		||||
//        SmartReloadManager reloadManager = new SmartReloadManager(applicationContext.getBean(ReloadCommand.class), intervalSeconds);
 | 
			
		||||
//        defaultListableBeanFactory.registerSingleton("smartReloadManager", reloadManager);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -37,7 +37,7 @@ public class ConfigService {
 | 
			
		||||
    /**
 | 
			
		||||
     * 一个简单的系统配置缓存
 | 
			
		||||
     */
 | 
			
		||||
    private final ConcurrentHashMap<String, ConfigEntity> configCache = new ConcurrentHashMap<>();
 | 
			
		||||
    private final ConcurrentHashMap<String, ConfigEntity> CONFIG_CACHE = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ConfigDao configDao;
 | 
			
		||||
@@ -52,13 +52,13 @@ public class ConfigService {
 | 
			
		||||
     */
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    private void loadConfigCache() {
 | 
			
		||||
        configCache.clear();
 | 
			
		||||
        CONFIG_CACHE.clear();
 | 
			
		||||
        List<ConfigEntity> entityList = configDao.selectList(null);
 | 
			
		||||
        if (CollectionUtils.isEmpty(entityList)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        entityList.forEach(entity -> this.configCache.put(entity.getConfigKey().toLowerCase(), entity));
 | 
			
		||||
        log.info("################# 系统配置缓存初始化完毕:{} ###################", configCache.size());
 | 
			
		||||
        entityList.forEach(entity -> this.CONFIG_CACHE.put(entity.getConfigKey().toLowerCase(), entity));
 | 
			
		||||
        log.info("################# 系统配置缓存初始化完毕:{} ###################", CONFIG_CACHE.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -70,7 +70,7 @@ public class ConfigService {
 | 
			
		||||
        if (null == configEntity) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.configCache.put(configEntity.getConfigKey().toLowerCase(), configEntity);
 | 
			
		||||
        this.CONFIG_CACHE.put(configEntity.getConfigKey().toLowerCase(), configEntity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -100,7 +100,7 @@ public class ConfigService {
 | 
			
		||||
        if (StrUtil.isBlank(configKey)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        ConfigEntity entity = this.configCache.get(configKey.toLowerCase());
 | 
			
		||||
        ConfigEntity entity = this.CONFIG_CACHE.get(configKey.toLowerCase());
 | 
			
		||||
        return SmartBeanUtil.copy(entity, ConfigVO.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,164 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.IdUtil;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.SmartJobMsg;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJob;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJobExecutor;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import org.redisson.api.RLock;
 | 
			
		||||
import org.redisson.api.RTopic;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
import org.redisson.api.listener.MessageListener;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.PreDestroy;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * smart job 执行端管理
 | 
			
		||||
 * 分布式系统之间 用发布/订阅消息的形式 来管理多个job
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/22 20:31
 | 
			
		||||
 */
 | 
			
		||||
@ConditionalOnBean(SmartJobAutoConfiguration.class)
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartJobClientManager {
 | 
			
		||||
 | 
			
		||||
    private final SmartJobLauncher jobLauncher;
 | 
			
		||||
 | 
			
		||||
    private final SmartJobRepository jobRepository;
 | 
			
		||||
 | 
			
		||||
    private final List<SmartJob> jobInterfaceList;
 | 
			
		||||
 | 
			
		||||
    private static final String EXECUTE_LOCK = "smart-job-lock-msg-execute-";
 | 
			
		||||
 | 
			
		||||
    private static final String TOPIC = "smart-job-instance";
 | 
			
		||||
 | 
			
		||||
    private final RedissonClient redissonClient;
 | 
			
		||||
 | 
			
		||||
    private final RTopic topic;
 | 
			
		||||
 | 
			
		||||
    private final SmartJobMsgListener jobMsgListener;
 | 
			
		||||
 | 
			
		||||
    public SmartJobClientManager(SmartJobLauncher jobLauncher,
 | 
			
		||||
                                 SmartJobRepository jobRepository,
 | 
			
		||||
                                 List<SmartJob> jobInterfaceList,
 | 
			
		||||
                                 RedissonClient redissonClient) {
 | 
			
		||||
        this.jobLauncher = jobLauncher;
 | 
			
		||||
        this.jobRepository = jobRepository;
 | 
			
		||||
        this.jobInterfaceList = jobInterfaceList;
 | 
			
		||||
        this.redissonClient = redissonClient;
 | 
			
		||||
 | 
			
		||||
        // 添加监听器
 | 
			
		||||
        this.topic = redissonClient.getTopic(TOPIC);
 | 
			
		||||
        this.jobMsgListener = new SmartJobMsgListener();
 | 
			
		||||
        topic.addListener(SmartJobMsg.class, jobMsgListener);
 | 
			
		||||
        log.info("==== SmartJob ==== client-manager init");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发布消息
 | 
			
		||||
     */
 | 
			
		||||
    public void publishToClient(SmartJobMsg msgDTO) {
 | 
			
		||||
        msgDTO.setMsgId(IdUtil.fastSimpleUUID());
 | 
			
		||||
        topic.publish(msgDTO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理消息
 | 
			
		||||
     */
 | 
			
		||||
    private class SmartJobMsgListener implements MessageListener<SmartJobMsg> {
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onMessage(CharSequence channel, SmartJobMsg msg) {
 | 
			
		||||
            log.info("==== SmartJob ==== on-message :{}", msg);
 | 
			
		||||
            // 判断消息类型 业务简单就直接判断 复杂的话可以策略模式
 | 
			
		||||
            SmartJobMsg.MsgTypeEnum msgType = msg.getMsgType();
 | 
			
		||||
            // 更新任务
 | 
			
		||||
            if (SmartJobMsg.MsgTypeEnum.UPDATE_JOB == msgType) {
 | 
			
		||||
                updateJob(msg.getJobId());
 | 
			
		||||
            }
 | 
			
		||||
            // 执行任务
 | 
			
		||||
            if (SmartJobMsg.MsgTypeEnum.EXECUTE_JOB == msgType) {
 | 
			
		||||
                executeJob(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取任务执行类
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobClass
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    private Optional<SmartJob> queryJobImpl(String jobClass) {
 | 
			
		||||
        return jobInterfaceList.stream().filter(e -> Objects.equals(e.getClassName(), jobClass)).findFirst();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobId
 | 
			
		||||
     */
 | 
			
		||||
    private void updateJob(Integer jobId) {
 | 
			
		||||
        SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        jobLauncher.startOrRefreshJob(Lists.newArrayList(jobEntity));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 立即执行任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg
 | 
			
		||||
     */
 | 
			
		||||
    private void executeJob(SmartJobMsg msg) {
 | 
			
		||||
        Integer jobId = msg.getJobId();
 | 
			
		||||
        SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 获取定时任务实现类
 | 
			
		||||
        Optional<SmartJob> optional = this.queryJobImpl(jobEntity.getJobClass());
 | 
			
		||||
        if (!optional.isPresent()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取执行锁 无需主动释放
 | 
			
		||||
        RLock rLock = redissonClient.getLock(EXECUTE_LOCK + msg.getMsgId());
 | 
			
		||||
        try {
 | 
			
		||||
            boolean getLock = rLock.tryLock(0, 20, TimeUnit.SECONDS);
 | 
			
		||||
            if (!getLock) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            log.error("==== SmartJob ==== msg execute err:", e);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 通过执行器 执行任务
 | 
			
		||||
        jobEntity.setParam(msg.getParam());
 | 
			
		||||
        SmartJobExecutor jobExecutor = new SmartJobExecutor(jobEntity, jobRepository, optional.get(), redissonClient);
 | 
			
		||||
        jobExecutor.execute(msg.getUpdateName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PreDestroy
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
        topic.removeListener(jobMsgListener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,246 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import net.lab1024.sa.base.common.code.UserErrorCode;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageResult;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartBeanUtil;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartPageUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.*;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobDao;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobLogDao;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity;
 | 
			
		||||
import org.apache.commons.collections4.CollectionUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 接口业务管理
 | 
			
		||||
 * 如果不需要通过接口管理定时任务 可以删除此类
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 20:41
 | 
			
		||||
 */
 | 
			
		||||
@ConditionalOnBean(SmartJobAutoConfiguration.class)
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartJobService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmartJobDao jobDao;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmartJobLogDao jobLogDao;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmartJobClientManager jobClientManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询 定时任务详情
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobId
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<SmartJobVO> queryJobInfo(Integer jobId) {
 | 
			
		||||
        SmartJobEntity jobEntity = jobDao.selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        SmartJobVO jobVO = SmartBeanUtil.copy(jobEntity, SmartJobVO.class);
 | 
			
		||||
        // 处理设置job详情
 | 
			
		||||
        this.handleJobInfo(Lists.newArrayList(jobVO));
 | 
			
		||||
        return ResponseDTO.ok(jobVO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询 定时任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<PageResult<SmartJobVO>> queryJob(SmartJobQueryForm queryForm) {
 | 
			
		||||
        Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
 | 
			
		||||
        List<SmartJobVO> jobList = jobDao.query(page, queryForm);
 | 
			
		||||
        PageResult<SmartJobVO> pageResult = SmartPageUtil.convert2PageResult(page, jobList);
 | 
			
		||||
        // 处理设置job详情
 | 
			
		||||
        this.handleJobInfo(jobList);
 | 
			
		||||
        return ResponseDTO.ok(pageResult);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理设置 任务信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobList
 | 
			
		||||
     */
 | 
			
		||||
    private void handleJobInfo(List<SmartJobVO> jobList) {
 | 
			
		||||
        if (CollectionUtils.isEmpty(jobList)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 查询最后一次执行记录
 | 
			
		||||
        List<Long> logIdList = jobList.stream().map(SmartJobVO::getLastExecuteLogId).filter(Objects::nonNull).collect(Collectors.toList());
 | 
			
		||||
        Map<Long, SmartJobLogVO> lastLogMap = Collections.emptyMap();
 | 
			
		||||
        if (CollectionUtils.isNotEmpty(logIdList)) {
 | 
			
		||||
            lastLogMap = jobLogDao.selectBatchIds(logIdList)
 | 
			
		||||
                    .stream()
 | 
			
		||||
                    .collect(Collectors.toMap(SmartJobLogEntity::getLogId, e -> SmartBeanUtil.copy(e, SmartJobLogVO.class)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 循环处理任务信息
 | 
			
		||||
        for (SmartJobVO jobVO : jobList) {
 | 
			
		||||
            // 设置最后一次执行记录
 | 
			
		||||
            Long lastExecuteLogId = jobVO.getLastExecuteLogId();
 | 
			
		||||
            if (null != lastExecuteLogId) {
 | 
			
		||||
                jobVO.setLastJobLog(lastLogMap.get(lastExecuteLogId));
 | 
			
		||||
            }
 | 
			
		||||
            // 计算未来5次执行时间
 | 
			
		||||
            if (jobVO.getEnabledFlag()) {
 | 
			
		||||
                List<LocalDateTime> nextTimeList = SmartJobUtil.queryNextTimeFromNow(jobVO.getTriggerType(), jobVO.getTriggerValue(), jobVO.getLastExecuteTime(), 5);
 | 
			
		||||
                jobVO.setNextJobExecuteTimeList(nextTimeList);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询 定时任务-执行记录
 | 
			
		||||
     *
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<PageResult<SmartJobLogVO>> queryJobLog(SmartJobLogQueryForm queryForm) {
 | 
			
		||||
        Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
 | 
			
		||||
        List<SmartJobLogVO> jobList = jobLogDao.query(page, queryForm);
 | 
			
		||||
        PageResult<SmartJobLogVO> pageResult = SmartPageUtil.convert2PageResult(page, jobList);
 | 
			
		||||
        return ResponseDTO.ok(pageResult);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新定时任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param updateForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> updateJob(SmartJobUpdateForm updateForm) {
 | 
			
		||||
        // 校验参数
 | 
			
		||||
        Integer jobId = updateForm.getJobId();
 | 
			
		||||
        SmartJobEntity jobEntity = jobDao.selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验触发时间配置
 | 
			
		||||
        String triggerType = updateForm.getTriggerType();
 | 
			
		||||
        String triggerValue = updateForm.getTriggerValue();
 | 
			
		||||
        if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType) && !SmartJobUtil.checkCron(triggerValue)) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("cron表达式错误");
 | 
			
		||||
        }
 | 
			
		||||
        if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType) && !SmartJobUtil.checkFixedDelay(triggerValue)) {
 | 
			
		||||
            return ResponseDTO.userErrorParam("固定间隔错误:整数且大于0");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 更新数据
 | 
			
		||||
        jobEntity = SmartBeanUtil.copy(updateForm, SmartJobEntity.class);
 | 
			
		||||
        jobDao.updateById(jobEntity);
 | 
			
		||||
 | 
			
		||||
        // 更新执行端
 | 
			
		||||
        SmartJobMsg jobMsg = new SmartJobMsg();
 | 
			
		||||
        jobMsg.setJobId(jobId);
 | 
			
		||||
        jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB);
 | 
			
		||||
        jobMsg.setUpdateName(updateForm.getUpdateName());
 | 
			
		||||
        jobClientManager.publishToClient(jobMsg);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新定时任务-是否开启
 | 
			
		||||
     *
 | 
			
		||||
     * @param updateForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> updateJobEnabled(SmartJobEnabledUpdateForm updateForm) {
 | 
			
		||||
        Integer jobId = updateForm.getJobId();
 | 
			
		||||
        SmartJobEntity jobEntity = jobDao.selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
        Boolean enabledFlag = updateForm.getEnabledFlag();
 | 
			
		||||
        if (Objects.equals(enabledFlag, jobEntity.getEnabledFlag())) {
 | 
			
		||||
            return ResponseDTO.ok();
 | 
			
		||||
        }
 | 
			
		||||
        // 更新数据
 | 
			
		||||
        jobEntity = new SmartJobEntity();
 | 
			
		||||
        jobEntity.setJobId(jobId);
 | 
			
		||||
        jobEntity.setEnabledFlag(enabledFlag);
 | 
			
		||||
        jobEntity.setUpdateName(updateForm.getUpdateName());
 | 
			
		||||
        jobDao.updateById(jobEntity);
 | 
			
		||||
 | 
			
		||||
        // 更新执行端
 | 
			
		||||
        SmartJobMsg jobMsg = new SmartJobMsg();
 | 
			
		||||
        jobMsg.setJobId(jobId);
 | 
			
		||||
        jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB);
 | 
			
		||||
        jobMsg.setUpdateName(updateForm.getUpdateName());
 | 
			
		||||
        jobClientManager.publishToClient(jobMsg);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行定时任务
 | 
			
		||||
     * 忽略任务的开启状态,立即执行一次
 | 
			
		||||
     *
 | 
			
		||||
     * @param executeForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> execute(SmartJobExecuteForm executeForm) {
 | 
			
		||||
        Integer jobId = executeForm.getJobId();
 | 
			
		||||
        SmartJobEntity jobEntity = jobDao.selectById(jobId);
 | 
			
		||||
        if (null == jobEntity) {
 | 
			
		||||
            return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 更新执行端
 | 
			
		||||
        SmartJobMsg jobMsg = new SmartJobMsg();
 | 
			
		||||
        jobMsg.setJobId(jobId);
 | 
			
		||||
        jobMsg.setParam(executeForm.getParam());
 | 
			
		||||
        jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.EXECUTE_JOB);
 | 
			
		||||
        jobMsg.setUpdateName(executeForm.getUpdateName());
 | 
			
		||||
        jobClientManager.publishToClient(jobMsg);
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增定时任务
 | 
			
		||||
     * ps:目前没有业务场景需要通过接口 添加任务
 | 
			
		||||
     * 因为新增定时任务无论如何都需要 手动编码
 | 
			
		||||
     * 需要时手动给数据库增加一条就行
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     * @author huke
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> addJob() {
 | 
			
		||||
        return ResponseDTO.userErrorParam("暂未支持");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 移除定时任务
 | 
			
		||||
     * ps:目前没有业务场景需要通过接口移除,理由同 {@link SmartJobService#addJob},
 | 
			
		||||
     * 彻底移除始终都需要手动删除代码
 | 
			
		||||
     * 如果只是想暂停任务执行,可以调用 {@link SmartJobService#updateJobEnabled}
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     * @author huke
 | 
			
		||||
     */
 | 
			
		||||
    public ResponseDTO<String> delJob() {
 | 
			
		||||
        return ResponseDTO.userErrorParam("暂未支持");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务-更新-开启状态
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobEnabledUpdateForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id")
 | 
			
		||||
    @NotNull(message = "任务id不能为空")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否启用")
 | 
			
		||||
    @NotNull(message = "是否启用不能为空")
 | 
			
		||||
    private Boolean enabledFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private String updateName;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务-手动执行
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/18 20:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobExecuteForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id")
 | 
			
		||||
    @NotNull(message = "任务id不能为空")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "定时任务参数|可选")
 | 
			
		||||
    @Length(max = 2000, message = "定时任务参数最多2000字符")
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private String updateName;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageParam;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务-执行记录 分页查询
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 20:50
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobLogQueryForm extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "搜索词|可选")
 | 
			
		||||
    @Length(max = 50, message = "搜索词最多50字符")
 | 
			
		||||
    private String searchWord;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id|可选")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否成功|可选")
 | 
			
		||||
    private Boolean successFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "开始时间|可选", example = "2024-06-06")
 | 
			
		||||
    private LocalDate startTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "截止时间|可选", example = "2025-10-15")
 | 
			
		||||
    private LocalDate endTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务-执行记录 vo
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobLogVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "logId")
 | 
			
		||||
    private Long logId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务名称")
 | 
			
		||||
    private String jobName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "定时任务参数|可选")
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "执行结果是否成功")
 | 
			
		||||
    private Boolean successFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "开始执行时间")
 | 
			
		||||
    private LocalDateTime executeStartTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "执行时长-毫秒")
 | 
			
		||||
    private Long executeTimeMillis;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "执行结果描述")
 | 
			
		||||
    private String executeResult;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "执行结束时间")
 | 
			
		||||
    private LocalDateTime executeEndTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "ip")
 | 
			
		||||
    private String ip;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "进程id")
 | 
			
		||||
    private String processId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "程序目录")
 | 
			
		||||
    private String programPath;
 | 
			
		||||
 | 
			
		||||
    private String createName;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 发布/订阅消息对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/20 21:10
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobMsg {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息id 无需设置
 | 
			
		||||
     */
 | 
			
		||||
    private String msgId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务参数
 | 
			
		||||
     */
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型
 | 
			
		||||
     */
 | 
			
		||||
    private MsgTypeEnum msgType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新人
 | 
			
		||||
     */
 | 
			
		||||
    private String updateName;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @AllArgsConstructor
 | 
			
		||||
    public enum MsgTypeEnum implements BaseEnum {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 1 更新任务
 | 
			
		||||
         */
 | 
			
		||||
        UPDATE_JOB(1, "更新任务"),
 | 
			
		||||
 | 
			
		||||
        EXECUTE_JOB(2, "执行任务"),
 | 
			
		||||
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        private final Integer value;
 | 
			
		||||
 | 
			
		||||
        private final String desc;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageParam;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 分页查询
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 20:50
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobQueryForm extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "搜索词|可选")
 | 
			
		||||
    @Length(max = 50, message = "搜索词最多50字符")
 | 
			
		||||
    private String searchWord;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class)
 | 
			
		||||
    @CheckEnum(value = SmartJobTriggerTypeEnum.class, message = "触发类型错误")
 | 
			
		||||
    private String triggerType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否启用|可选")
 | 
			
		||||
    private Boolean enabledFlag;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 更新
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobUpdateForm {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id")
 | 
			
		||||
    @NotNull(message = "任务id不能为空")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务名称")
 | 
			
		||||
    @NotBlank(message = "任务名称不能为空")
 | 
			
		||||
    @Length(max = 100, message = "任务名称最多100字符")
 | 
			
		||||
    private String jobName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务执行类")
 | 
			
		||||
    @NotBlank(message = "任务执行类不能为空")
 | 
			
		||||
    @Length(max = 200, message = "任务执行类最多200字符")
 | 
			
		||||
    private String jobClass;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class)
 | 
			
		||||
    @CheckEnum(value = SmartJobTriggerTypeEnum.class, required = true, message = "触发类型错误")
 | 
			
		||||
    private String triggerType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "触发配置")
 | 
			
		||||
    @NotBlank(message = "触发配置不能为空")
 | 
			
		||||
    @Length(max = 100, message = "触发配置最多100字符")
 | 
			
		||||
    private String triggerValue;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "定时任务参数|可选")
 | 
			
		||||
    @Length(max = 1000, message = "定时任务参数最多1000字符")
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否开启")
 | 
			
		||||
    @NotNull(message = "是否开启不能为空")
 | 
			
		||||
    private Boolean enabledFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    @Length(max = 250, message = "任务备注最多250字符")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @NotNull(message = "排序不能为空")
 | 
			
		||||
    @Schema(description = "排序")
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private String updateName;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.api.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.json.serializer.enumeration.EnumSerialize;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 vo
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务id")
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "任务名称")
 | 
			
		||||
    private String jobName;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "执行类")
 | 
			
		||||
    private String jobClass;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class)
 | 
			
		||||
    @EnumSerialize(SmartJobTriggerTypeEnum.class)
 | 
			
		||||
    private String triggerType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "触发配置")
 | 
			
		||||
    private String triggerValue;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "定时任务参数|可选")
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否启用")
 | 
			
		||||
    private Boolean enabledFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "最后一执行时间")
 | 
			
		||||
    private LocalDateTime lastExecuteTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "最后一次执行记录id")
 | 
			
		||||
    private Long lastExecuteLogId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "备注")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "排序")
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    private String updateName;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "上次执行记录")
 | 
			
		||||
    private SmartJobLogVO lastJobLog;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "未来N次任务执行时间")
 | 
			
		||||
    private List<LocalDateTime> nextJobExecuteTimeList;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.config;
 | 
			
		||||
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJob;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 配置
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
@EnableConfigurationProperties(SmartJobConfig.class)
 | 
			
		||||
@ConditionalOnProperty(
 | 
			
		||||
        prefix = SmartJobConfig.CONFIG_PREFIX,
 | 
			
		||||
        name = "enabled",
 | 
			
		||||
        havingValue = "true"
 | 
			
		||||
)
 | 
			
		||||
public class SmartJobAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
    private final SmartJobConfig jobConfig;
 | 
			
		||||
 | 
			
		||||
    private final SmartJobRepository jobRepository;
 | 
			
		||||
 | 
			
		||||
    private final List<SmartJob> jobInterfaceList;
 | 
			
		||||
 | 
			
		||||
    public SmartJobAutoConfiguration(SmartJobConfig jobConfig,
 | 
			
		||||
                                     SmartJobRepository jobRepository,
 | 
			
		||||
                                     List<SmartJob> jobInterfaceList) {
 | 
			
		||||
        this.jobConfig = jobConfig;
 | 
			
		||||
        this.jobRepository = jobRepository;
 | 
			
		||||
        this.jobInterfaceList = jobInterfaceList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务启动器
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    @Bean
 | 
			
		||||
    public SmartJobLauncher initJobLauncher(RedissonClient redissonClient) {
 | 
			
		||||
        return new SmartJobLauncher(jobConfig, jobRepository, jobInterfaceList, redissonClient);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.config;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * smart job 配置
 | 
			
		||||
 * 与配置文件参数对应
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@ConfigurationProperties(prefix = SmartJobConfig.CONFIG_PREFIX)
 | 
			
		||||
@Data
 | 
			
		||||
public class SmartJobConfig {
 | 
			
		||||
 | 
			
		||||
    public static final String CONFIG_PREFIX = "smart.job";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务执行核心线程数 偶数 默认2
 | 
			
		||||
     */
 | 
			
		||||
    private Integer corePoolSize = 2;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务延迟初始化 默认30秒
 | 
			
		||||
     */
 | 
			
		||||
    private Integer initDelay = 30;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据库配置检测-开关 默认开启
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean dbRefreshEnabled = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据库配置检测-执行间隔 默认120秒
 | 
			
		||||
     */
 | 
			
		||||
    private Integer dbRefreshInterval = 120;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.constant;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * smart job 常量
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/19 20:25
 | 
			
		||||
 */
 | 
			
		||||
public class SmartJobConst {
 | 
			
		||||
 | 
			
		||||
    public static final String SYSTEM_NAME = "system";
 | 
			
		||||
 | 
			
		||||
    public static final String LOGO = "   _____                      __         __      __  \n" +
 | 
			
		||||
                                      "  / ___/____ ___  ____ ______/ /_       / /___  / /_ \n" +
 | 
			
		||||
                                      "  \\__ \\/ __ `__ \\/ __ `/ ___/ __/  __  / / __ \\/ __ \\\n" +
 | 
			
		||||
                                      " ___/ / / / / / / /_/ / /  / /_   / /_/ / /_/ / /_/ /\n" +
 | 
			
		||||
                                      "/____/_/ /_/ /_/\\__,_/_/   \\__/   \\____/\\____/_.___/ \n" +
 | 
			
		||||
                                      "-->任务执行线程池:%s\n" +
 | 
			
		||||
                                      "-->任务初始化延迟:%s秒\n" +
 | 
			
		||||
                                      "-->数据库配置检测:%s\n\n";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.constant;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * job 任务触发类型 枚举类
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024年6月29日
 | 
			
		||||
 **/
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@Getter
 | 
			
		||||
public enum SmartJobTriggerTypeEnum implements BaseEnum {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1 cron表达式
 | 
			
		||||
     */
 | 
			
		||||
    CRON("cron", "cron表达式"),
 | 
			
		||||
 | 
			
		||||
    FIXED_DELAY("fixed_delay", "固定间隔"),
 | 
			
		||||
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    private final String value;
 | 
			
		||||
 | 
			
		||||
    private final String desc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,188 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.constant;
 | 
			
		||||
 | 
			
		||||
import org.springframework.scheduling.support.CronExpression;
 | 
			
		||||
 | 
			
		||||
import java.lang.management.ManagementFactory;
 | 
			
		||||
import java.lang.management.RuntimeMXBean;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * smart job util
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/18 20:00
 | 
			
		||||
 */
 | 
			
		||||
public class SmartJobUtil {
 | 
			
		||||
 | 
			
		||||
    private SmartJobUtil() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验cron表达式 是否合法
 | 
			
		||||
     *
 | 
			
		||||
     * @param cron
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean checkCron(String cron) {
 | 
			
		||||
        return CronExpression.isValidExpression(cron);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验固定间隔 值是否合法
 | 
			
		||||
     *
 | 
			
		||||
     * @param val
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean checkFixedDelay(String val) {
 | 
			
		||||
        int intVal;
 | 
			
		||||
        try {
 | 
			
		||||
            intVal = Integer.parseInt(val);
 | 
			
		||||
        } catch (NumberFormatException e) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return intVal > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打印一些展示信息到控制台
 | 
			
		||||
     * 环保绿
 | 
			
		||||
     *
 | 
			
		||||
     * @param info
 | 
			
		||||
     */
 | 
			
		||||
    public static void printInfo(String info) {
 | 
			
		||||
        System.out.printf("\033[32;1m %s \033[0m", info);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询未来N次执行时间 从最后一次时间时间 开始计算
 | 
			
		||||
     *
 | 
			
		||||
     * @param triggerType
 | 
			
		||||
     * @param triggerVal
 | 
			
		||||
     * @param lastExecuteTime
 | 
			
		||||
     * @param num
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<LocalDateTime> queryNextTimeFromLast(String triggerType,
 | 
			
		||||
                                                            String triggerVal,
 | 
			
		||||
                                                            LocalDateTime lastExecuteTime,
 | 
			
		||||
                                                            int num) {
 | 
			
		||||
        List<LocalDateTime> nextTimeList = null;
 | 
			
		||||
        if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) {
 | 
			
		||||
            nextTimeList = SmartJobUtil.queryNextTime(triggerVal, lastExecuteTime, num);
 | 
			
		||||
        } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) {
 | 
			
		||||
            nextTimeList = SmartJobUtil.queryNextTime(getFixedDelayVal(triggerVal), lastExecuteTime, num);
 | 
			
		||||
        }
 | 
			
		||||
        return nextTimeList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询未来N次执行时间 从当前时间 开始计算
 | 
			
		||||
     *
 | 
			
		||||
     * @param triggerType
 | 
			
		||||
     * @param triggerVal
 | 
			
		||||
     * @param lastExecuteTime
 | 
			
		||||
     * @param num
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<LocalDateTime> queryNextTimeFromNow(String triggerType,
 | 
			
		||||
                                                           String triggerVal,
 | 
			
		||||
                                                           LocalDateTime lastExecuteTime,
 | 
			
		||||
                                                           int num) {
 | 
			
		||||
        LocalDateTime nowTime = LocalDateTime.now();
 | 
			
		||||
        List<LocalDateTime> nextTimeList = null;
 | 
			
		||||
        if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) {
 | 
			
		||||
            nextTimeList = SmartJobUtil.queryNextTime(triggerVal, nowTime, num);
 | 
			
		||||
        } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) {
 | 
			
		||||
            Integer fixedDelay = getFixedDelayVal(triggerVal);
 | 
			
		||||
            LocalDateTime startTime = null == lastExecuteTime || lastExecuteTime.plusSeconds(fixedDelay).isBefore(nowTime)
 | 
			
		||||
                                      ? nowTime : lastExecuteTime;
 | 
			
		||||
            nextTimeList = SmartJobUtil.queryNextTime(fixedDelay, startTime, num);
 | 
			
		||||
        }
 | 
			
		||||
        return nextTimeList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据cron表达式 计算N次执行时间
 | 
			
		||||
     *
 | 
			
		||||
     * @param cron
 | 
			
		||||
     * @param startTime
 | 
			
		||||
     * @param num
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<LocalDateTime> queryNextTime(String cron, LocalDateTime startTime, int num) {
 | 
			
		||||
        if (null == startTime) {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
        }
 | 
			
		||||
        CronExpression parse = CronExpression.parse(cron);
 | 
			
		||||
        List<LocalDateTime> timeList = new ArrayList<>(num);
 | 
			
		||||
        for (int i = 0; i < num; i++) {
 | 
			
		||||
            startTime = parse.next(startTime);
 | 
			
		||||
            timeList.add(startTime);
 | 
			
		||||
        }
 | 
			
		||||
        return timeList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据 固定间隔 计算N次执行时间
 | 
			
		||||
     *
 | 
			
		||||
     * @param fixDelaySecond
 | 
			
		||||
     * @param startTime
 | 
			
		||||
     * @param num
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<LocalDateTime> queryNextTime(Integer fixDelaySecond, LocalDateTime startTime, int num) {
 | 
			
		||||
        if (null == startTime) {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
        }
 | 
			
		||||
        List<LocalDateTime> timeList = new ArrayList<>(num);
 | 
			
		||||
        for (int i = 0; i < num; i++) {
 | 
			
		||||
            startTime = startTime.plusSeconds(fixDelaySecond);
 | 
			
		||||
            timeList.add(startTime);
 | 
			
		||||
        }
 | 
			
		||||
        return timeList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取固定间隔时间
 | 
			
		||||
     *
 | 
			
		||||
     * @param val
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static Integer getFixedDelayVal(String val) {
 | 
			
		||||
        return Integer.parseInt(val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前 Java 应用程序的工作目录
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static String getProgramPath() {
 | 
			
		||||
        return System.getProperty("user.dir");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前 Java 应用程序的进程id
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static String getProcessId() {
 | 
			
		||||
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
 | 
			
		||||
        return runtime.getName().split("@")[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        LocalDateTime startTime = LocalDateTime.now();
 | 
			
		||||
        List<LocalDateTime> timeList = SmartJobUtil.queryNextTime("5 * * * * *", startTime, 3);
 | 
			
		||||
        System.out.println(timeList);
 | 
			
		||||
 | 
			
		||||
        timeList = SmartJobUtil.queryNextTime(10, startTime, 3);
 | 
			
		||||
        System.out.println(timeList);
 | 
			
		||||
 | 
			
		||||
        System.out.println("project path ->" + getProgramPath());
 | 
			
		||||
        System.out.println("project process id ->" + getProcessId());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.core;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 执行接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
public interface SmartJob {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认方法
 | 
			
		||||
     * 获取当前任务类名
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    default String getClassName() {
 | 
			
		||||
        return this.getClass().getName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行定时任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param param 可选参数 任务不需要时不用管
 | 
			
		||||
     * @return 可null, 自行组织语言描述执行结果,例如:本次处理数据N条 等
 | 
			
		||||
     */
 | 
			
		||||
    String run(String param);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,147 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.core;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.exceptions.ExceptionUtil;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartIpUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity;
 | 
			
		||||
import org.redisson.api.RLock;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
import org.springframework.util.StopWatch;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.temporal.ChronoUnit;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 执行器
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class SmartJobExecutor implements Runnable {
 | 
			
		||||
 | 
			
		||||
    private final SmartJobEntity jobEntity;
 | 
			
		||||
 | 
			
		||||
    private final SmartJobRepository jobRepository;
 | 
			
		||||
 | 
			
		||||
    private final SmartJob jobInterface;
 | 
			
		||||
 | 
			
		||||
    private final RedissonClient redissonClient;
 | 
			
		||||
 | 
			
		||||
    private static final String EXECUTE_LOCK = "smart-job-lock-execute-";
 | 
			
		||||
 | 
			
		||||
    public SmartJobExecutor(SmartJobEntity jobEntity,
 | 
			
		||||
                            SmartJobRepository jobRepository,
 | 
			
		||||
                            SmartJob jobInterface,
 | 
			
		||||
                            RedissonClient redissonClient) {
 | 
			
		||||
        this.jobEntity = jobEntity;
 | 
			
		||||
        this.jobRepository = jobRepository;
 | 
			
		||||
        this.jobInterface = jobInterface;
 | 
			
		||||
        this.redissonClient = redissonClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 系统线程执行
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
        // 获取当前任务执行锁 最多持有30s自动释放
 | 
			
		||||
        Integer jobId = jobEntity.getJobId();
 | 
			
		||||
        RLock rLock = redissonClient.getLock(EXECUTE_LOCK + jobId);
 | 
			
		||||
        try {
 | 
			
		||||
            boolean lock = rLock.tryLock(0, 30, TimeUnit.SECONDS);
 | 
			
		||||
            if (!lock) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 查询上次执行时间 校验执行间隔
 | 
			
		||||
            SmartJobEntity dbJobEntity = jobRepository.getJobDao().selectById(jobId);
 | 
			
		||||
            if (null == dbJobEntity) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            LocalDateTime lastExecuteTime = dbJobEntity.getLastExecuteTime();
 | 
			
		||||
            if (null != lastExecuteTime) {
 | 
			
		||||
                LocalDateTime nextTime = SmartJobUtil.queryNextTimeFromLast(jobEntity.getTriggerType(), jobEntity.getTriggerValue(), lastExecuteTime, 1).get(0);
 | 
			
		||||
                if (LocalDateTime.now().isBefore(nextTime)) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // 执行任务
 | 
			
		||||
            SmartJobLogEntity logEntity = this.execute(SmartJobConst.SYSTEM_NAME);
 | 
			
		||||
            log.info("==== SmartJob ==== execute job->{},time-millis->{}ms", jobEntity.getJobName(), logEntity.getExecuteTimeMillis());
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            log.error("==== SmartJob ==== execute err:", t);
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (rLock.isHeldByCurrentThread()) {
 | 
			
		||||
                rLock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param executorName
 | 
			
		||||
     */
 | 
			
		||||
    public SmartJobLogEntity execute(String executorName) {
 | 
			
		||||
        // 执行计时
 | 
			
		||||
        LocalDateTime startTime = LocalDateTime.now();
 | 
			
		||||
        StopWatch stopWatch = new StopWatch();
 | 
			
		||||
        stopWatch.start();
 | 
			
		||||
 | 
			
		||||
        // 执行任务
 | 
			
		||||
        boolean successFlag = true;
 | 
			
		||||
        String executeResult;
 | 
			
		||||
        try {
 | 
			
		||||
            executeResult = jobInterface.run(jobEntity.getParam());
 | 
			
		||||
            stopWatch.stop();
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            stopWatch.stop();
 | 
			
		||||
            successFlag = false;
 | 
			
		||||
            // ps:异常信息不大于数据库字段长度限制
 | 
			
		||||
            executeResult = ExceptionUtil.stacktraceToString(t, 1800);
 | 
			
		||||
            log.error("==== SmartJob ==== execute err:", t);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 保存执行记录
 | 
			
		||||
        Integer jobId = jobEntity.getJobId();
 | 
			
		||||
        SmartJobLogEntity logEntity = new SmartJobLogEntity();
 | 
			
		||||
        logEntity.setJobId(jobId);
 | 
			
		||||
        logEntity.setJobName(jobEntity.getJobName());
 | 
			
		||||
        logEntity.setParam(jobEntity.getParam());
 | 
			
		||||
        logEntity.setSuccessFlag(successFlag);
 | 
			
		||||
        // 执行开始 结束时间
 | 
			
		||||
        logEntity.setExecuteStartTime(startTime);
 | 
			
		||||
        long totalTimeMillis = stopWatch.getTotalTimeMillis();
 | 
			
		||||
        logEntity.setExecuteTimeMillis(totalTimeMillis);
 | 
			
		||||
        logEntity.setExecuteEndTime(startTime.plus(totalTimeMillis, ChronoUnit.MILLIS));
 | 
			
		||||
        // 执行结果
 | 
			
		||||
        logEntity.setExecuteResult(executeResult);
 | 
			
		||||
        logEntity.setIp(SmartIpUtil.getLocalFirstIp());
 | 
			
		||||
        logEntity.setProcessId(SmartJobUtil.getProcessId());
 | 
			
		||||
        logEntity.setProgramPath(SmartJobUtil.getProgramPath());
 | 
			
		||||
        logEntity.setCreateName(executorName);
 | 
			
		||||
 | 
			
		||||
        // 更新上次执行
 | 
			
		||||
        SmartJobEntity updateJobEntity = new SmartJobEntity();
 | 
			
		||||
        updateJobEntity.setJobId(jobId);
 | 
			
		||||
        updateJobEntity.setLastExecuteTime(startTime);
 | 
			
		||||
 | 
			
		||||
        // 持久化数据
 | 
			
		||||
        jobRepository.saveLog(logEntity, updateJobEntity);
 | 
			
		||||
        return logEntity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询 当前任务信息
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public SmartJobEntity getJob() {
 | 
			
		||||
        return jobEntity;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,148 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.core;
 | 
			
		||||
 | 
			
		||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.config.SmartJobConfig;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
import org.springframework.util.CollectionUtils;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.PreDestroy;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
 | 
			
		||||
import java.util.concurrent.ThreadFactory;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 作业启动类
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class SmartJobLauncher {
 | 
			
		||||
 | 
			
		||||
    private final SmartJobRepository jobRepository;
 | 
			
		||||
 | 
			
		||||
    private final List<SmartJob> jobInterfaceList;
 | 
			
		||||
 | 
			
		||||
    private final RedissonClient redissonClient;
 | 
			
		||||
 | 
			
		||||
    public SmartJobLauncher(SmartJobConfig jobConfig,
 | 
			
		||||
                            SmartJobRepository jobRepository,
 | 
			
		||||
                            List<SmartJob> jobInterfaceList,
 | 
			
		||||
                            RedissonClient redissonClient) {
 | 
			
		||||
        this.jobRepository = jobRepository;
 | 
			
		||||
        this.jobInterfaceList = jobInterfaceList;
 | 
			
		||||
        this.redissonClient = redissonClient;
 | 
			
		||||
 | 
			
		||||
        // init job scheduler
 | 
			
		||||
        SmartJobScheduler.init(jobConfig);
 | 
			
		||||
 | 
			
		||||
        // 任务自动检测配置 固定1个线程
 | 
			
		||||
        Integer initDelay = jobConfig.getInitDelay();
 | 
			
		||||
        Boolean refreshEnabled = jobConfig.getDbRefreshEnabled();
 | 
			
		||||
        Integer refreshInterval = jobConfig.getDbRefreshInterval();
 | 
			
		||||
 | 
			
		||||
        ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("SmartJobLauncher-%d").build();
 | 
			
		||||
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, factory);
 | 
			
		||||
        Runnable launcherRunnable = () -> {
 | 
			
		||||
            try {
 | 
			
		||||
                // 查询所有任务
 | 
			
		||||
                List<SmartJobEntity> smartJobList = this.queryJob();
 | 
			
		||||
                this.startOrRefreshJob(smartJobList);
 | 
			
		||||
            } catch (Throwable t) {
 | 
			
		||||
                log.error("SmartJob Error:", t);
 | 
			
		||||
            }
 | 
			
		||||
            // 只在启动时 执行一次
 | 
			
		||||
            if (!refreshEnabled) {
 | 
			
		||||
                executor.shutdown();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        executor.scheduleWithFixedDelay(launcherRunnable, initDelay, refreshInterval, TimeUnit.SECONDS);
 | 
			
		||||
 | 
			
		||||
        // 打印信息
 | 
			
		||||
        String refreshDesc = refreshEnabled ? "开启|检测间隔" + refreshInterval + "秒" : "关闭";
 | 
			
		||||
        String format = String.format(SmartJobConst.LOGO, jobConfig.getCorePoolSize(), initDelay, refreshDesc);
 | 
			
		||||
        SmartJobUtil.printInfo(format);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询数据库
 | 
			
		||||
     * 启动/刷新任务
 | 
			
		||||
     */
 | 
			
		||||
    public void startOrRefreshJob(List<SmartJobEntity> smartJobList) {
 | 
			
		||||
        // 查询任务配置
 | 
			
		||||
        if (CollectionUtils.isEmpty(smartJobList) || CollectionUtils.isEmpty(jobInterfaceList)) {
 | 
			
		||||
            log.info("==== SmartJob ==== job list empty");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 任务实现类
 | 
			
		||||
        Map<String, SmartJob> jobImplMap = jobInterfaceList.stream().collect(Collectors.toMap(SmartJob::getClassName, Function.identity()));
 | 
			
		||||
        for (SmartJobEntity jobEntity : smartJobList) {
 | 
			
		||||
            // 任务是否存在 判断是否需要更新
 | 
			
		||||
            Integer jobId = jobEntity.getJobId();
 | 
			
		||||
            SmartJobEntity oldJobEntity = SmartJobScheduler.getJobInfo(jobId);
 | 
			
		||||
            if (null != oldJobEntity) {
 | 
			
		||||
                // 不需要更新
 | 
			
		||||
                if (!isNeedUpdate(oldJobEntity, jobEntity)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                // 需要更新 移除原任务
 | 
			
		||||
                SmartJobScheduler.removeJob(jobId);
 | 
			
		||||
            }
 | 
			
		||||
            // 任务未开启
 | 
			
		||||
            if (!jobEntity.getEnabledFlag()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // 查找任务实现类
 | 
			
		||||
            SmartJob jobImpl = jobImplMap.get(jobEntity.getJobClass());
 | 
			
		||||
            if (null == jobImpl) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // 添加任务
 | 
			
		||||
            SmartJobExecutor jobExecute = new SmartJobExecutor(jobEntity, jobRepository, jobImpl, redissonClient);
 | 
			
		||||
            SmartJobScheduler.addJob(jobExecute);
 | 
			
		||||
        }
 | 
			
		||||
        List<SmartJobEntity> runjJobList = SmartJobScheduler.getJobInfo();
 | 
			
		||||
        List<String> jobNameList = runjJobList.stream().map(SmartJobEntity::getJobName).collect(Collectors.toList());
 | 
			
		||||
        log.info("==== SmartJob ==== start/refresh job num:{}->{}", runjJobList.size(), jobNameList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询全部任务
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    private List<SmartJobEntity> queryJob() {
 | 
			
		||||
        return jobRepository.getJobDao().selectList(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 手动判断 任务配置 是否需要更新
 | 
			
		||||
     * 新增字段的话 在这个方法里增加判断
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean isNeedUpdate(SmartJobEntity oldJob, SmartJobEntity newJob) {
 | 
			
		||||
        // cron为空时 fixedDelay 才有意义
 | 
			
		||||
        return !Objects.equals(oldJob.getEnabledFlag(), newJob.getEnabledFlag())
 | 
			
		||||
                || !Objects.equals(oldJob.getTriggerType(), newJob.getTriggerType())
 | 
			
		||||
                || !Objects.equals(oldJob.getTriggerValue(), newJob.getTriggerValue())
 | 
			
		||||
                || !Objects.equals(oldJob.getJobClass(), newJob.getJobClass());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PreDestroy
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
        SmartJobScheduler.destroy();
 | 
			
		||||
        log.info("==== SmartJob ==== destroy job");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,178 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.core;
 | 
			
		||||
 | 
			
		||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.config.SmartJobConfig;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import org.apache.commons.lang3.tuple.Pair;
 | 
			
		||||
import org.springframework.scheduling.Trigger;
 | 
			
		||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 | 
			
		||||
import org.springframework.scheduling.support.CronTrigger;
 | 
			
		||||
import org.springframework.scheduling.support.PeriodicTrigger;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.ScheduledFuture;
 | 
			
		||||
import java.util.concurrent.ThreadFactory;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 调度管理
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/22 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class SmartJobScheduler {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Spring线程池任务调度器
 | 
			
		||||
     */
 | 
			
		||||
    private static ThreadPoolTaskScheduler TASK_SCHEDULER;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务 map
 | 
			
		||||
     */
 | 
			
		||||
    private static Map<Integer, Pair<SmartJobEntity, ScheduledFuture<?>>> JOB_FUTURE_MAP;
 | 
			
		||||
 | 
			
		||||
    private SmartJobScheduler() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化任务调度配置
 | 
			
		||||
     */
 | 
			
		||||
    public static void init(SmartJobConfig config) {
 | 
			
		||||
        TASK_SCHEDULER = new ThreadPoolTaskScheduler();
 | 
			
		||||
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("SmartJobExecutor-%d").build();
 | 
			
		||||
        TASK_SCHEDULER.setThreadFactory(threadFactory);
 | 
			
		||||
        TASK_SCHEDULER.setPoolSize(config.getCorePoolSize());
 | 
			
		||||
        // 线程池在关闭时会等待所有任务完成
 | 
			
		||||
        TASK_SCHEDULER.setWaitForTasksToCompleteOnShutdown(true);
 | 
			
		||||
        // 在调用shutdown方法后,等待任务完成的最长时间
 | 
			
		||||
        TASK_SCHEDULER.setAwaitTerminationSeconds(10);
 | 
			
		||||
        // 错误处理
 | 
			
		||||
        TASK_SCHEDULER.setErrorHandler((t) -> log.error("SmartJobExecute Err:", t));
 | 
			
		||||
        // 当一个任务在被调度执行前被取消时,是否应该从线程池的任务队列中移除
 | 
			
		||||
        TASK_SCHEDULER.setRemoveOnCancelPolicy(true);
 | 
			
		||||
        TASK_SCHEDULER.initialize();
 | 
			
		||||
 | 
			
		||||
        JOB_FUTURE_MAP = new ConcurrentHashMap<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取任务执行对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobId
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static ScheduledFuture<?> getJobFuture(Integer jobId) {
 | 
			
		||||
        Pair<SmartJobEntity, ScheduledFuture<?>> pair = JOB_FUTURE_MAP.get(jobId);
 | 
			
		||||
        if (null == pair) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return pair.getRight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前所有执行任务
 | 
			
		||||
     *
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static List<SmartJobEntity> getJobInfo() {
 | 
			
		||||
        return JOB_FUTURE_MAP.values().stream().map(Pair::getLeft).collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取任务执行实体类
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobId
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static SmartJobEntity getJobInfo(Integer jobId) {
 | 
			
		||||
        Pair<SmartJobEntity, ScheduledFuture<?>> pair = JOB_FUTURE_MAP.get(jobId);
 | 
			
		||||
        if (null == pair) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return pair.getLeft();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobExecute
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public static void addJob(SmartJobExecutor jobExecute) {
 | 
			
		||||
        // 任务是否存在
 | 
			
		||||
        SmartJobEntity jobEntity = jobExecute.getJob();
 | 
			
		||||
        Integer jobId = jobEntity.getJobId();
 | 
			
		||||
        if (JOB_FUTURE_MAP.containsKey(jobId)) {
 | 
			
		||||
            // 移除任务
 | 
			
		||||
            removeJob(jobId);
 | 
			
		||||
        }
 | 
			
		||||
        // 任务触发类型
 | 
			
		||||
        Trigger trigger = null;
 | 
			
		||||
        String triggerType = jobEntity.getTriggerType();
 | 
			
		||||
        String triggerValue = jobEntity.getTriggerValue();
 | 
			
		||||
        // 优先 cron 表达式
 | 
			
		||||
        if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) {
 | 
			
		||||
            trigger = new CronTrigger(triggerValue);
 | 
			
		||||
        } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) {
 | 
			
		||||
            trigger = new PeriodicTrigger(SmartJobUtil.getFixedDelayVal(triggerValue), TimeUnit.SECONDS);
 | 
			
		||||
        }
 | 
			
		||||
        String jobName = jobEntity.getJobName();
 | 
			
		||||
        if (null == trigger) {
 | 
			
		||||
            log.error("==== SmartJob ==== trigger-value not null {}", jobName);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 执行任务
 | 
			
		||||
        ScheduledFuture<?> schedule = TASK_SCHEDULER.schedule(jobExecute, trigger);
 | 
			
		||||
        JOB_FUTURE_MAP.put(jobId, Pair.of(jobEntity, schedule));
 | 
			
		||||
        log.info("==== SmartJob ==== add job:{}", jobName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 移除任务
 | 
			
		||||
     * 等待任务执行完成后移除
 | 
			
		||||
     *
 | 
			
		||||
     * @param jobId
 | 
			
		||||
     */
 | 
			
		||||
    public static void removeJob(Integer jobId) {
 | 
			
		||||
        ScheduledFuture<?> jobFuture = getJobFuture(jobId);
 | 
			
		||||
        if (null == jobFuture) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 结束任务
 | 
			
		||||
        stopJob(jobFuture);
 | 
			
		||||
        JOB_FUTURE_MAP.remove(jobId);
 | 
			
		||||
        log.info("==== SmartJob ==== remove job:{}", jobId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止所有定时任务
 | 
			
		||||
     */
 | 
			
		||||
    public static void destroy() {
 | 
			
		||||
        // 启动一个有序的关闭过程,在这个过程中,不再接受新的任务提交,但已提交的任务(包括正在执行的和队列中等待的)会被允许执行完成。
 | 
			
		||||
        TASK_SCHEDULER.destroy();
 | 
			
		||||
        JOB_FUTURE_MAP.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 结束任务
 | 
			
		||||
     * 如果任务还没有开始执行,会直接被取消。
 | 
			
		||||
     * 如果任务已经开始执行,此时不会中断执行中的线程,任务会执行完成再被取消
 | 
			
		||||
     *
 | 
			
		||||
     * @param scheduledFuture
 | 
			
		||||
     */
 | 
			
		||||
    private static void stopJob(ScheduledFuture<?> scheduledFuture) {
 | 
			
		||||
        if (null == scheduledFuture || scheduledFuture.isCancelled()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        scheduledFuture.cancel(false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.repository;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.SmartJobQueryForm;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.SmartJobVO;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 dao
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
@Component
 | 
			
		||||
public interface SmartJobDao extends BaseMapper<SmartJobEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务-分页查询
 | 
			
		||||
     *
 | 
			
		||||
     * @param page
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    List<SmartJobVO> query(Page<?> page, @Param("query") SmartJobQueryForm queryForm);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.repository;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogQueryForm;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogVO;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务-执行记录 dao
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
@Component
 | 
			
		||||
public interface SmartJobLogDao extends BaseMapper<SmartJobLogEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务-执行记录-分页查询
 | 
			
		||||
     *
 | 
			
		||||
     * @param page
 | 
			
		||||
     * @param queryForm
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    List<SmartJobLogVO> query(Page<?> page, @Param("query") SmartJobLogQueryForm queryForm);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.repository;
 | 
			
		||||
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * job 持久化业务
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/22 22:28
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartJobRepository {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private SmartJobDao jobDao;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private SmartJobLogDao jobLogDao;
 | 
			
		||||
 | 
			
		||||
    public SmartJobDao getJobDao() {
 | 
			
		||||
        return jobDao;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存执行记录
 | 
			
		||||
     *
 | 
			
		||||
     * @param logEntity
 | 
			
		||||
     * @param jobEntity
 | 
			
		||||
     */
 | 
			
		||||
    @Transactional(rollbackFor = Throwable.class)
 | 
			
		||||
    public void saveLog(SmartJobLogEntity logEntity, SmartJobEntity jobEntity) {
 | 
			
		||||
        jobLogDao.insert(logEntity);
 | 
			
		||||
 | 
			
		||||
        jobEntity.setLastExecuteLogId(logEntity.getLogId());
 | 
			
		||||
        jobDao.updateById(jobEntity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.repository.domain;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.IdType;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableId;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableName;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 实体类
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@TableName("t_smart_job")
 | 
			
		||||
public class SmartJobEntity {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
    @TableId(type = IdType.AUTO)
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务名称
 | 
			
		||||
     */
 | 
			
		||||
    private String jobName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行类
 | 
			
		||||
     */
 | 
			
		||||
    private String jobClass;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 触发类型
 | 
			
		||||
     *
 | 
			
		||||
     * @see SmartJobTriggerTypeEnum
 | 
			
		||||
     */
 | 
			
		||||
    private String triggerType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 触发配置
 | 
			
		||||
     */
 | 
			
		||||
    private String triggerValue;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务参数 可选
 | 
			
		||||
     */
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否启用
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean enabledFlag;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 最后一执行时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime lastExecuteTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 最后一次执行记录id
 | 
			
		||||
     */
 | 
			
		||||
    private Long lastExecuteLogId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 备注描述 可选
 | 
			
		||||
     */
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 排序
 | 
			
		||||
     */
 | 
			
		||||
    private Integer sort;
 | 
			
		||||
 | 
			
		||||
    private String updateName;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.repository.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 huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@TableName("t_smart_job_log")
 | 
			
		||||
public class SmartJobLogEntity {
 | 
			
		||||
 | 
			
		||||
    @TableId(type = IdType.AUTO)
 | 
			
		||||
    private Long logId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
    private Integer jobId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务名称
 | 
			
		||||
     */
 | 
			
		||||
    private String jobName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务参数 可选
 | 
			
		||||
     */
 | 
			
		||||
    private String param;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行结果 是否成功
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean successFlag;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 开始执行时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime executeStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行时长-毫秒
 | 
			
		||||
     */
 | 
			
		||||
    private Long executeTimeMillis;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行结束时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime executeEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行结果 描述 可选
 | 
			
		||||
     */
 | 
			
		||||
    private String executeResult;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ip
 | 
			
		||||
     */
 | 
			
		||||
    private String ip;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 进程id
 | 
			
		||||
     */
 | 
			
		||||
    private String processId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 程序目录
 | 
			
		||||
     */
 | 
			
		||||
    private String programPath;
 | 
			
		||||
 | 
			
		||||
    private String createName;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.sample;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJob;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 示例1
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartJobSample1 implements SmartJob {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务示例
 | 
			
		||||
     *
 | 
			
		||||
     * @param param 可选参数 任务不需要时不用管
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String run(String param) {
 | 
			
		||||
        // 写点什么业务逻辑
 | 
			
		||||
        return "执行完毕,随便说点什么吧";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.sample;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.module.support.config.ConfigDao;
 | 
			
		||||
import net.lab1024.sa.base.module.support.config.domain.ConfigEntity;
 | 
			
		||||
import net.lab1024.sa.base.module.support.job.core.SmartJob;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 示例2
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/17 21:30
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartJobSample2 implements SmartJob {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ConfigDao configDao;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 定时任务示例
 | 
			
		||||
     * 需要事务时 添加 @Transactional 注解
 | 
			
		||||
     *
 | 
			
		||||
     * @param param 可选参数 任务不需要时不用管
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    @Transactional(rollbackFor = Throwable.class)
 | 
			
		||||
    @Override
 | 
			
		||||
    public String run(String param) {
 | 
			
		||||
        // 随便更新点什么东西
 | 
			
		||||
        ConfigEntity configEntity = new ConfigEntity();
 | 
			
		||||
        configEntity.setConfigId(1L);
 | 
			
		||||
        configEntity.setRemark(param);
 | 
			
		||||
        configDao.updateById(configEntity);
 | 
			
		||||
 | 
			
		||||
        configEntity = new ConfigEntity();
 | 
			
		||||
        configEntity.setConfigId(2L);
 | 
			
		||||
        configEntity.setRemark("SmartJob Sample2 update");
 | 
			
		||||
        configDao.updateById(configEntity);
 | 
			
		||||
 | 
			
		||||
        return "执行成功,本次处理数据1条";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 定时任务 示例包
 | 
			
		||||
 * 可以删除
 | 
			
		||||
 */
 | 
			
		||||
package net.lab1024.sa.base.module.support.job.sample;
 | 
			
		||||
@@ -16,6 +16,12 @@ import net.lab1024.sa.base.common.domain.PageParam;
 | 
			
		||||
@Data
 | 
			
		||||
public class LoginLogQueryForm extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "用户ID")
 | 
			
		||||
    private Long userId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "用户类型")
 | 
			
		||||
    private Integer userType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "开始日期")
 | 
			
		||||
    private String startDate;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.constant;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息模板类型
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Getter
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public enum MessageTemplateEnum implements BaseEnum {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ORDER_AUDIT(1000, "订单审批", MessageTypeEnum.ORDER, "您有一个订单等待审批,订单号【${orderNumber}】"),
 | 
			
		||||
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    private final Integer value;
 | 
			
		||||
 | 
			
		||||
    private final String desc;
 | 
			
		||||
 | 
			
		||||
    private final MessageTypeEnum messageTypeEnum;
 | 
			
		||||
 | 
			
		||||
    private final String content;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.constant;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息类型
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Getter
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public enum MessageTypeEnum implements BaseEnum {
 | 
			
		||||
 | 
			
		||||
    MAIL(1, "站内信"),
 | 
			
		||||
 | 
			
		||||
    ORDER(2, "订单"),
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    private final Integer value;
 | 
			
		||||
 | 
			
		||||
    private final String desc;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,68 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.controller;
 | 
			
		||||
 | 
			
		||||
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.RequestUser;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
 | 
			
		||||
import net.lab1024.sa.base.common.util.SmartRequestUtil;
 | 
			
		||||
import net.lab1024.sa.base.constant.SwaggerTagConst;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageVO;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.service.MessageService;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = SwaggerTagConst.Support.MESSAGE)
 | 
			
		||||
public class MessageController extends SupportBaseController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private MessageService messageService;
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "分页查询我的消息 @luoyi")
 | 
			
		||||
    @PostMapping("/message/queryMyMessage")
 | 
			
		||||
    public ResponseDTO<PageResult<MessageVO>> query(@RequestBody @Valid MessageQueryForm queryForm) {
 | 
			
		||||
        RequestUser user = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        if(user == null){
 | 
			
		||||
            return ResponseDTO.userErrorParam("用户未登录");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        queryForm.setSearchCount(false);
 | 
			
		||||
        queryForm.setReceiverUserId(user.getUserId());
 | 
			
		||||
        queryForm.setReceiverUserType(user.getUserType().getValue());
 | 
			
		||||
        return ResponseDTO.ok(messageService.query(queryForm));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "查询未读消息数量 @luoyi")
 | 
			
		||||
    @GetMapping("/message/getUnreadCount")
 | 
			
		||||
    public ResponseDTO<Long> getUnreadCount() {
 | 
			
		||||
        RequestUser user = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        if(user == null){
 | 
			
		||||
            return ResponseDTO.userErrorParam("用户未登录");
 | 
			
		||||
        }
 | 
			
		||||
        return ResponseDTO.ok(messageService.getUnreadCount(user.getUserType(), user.getUserId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation(summary = "更新已读 @luoyi")
 | 
			
		||||
    @GetMapping("/message/read/{messageId}")
 | 
			
		||||
    public ResponseDTO<String> updateReadFlag(@PathVariable Long messageId) {
 | 
			
		||||
        RequestUser user = SmartRequestUtil.getRequestUser();
 | 
			
		||||
        if(user == null){
 | 
			
		||||
            return ResponseDTO.userErrorParam("用户未登录");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        messageService.updateReadFlag(messageId, user.getUserType(), user.getUserId());
 | 
			
		||||
        return ResponseDTO.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.dao;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageEntity;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageVO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息 接受者类型枚举
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface MessageDao extends BaseMapper<MessageEntity> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询消息
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    List<MessageVO> query(Page<?> page, @Param("query") MessageQueryForm queryForm);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新已读状态
 | 
			
		||||
     */
 | 
			
		||||
    Integer updateReadFlag(@Param("messageId") Long messageId,
 | 
			
		||||
                           @Param("receiverUserType") Integer receiverUserType,
 | 
			
		||||
                           @Param("receiverUserId") Long receiverUserId,
 | 
			
		||||
                           @Param("readFlag") Boolean readFlag);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询未读消息数
 | 
			
		||||
     */
 | 
			
		||||
    Long getUnreadCount( @Param("receiverUserType") Integer receiverUserType,
 | 
			
		||||
                         @Param("receiverUserId") Long receiverUserId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,70 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.domain;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.IdType;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableId;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableName;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息实体
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@TableName("t_message")
 | 
			
		||||
public class MessageEntity {
 | 
			
		||||
 | 
			
		||||
    @TableId(type = IdType.AUTO)
 | 
			
		||||
    private Long messageId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型
 | 
			
		||||
     *
 | 
			
		||||
     * @see MessageTypeEnum
 | 
			
		||||
     */
 | 
			
		||||
    private Integer messageType;
 | 
			
		||||
    /**
 | 
			
		||||
     * 接收者类型
 | 
			
		||||
     *
 | 
			
		||||
     * @see net.lab1024.sa.base.common.enumeration.UserTypeEnum
 | 
			
		||||
     */
 | 
			
		||||
    private Integer receiverUserType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 接收者id
 | 
			
		||||
     */
 | 
			
		||||
    private Long receiverUserId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 相关业务id
 | 
			
		||||
     */
 | 
			
		||||
    private String dataId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息标题
 | 
			
		||||
     */
 | 
			
		||||
    private String title;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息内容
 | 
			
		||||
     */
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否已读
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean readFlag;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 已读时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime readTime;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime updateTime;
 | 
			
		||||
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageParam;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum;
 | 
			
		||||
import org.hibernate.validator.constraints.Length;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息查询form
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class MessageQueryForm extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "搜索词")
 | 
			
		||||
    @Length(max = 50, message = "搜索词最多50字符")
 | 
			
		||||
    private String searchWord;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(value = MessageTypeEnum.class)
 | 
			
		||||
    @CheckEnum(value = MessageTypeEnum.class, message = "消息类型")
 | 
			
		||||
    private Integer messageType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否已读")
 | 
			
		||||
    private Boolean readFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "查询开始时间")
 | 
			
		||||
    private LocalDate startDate;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "查询结束时间")
 | 
			
		||||
    private LocalDate endDate;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private Long receiverUserId;
 | 
			
		||||
 | 
			
		||||
    @Schema(hidden = true)
 | 
			
		||||
    private Integer receiverUserType;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
/**
 | 
			
		||||
 * 消息发送form
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class MessageSendForm {
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(value = MessageTypeEnum.class, desc = "消息类型")
 | 
			
		||||
    @NotNull(message = "消息类型不能为空")
 | 
			
		||||
    private Integer messageType;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(value = UserTypeEnum.class, desc = "接收者类型")
 | 
			
		||||
    @NotNull(message = "接收者类型不能为空")
 | 
			
		||||
    private Integer receiverUserType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "接收者id")
 | 
			
		||||
    @NotNull(message = "接收者id不能为空")
 | 
			
		||||
    private Long receiverUserId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "标题")
 | 
			
		||||
    @NotBlank(message = "标题")
 | 
			
		||||
    private String title;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "内容")
 | 
			
		||||
    @NotBlank(message = "内容")
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 相关业务id | 可选
 | 
			
		||||
     */
 | 
			
		||||
    private Object dataId;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.domain;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.constant.MessageTemplateEnum;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.NotNull;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息发送form
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class MessageTemplateSendForm {
 | 
			
		||||
 | 
			
		||||
    @NotNull(message = "消息子类型不能为空")
 | 
			
		||||
    private MessageTemplateEnum messageTemplateEnum;
 | 
			
		||||
 | 
			
		||||
    @NotNull(message = "接收者类型不能为空")
 | 
			
		||||
    private UserTypeEnum receiverUserType;
 | 
			
		||||
 | 
			
		||||
    @NotNull(message = "接收者id不能为空")
 | 
			
		||||
    private Long receiverUserId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 相关业务id | 可选
 | 
			
		||||
     * 用于跳转具体业务
 | 
			
		||||
     */
 | 
			
		||||
    private Object dataId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息参数 | 可选
 | 
			
		||||
     * 例:订单号:【{orderId}】{time}所提交的对账单被作废,请核实信息重新提交~
 | 
			
		||||
     * {orderId} {time} 就是消息的参数变量
 | 
			
		||||
     * 发送消息时 需要在map中放入k->orderId k->time
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, Object> contentParam;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.domain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
 | 
			
		||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class MessageVO {
 | 
			
		||||
 | 
			
		||||
    private Long messageId;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(value = MessageTypeEnum.class)
 | 
			
		||||
    private Integer messageType;
 | 
			
		||||
 | 
			
		||||
    @SchemaEnum(value = UserTypeEnum.class)
 | 
			
		||||
    private Integer receiverUserType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "接收者id")
 | 
			
		||||
    private Long receiverUserId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "相关业务id")
 | 
			
		||||
    private String dataId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "消息标题")
 | 
			
		||||
    private String title;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "消息内容")
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否已读")
 | 
			
		||||
    private Boolean readFlag;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "已读时间")
 | 
			
		||||
    private LocalDateTime readTime;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "创建时间")
 | 
			
		||||
    private LocalDateTime createTime;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.service;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.dao.MessageDao;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.MessageEntity;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 消息manager
 | 
			
		||||
 *
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/06/22 20:20
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class MessageManager extends ServiceImpl<MessageDao, MessageEntity> {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,108 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.message.service;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import net.lab1024.sa.base.common.domain.PageResult;
 | 
			
		||||
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.message.constant.MessageTemplateEnum;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.dao.MessageDao;
 | 
			
		||||
import net.lab1024.sa.base.module.support.message.domain.*;
 | 
			
		||||
import org.apache.commons.text.StringSubstitutor;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author luoyi
 | 
			
		||||
 * @date 2024/6/27 12:14 上午
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class MessageService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private MessageDao messageDao;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private MessageManager messageManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询 消息
 | 
			
		||||
     */
 | 
			
		||||
    public PageResult<MessageVO> query(MessageQueryForm queryForm) {
 | 
			
		||||
        Page page = SmartPageUtil.convert2PageQuery(queryForm);
 | 
			
		||||
        List<MessageVO> messageVOList = messageDao.query(page, queryForm);
 | 
			
		||||
        return SmartPageUtil.convert2PageResult(page, messageVOList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询未读消息数量
 | 
			
		||||
     */
 | 
			
		||||
    public Long getUnreadCount(UserTypeEnum userType, Long userId) {
 | 
			
		||||
        return messageDao.getUnreadCount(userType.getValue(), userId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新已读状态
 | 
			
		||||
     */
 | 
			
		||||
    public void updateReadFlag(Long messageId, UserTypeEnum userType, Long receiverUserId) {
 | 
			
		||||
        messageDao.updateReadFlag(messageId, userType.getValue(), receiverUserId, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送【模板消息】
 | 
			
		||||
     */
 | 
			
		||||
    public void sendTemplateMessage(MessageTemplateSendForm... sendTemplateForms) {
 | 
			
		||||
        List<MessageSendForm> sendFormList = Lists.newArrayList();
 | 
			
		||||
        for (MessageTemplateSendForm sendTemplateForm : sendTemplateForms) {
 | 
			
		||||
            MessageTemplateEnum msgTemplateTypeEnum = sendTemplateForm.getMessageTemplateEnum();
 | 
			
		||||
            StringSubstitutor stringSubstitutor = new StringSubstitutor(sendTemplateForm.getContentParam());
 | 
			
		||||
            String content = stringSubstitutor.replace(msgTemplateTypeEnum.getContent());
 | 
			
		||||
 | 
			
		||||
            MessageSendForm messageSendForm = new MessageSendForm();
 | 
			
		||||
            messageSendForm.setMessageType(msgTemplateTypeEnum.getMessageTypeEnum().getValue());
 | 
			
		||||
            messageSendForm.setReceiverUserType(sendTemplateForm.getReceiverUserType().getValue());
 | 
			
		||||
            messageSendForm.setReceiverUserId(sendTemplateForm.getReceiverUserId());
 | 
			
		||||
            messageSendForm.setTitle(msgTemplateTypeEnum.getDesc());
 | 
			
		||||
            messageSendForm.setContent(content);
 | 
			
		||||
            messageSendForm.setDataId(sendTemplateForm.getDataId());
 | 
			
		||||
            sendFormList.add(messageSendForm);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        this.sendMessage(sendFormList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送消息
 | 
			
		||||
     */
 | 
			
		||||
    public void sendMessage(MessageSendForm... sendForms) {
 | 
			
		||||
        this.sendMessage(Lists.newArrayList(sendForms));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量发送通知消息
 | 
			
		||||
     */
 | 
			
		||||
    public void sendMessage(List<MessageSendForm> sendList) {
 | 
			
		||||
        for (MessageSendForm sendDTO : sendList) {
 | 
			
		||||
            String verify = SmartBeanUtil.verify(sendDTO);
 | 
			
		||||
            if (null != verify) {
 | 
			
		||||
                throw new RuntimeException("send msg error: " + verify);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        List<MessageEntity> messageEntityList = sendList.stream().map(e -> {
 | 
			
		||||
            MessageEntity messageEntity = new MessageEntity();
 | 
			
		||||
            messageEntity.setMessageType(e.getMessageType());
 | 
			
		||||
            messageEntity.setReceiverUserType(e.getReceiverUserType());
 | 
			
		||||
            messageEntity.setReceiverUserId(e.getReceiverUserId());
 | 
			
		||||
            messageEntity.setDataId(String.valueOf(e.getDataId()));
 | 
			
		||||
            messageEntity.setTitle(e.getTitle());
 | 
			
		||||
            messageEntity.setContent(e.getContent());
 | 
			
		||||
            return messageEntity;
 | 
			
		||||
        }).collect(Collectors.toList());
 | 
			
		||||
        messageManager.saveBatch(messageEntityList);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -16,6 +16,11 @@ import lombok.Data;
 | 
			
		||||
@Data
 | 
			
		||||
public class OperateLogQueryForm extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "用户ID")
 | 
			
		||||
    private Long operateUserId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "用户类型")
 | 
			
		||||
    private Integer operateUserType;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "开始日期")
 | 
			
		||||
    private String startDate;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,139 @@
 | 
			
		||||
package net.lab1024.sa.base.module.support.redis;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import net.lab1024.sa.base.common.exception.BusinessException;
 | 
			
		||||
import org.redisson.api.RBucket;
 | 
			
		||||
import org.redisson.api.RIdGenerator;
 | 
			
		||||
import org.redisson.api.RLock;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Redisson 业务
 | 
			
		||||
 *
 | 
			
		||||
 * @author huke
 | 
			
		||||
 * @date 2024/6/19 20:39
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Service
 | 
			
		||||
public class RedissonService {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private final RedissonClient redissonClient;
 | 
			
		||||
 | 
			
		||||
    public RedissonService(RedissonClient redissonClient) {
 | 
			
		||||
        this.redissonClient = redissonClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RedissonClient getRedissonClient() {
 | 
			
		||||
        return redissonClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取锁 并 执行程序
 | 
			
		||||
     *
 | 
			
		||||
     * @param lockKey
 | 
			
		||||
     * @param waitTime 毫秒
 | 
			
		||||
     * @param lockTime 毫秒
 | 
			
		||||
     * @param supplier
 | 
			
		||||
     */
 | 
			
		||||
    public <T> T executeWithLock(String lockKey, long waitTime, long lockTime, Supplier<T> supplier) {
 | 
			
		||||
        // 获取锁
 | 
			
		||||
        RLock lock = this.tryLock(lockKey, waitTime, lockTime);
 | 
			
		||||
        try {
 | 
			
		||||
            return supplier.get();
 | 
			
		||||
        } finally {
 | 
			
		||||
            // 释放锁
 | 
			
		||||
            if (lock.isHeldByCurrentThread()) {
 | 
			
		||||
                lock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取锁 并 执行程序
 | 
			
		||||
     *
 | 
			
		||||
     * @param lockKey
 | 
			
		||||
     * @param waitTime 毫秒
 | 
			
		||||
     * @param lockTime 毫秒
 | 
			
		||||
     * @param runnable
 | 
			
		||||
     */
 | 
			
		||||
    public void executeWithLock(String lockKey, long waitTime, long lockTime, Runnable runnable) {
 | 
			
		||||
        // 获取锁
 | 
			
		||||
        RLock lock = this.tryLock(lockKey, waitTime, lockTime);
 | 
			
		||||
        try {
 | 
			
		||||
            runnable.run();
 | 
			
		||||
        } finally {
 | 
			
		||||
            // 释放锁
 | 
			
		||||
            if (lock.isHeldByCurrentThread()) {
 | 
			
		||||
                lock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 尝试获取锁
 | 
			
		||||
     * 最多等待 waitTime 毫秒
 | 
			
		||||
     * 获取锁成功后占用 lockTime 毫秒
 | 
			
		||||
     * ps:需要手动解锁 lock.unlock()
 | 
			
		||||
     *
 | 
			
		||||
     * @param lockKey
 | 
			
		||||
     * @param waitTime 毫秒
 | 
			
		||||
     * @param lockTime 毫秒
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public RLock tryLock(String lockKey, long waitTime, long lockTime) {
 | 
			
		||||
        RLock lock = redissonClient.getLock(lockKey);
 | 
			
		||||
        try {
 | 
			
		||||
            boolean getLock = lock.tryLock(waitTime, lockTime, TimeUnit.MILLISECONDS);
 | 
			
		||||
            if (getLock) {
 | 
			
		||||
                return lock;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            log.error("Redisson tryLock", e);
 | 
			
		||||
        }
 | 
			
		||||
        throw new BusinessException("业务繁忙,请稍后重试~");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 id 生成器
 | 
			
		||||
     * nextId 可生成连续不重复的id
 | 
			
		||||
     *
 | 
			
		||||
     * @param key
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public RIdGenerator idGenerator(String key) {
 | 
			
		||||
        return redissonClient.getIdGenerator(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 存放任意数据类型
 | 
			
		||||
     *
 | 
			
		||||
     * @param key
 | 
			
		||||
     * @param v
 | 
			
		||||
     * @param duration
 | 
			
		||||
     * @param <T>
 | 
			
		||||
     */
 | 
			
		||||
    public <T> void putObj(String key, T v, Duration duration) {
 | 
			
		||||
        redissonClient.getBucket(key).set(v, duration);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取任意数据类型
 | 
			
		||||
     *
 | 
			
		||||
     * @param key
 | 
			
		||||
     * @param clazz
 | 
			
		||||
     * @param <T>
 | 
			
		||||
     * @return 如果没有找到则返回null
 | 
			
		||||
     */
 | 
			
		||||
    public <T> T getObj(String key, Class<T> clazz) {
 | 
			
		||||
        RBucket<T> bucket = redissonClient.getBucket(key);
 | 
			
		||||
        return bucket.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -6,9 +6,15 @@ import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload;
 | 
			
		||||
import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject;
 | 
			
		||||
import net.lab1024.sa.base.module.support.reload.core.thread.SmartReloadRunnable;
 | 
			
		||||
import org.springframework.beans.BeansException;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.beans.factory.config.BeanPostProcessor;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.util.ReflectionUtils;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.PostConstruct;
 | 
			
		||||
import javax.annotation.PreDestroy;
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
@@ -24,19 +30,32 @@ import java.util.concurrent.TimeUnit;
 | 
			
		||||
 * @Date 2015-03-02 19:11:52
 | 
			
		||||
 * @Wechat zhuoda1024
 | 
			
		||||
 * @Email lab1024@163.com
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Service
 | 
			
		||||
public class SmartReloadManager implements BeanPostProcessor {
 | 
			
		||||
 | 
			
		||||
    private static final String THREAD_NAME_PREFIX = "smart-reload";
 | 
			
		||||
    private static final int THREAD_COUNT = 1;
 | 
			
		||||
 | 
			
		||||
    private Map<String, SmartReloadObject> reloadObjectMap = new ConcurrentHashMap<>();
 | 
			
		||||
    @Value("${reload.interval-seconds}")
 | 
			
		||||
    private Integer intervalSeconds;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AbstractSmartReloadCommand reloadCommand;
 | 
			
		||||
 | 
			
		||||
    private final Map<String, SmartReloadObject> reloadObjectMap = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
    private ScheduledThreadPoolExecutor threadPoolExecutor;
 | 
			
		||||
 | 
			
		||||
    public SmartReloadManager(AbstractSmartReloadCommand reloadCommand, int intervalSeconds) {
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void init() {
 | 
			
		||||
        if (threadPoolExecutor != null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.threadPoolExecutor = new ScheduledThreadPoolExecutor(THREAD_COUNT, r -> {
 | 
			
		||||
            Thread t = new Thread(r, THREAD_NAME_PREFIX);
 | 
			
		||||
            if (!t.isDaemon()) {
 | 
			
		||||
@@ -44,17 +63,23 @@ public class SmartReloadManager implements BeanPostProcessor {
 | 
			
		||||
            }
 | 
			
		||||
            return t;
 | 
			
		||||
        });
 | 
			
		||||
        this.threadPoolExecutor.scheduleWithFixedDelay(new SmartReloadRunnable(reloadCommand), 10, intervalSeconds, TimeUnit.SECONDS);
 | 
			
		||||
        reloadCommand.setReloadManager(this);
 | 
			
		||||
        this.threadPoolExecutor.scheduleWithFixedDelay(new SmartReloadRunnable(this.reloadCommand), 10, this.intervalSeconds, TimeUnit.SECONDS);
 | 
			
		||||
        this.reloadCommand.setReloadManager(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PreDestroy
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        if (this.threadPoolExecutor != null) {
 | 
			
		||||
            this.threadPoolExecutor.shutdownNow();
 | 
			
		||||
            this.threadPoolExecutor = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 | 
			
		||||
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
 | 
			
		||||
        if (methods == null) {
 | 
			
		||||
            return bean;
 | 
			
		||||
        }
 | 
			
		||||
        for (Method method : methods) {
 | 
			
		||||
            SmartReload smartReload = method.getAnnotation(SmartReload.class);
 | 
			
		||||
            if (smartReload == null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -140,3 +140,16 @@ sa-token:
 | 
			
		||||
  is-print: false
 | 
			
		||||
  # 是否从cookie读取token
 | 
			
		||||
  is-read-cookie: false
 | 
			
		||||
 | 
			
		||||
# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com)
 | 
			
		||||
smart:
 | 
			
		||||
  job:
 | 
			
		||||
    enabled: true
 | 
			
		||||
    # 任务初始化延迟 默认30秒 可选
 | 
			
		||||
    init-delay: 10
 | 
			
		||||
    # 定时任务执行线程池数量 默认2 可选
 | 
			
		||||
    core-pool-size: 2
 | 
			
		||||
    # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启)
 | 
			
		||||
    db-refresh-enabled: true
 | 
			
		||||
    # 数据库配置检测-执行间隔 默认120秒 可选
 | 
			
		||||
    db-refresh-interval: 60
 | 
			
		||||
@@ -7,6 +7,15 @@
 | 
			
		||||
        *
 | 
			
		||||
        from t_login_log
 | 
			
		||||
        <where>
 | 
			
		||||
            <if test="query.userId != null">
 | 
			
		||||
                AND user_id = #{query.userId}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.userType != null">
 | 
			
		||||
                AND user_type = #{query.userType}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.ip != null">
 | 
			
		||||
                AND INSTR(login_ip,#{query.ip})
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.startDate != null and query.startDate != ''">
 | 
			
		||||
                AND DATE_FORMAT(create_time, '%Y-%m-%d') >= #{query.startDate}
 | 
			
		||||
            </if>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
<?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.message.dao.MessageDao">
 | 
			
		||||
 | 
			
		||||
    <!-- 更新已读状态 -->
 | 
			
		||||
    <update id="updateReadFlag">
 | 
			
		||||
        UPDATE t_message
 | 
			
		||||
        SET read_flag = #{readFlag},
 | 
			
		||||
            read_time = now()
 | 
			
		||||
        WHERE message_id = #{messageId}
 | 
			
		||||
          AND receiver_user_type = #{receiverUserType}
 | 
			
		||||
          AND receiver_user_id = #{receiverUserId}
 | 
			
		||||
          AND read_flag != #{readFlag}
 | 
			
		||||
    </update>
 | 
			
		||||
 | 
			
		||||
    <!-- 分页查询消息 -->
 | 
			
		||||
    <select id="query" resultType="net.lab1024.sa.base.module.support.message.domain.MessageVO">
 | 
			
		||||
        SELECT * FROM t_message
 | 
			
		||||
        <where>
 | 
			
		||||
            <if test="query.receiverUserType != null">
 | 
			
		||||
                AND receiver_user_type = #{query.receiverUserType}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.receiverUserId != null">
 | 
			
		||||
                AND receiver_user_id = #{query.receiverUserId}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.messageType != null">
 | 
			
		||||
                AND message_type = #{query.messageType}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.searchWord != null and query.searchWord !=''">
 | 
			
		||||
                AND ( INSTR(title,#{query.searchWord})
 | 
			
		||||
                OR INSTR(content,#{query.searchWord})
 | 
			
		||||
                )
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.readFlag != null">
 | 
			
		||||
                AND read_flag = #{query.readFlag}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.startDate != null">
 | 
			
		||||
                AND DATE_FORMAT(create_time, '%Y-%m-%d') >= DATE_FORMAT(#{query.startDate}, '%Y-%m-%d')
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.endDate != null">
 | 
			
		||||
                AND DATE_FORMAT(create_time, '%Y-%m-%d') <= DATE_FORMAT(#{query.endDate}, '%Y-%m-%d')
 | 
			
		||||
            </if>
 | 
			
		||||
        </where>
 | 
			
		||||
        <if test="query.sortItemList == null or query.sortItemList.size == 0">
 | 
			
		||||
            ORDER BY message_id DESC
 | 
			
		||||
        </if>
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="getUnreadCount" resultType="java.lang.Long">
 | 
			
		||||
        SELECT count(1)
 | 
			
		||||
        FROM t_message
 | 
			
		||||
        where receiver_user_type = #{receiverUserType}
 | 
			
		||||
          AND receiver_user_id = #{receiverUserId}
 | 
			
		||||
          AND read_flag = false
 | 
			
		||||
    </select>
 | 
			
		||||
</mapper>
 | 
			
		||||
@@ -7,6 +7,12 @@
 | 
			
		||||
        *
 | 
			
		||||
        from t_operate_log
 | 
			
		||||
        <where>
 | 
			
		||||
            <if test="query.operateUserId != null">
 | 
			
		||||
                AND operate_user_id = #{query.operateUserId}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.operateUserType != null">
 | 
			
		||||
                AND operate_user_type = #{query.operateUserType}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.startDate != null and query.startDate != ''">
 | 
			
		||||
                AND DATE_FORMAT(create_time, '%Y-%m-%d') >= #{query.startDate}
 | 
			
		||||
            </if>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
<?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.job.repository.SmartJobLogDao">
 | 
			
		||||
 | 
			
		||||
    <!-- 定时任务-执行记录-分页查询 -->
 | 
			
		||||
    <select id="query" resultType="net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogVO">
 | 
			
		||||
        SELECT *
 | 
			
		||||
        FROM t_smart_job_log
 | 
			
		||||
        <where>
 | 
			
		||||
            <if test="query.searchWord != null and query.searchWord != ''">
 | 
			
		||||
                AND ( INSTR(job_name,#{query.searchWord})
 | 
			
		||||
                OR INSTR(param,#{query.searchWord})
 | 
			
		||||
                OR INSTR(execute_result,#{query.searchWord})
 | 
			
		||||
                OR INSTR(create_name,#{query.searchWord})
 | 
			
		||||
                )
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.jobId != null">
 | 
			
		||||
                AND job_id = #{query.jobId}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.successFlag != null">
 | 
			
		||||
                AND success_flag = #{query.successFlag}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.startTime != null">
 | 
			
		||||
                AND DATE_FORMAT(execute_start_time, '%Y-%m-%d') >= #{query.startTime}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.endTime != null">
 | 
			
		||||
                AND DATE_FORMAT(execute_start_time, '%Y-%m-%d') <= #{query.endTime}
 | 
			
		||||
            </if>
 | 
			
		||||
        </where>
 | 
			
		||||
        <if test="query.sortItemList == null or query.sortItemList.size == 0">
 | 
			
		||||
            ORDER BY log_id DESC
 | 
			
		||||
        </if>
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
@@ -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.job.repository.SmartJobDao">
 | 
			
		||||
 | 
			
		||||
    <!-- 定时任务-分页查询 -->
 | 
			
		||||
    <select id="query" resultType="net.lab1024.sa.base.module.support.job.api.domain.SmartJobVO">
 | 
			
		||||
        SELECT *
 | 
			
		||||
        FROM t_smart_job
 | 
			
		||||
        <where>
 | 
			
		||||
            <if test="query.searchWord != null and query.searchWord != ''">
 | 
			
		||||
                AND ( INSTR(job_name,#{query.searchWord})
 | 
			
		||||
                OR INSTR(job_class,#{query.searchWord})
 | 
			
		||||
                OR INSTR(trigger_value,#{query.searchWord})
 | 
			
		||||
                )
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.triggerType != null">
 | 
			
		||||
                AND trigger_type = #{query.triggerType}
 | 
			
		||||
            </if>
 | 
			
		||||
            <if test="query.enabledFlag != null">
 | 
			
		||||
                AND enabled_flag = #{query.enabledFlag}
 | 
			
		||||
            </if>
 | 
			
		||||
        </where>
 | 
			
		||||
        <if test="query.sortItemList == null or query.sortItemList.size == 0">
 | 
			
		||||
            ORDER BY sort ASC,job_id DESC
 | 
			
		||||
        </if>
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
@@ -142,3 +142,16 @@ sa-token:
 | 
			
		||||
  is-print: false
 | 
			
		||||
  # 是否从cookie读取token
 | 
			
		||||
  is-read-cookie: false
 | 
			
		||||
 | 
			
		||||
# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com)
 | 
			
		||||
smart:
 | 
			
		||||
  job:
 | 
			
		||||
    enabled: true
 | 
			
		||||
    # 任务初始化延迟 默认30秒 可选
 | 
			
		||||
    init-delay: 10
 | 
			
		||||
    # 定时任务执行线程池数量 默认2 可选
 | 
			
		||||
    core-pool-size: 2
 | 
			
		||||
    # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启)
 | 
			
		||||
    db-refresh-enabled: true
 | 
			
		||||
    # 数据库配置检测-执行间隔 默认120秒 可选
 | 
			
		||||
    db-refresh-interval: 60
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
spring:
 | 
			
		||||
  # 数据库连接信息
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc: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:mysql://127.0.0.1:3306/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: Zhuoda#1024lab
 | 
			
		||||
    initial-size: 10
 | 
			
		||||
    min-idle: 10
 | 
			
		||||
    max-active: 100
 | 
			
		||||
@@ -108,7 +108,7 @@ http:
 | 
			
		||||
    keep-alive: 300000
 | 
			
		||||
 | 
			
		||||
# 跨域配置
 | 
			
		||||
access-control-allow-origin: 'smartadmin.vip'
 | 
			
		||||
access-control-allow-origin: 'https://preview.smartadmin.vip'
 | 
			
		||||
 | 
			
		||||
# 心跳配置
 | 
			
		||||
heart-beat:
 | 
			
		||||
@@ -142,3 +142,16 @@ sa-token:
 | 
			
		||||
  is-print: false
 | 
			
		||||
  # 是否从cookie读取token
 | 
			
		||||
  is-read-cookie: false
 | 
			
		||||
 | 
			
		||||
# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com)
 | 
			
		||||
smart:
 | 
			
		||||
  job:
 | 
			
		||||
    enabled: true
 | 
			
		||||
    # 任务初始化延迟 默认30秒 可选
 | 
			
		||||
    init-delay: 10
 | 
			
		||||
    # 定时任务执行线程池数量 默认2 可选
 | 
			
		||||
    core-pool-size: 2
 | 
			
		||||
    # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启)
 | 
			
		||||
    db-refresh-enabled: true
 | 
			
		||||
    # 数据库配置检测-执行间隔 默认120秒 可选
 | 
			
		||||
    db-refresh-interval: 60
 | 
			
		||||
@@ -74,9 +74,9 @@ file:
 | 
			
		||||
      upload-path: /home/smart_admin_v3/upload/   #文件上传目录
 | 
			
		||||
      url-prefix:
 | 
			
		||||
    cloud:
 | 
			
		||||
      region: oss-cn-qingdao
 | 
			
		||||
      endpoint: oss-cn-qingdao.aliyuncs.com
 | 
			
		||||
      bucket-name: common
 | 
			
		||||
      region: oss-cn-hangzhou
 | 
			
		||||
      endpoint: oss-cn-hangzhou.aliyuncs.com
 | 
			
		||||
      bucket-name: 1024lab-smart-admin
 | 
			
		||||
      access-key:
 | 
			
		||||
      secret-key:
 | 
			
		||||
      url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
 | 
			
		||||
@@ -89,6 +89,7 @@ springdoc:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
    doc-expansion: none  #关闭展开
 | 
			
		||||
    tags-sorter: alpha
 | 
			
		||||
    server-base-url: http://smartadmin.dev.1024lab.net/api/
 | 
			
		||||
  api-docs:
 | 
			
		||||
    enabled: true # 开关
 | 
			
		||||
knife4j:
 | 
			
		||||
@@ -142,3 +143,16 @@ sa-token:
 | 
			
		||||
  is-print: false
 | 
			
		||||
  # 是否从cookie读取token
 | 
			
		||||
  is-read-cookie: false
 | 
			
		||||
 | 
			
		||||
# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com)
 | 
			
		||||
smart:
 | 
			
		||||
  job:
 | 
			
		||||
    enabled: true
 | 
			
		||||
    # 任务初始化延迟 默认30秒 可选
 | 
			
		||||
    init-delay: 10
 | 
			
		||||
    # 定时任务执行线程池数量 默认2 可选
 | 
			
		||||
    core-pool-size: 2
 | 
			
		||||
    # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启)
 | 
			
		||||
    db-refresh-enabled: true
 | 
			
		||||
    # 数据库配置检测-执行间隔 默认120秒 可选
 | 
			
		||||
    db-refresh-interval: 60
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
NODE_ENV=development
 | 
			
		||||
VITE_APP_TITLE='SmartAdmin 开发环境(Dev)'
 | 
			
		||||
VITE_APP_API_URL='http://127.0.0.1:1024'
 | 
			
		||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
NODE_ENV=production
 | 
			
		||||
VITE_APP_TITLE='SmartAdmin 测试环境(Test)'
 | 
			
		||||
VITE_APP_API_URL='http://127.0.0.1:1024'
 | 
			
		||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,16 @@
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "localhost": "vite --mode localhost",
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build:test": "vite build  --mode test",
 | 
			
		||||
    "build:test": "vite build  --base=/admin/ --mode test",
 | 
			
		||||
    "build:pre": "vite build  --mode pre",
 | 
			
		||||
    "build:prod": "vite build  --mode production"
 | 
			
		||||
  },
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@ant-design/icons-vue": "^6.1.0",
 | 
			
		||||
    "@wangeditor/editor": "5.1.14",
 | 
			
		||||
    "@wangeditor/editor-for-vue": "5.1.12",
 | 
			
		||||
    "ant-design-vue": "4.2.0",
 | 
			
		||||
    "ant-design-vue": "4.2.1",
 | 
			
		||||
    "axios": "1.6.8",
 | 
			
		||||
    "clipboard": "2.0.11",
 | 
			
		||||
    "crypto-js": "4.1.1",
 | 
			
		||||
@@ -37,14 +38,15 @@
 | 
			
		||||
    "sortablejs": "1.15.0",
 | 
			
		||||
    "ua-parser-js": "1.0.35",
 | 
			
		||||
    "v-viewer": "~1.6.4",
 | 
			
		||||
    "vue": "3.3.13",
 | 
			
		||||
    "vue-i18n": "9.10.2",
 | 
			
		||||
    "vue-router": "4.3.0",
 | 
			
		||||
    "vue3-json-viewer": "2.2.2"
 | 
			
		||||
    "vue": "3.4.27",
 | 
			
		||||
    "vue-i18n": "9.13.1",
 | 
			
		||||
    "vue-router": "4.3.2",
 | 
			
		||||
    "vue3-json-viewer": "2.2.2",
 | 
			
		||||
    "vue3-tabs-chrome": "^0.3.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@vitejs/plugin-vue": "5.0.4",
 | 
			
		||||
    "@vue/compiler-sfc": "3.4.21",
 | 
			
		||||
    "@vue/compiler-sfc": "3.4.27",
 | 
			
		||||
    "eslint": "^8.16.0",
 | 
			
		||||
    "eslint-config-prettier": "~9.0.0",
 | 
			
		||||
    "eslint-plugin-prettier": "~5.0.0",
 | 
			
		||||
@@ -58,7 +60,7 @@
 | 
			
		||||
    "stylelint-config-standard": "~25.0.0",
 | 
			
		||||
    "stylelint-order": "~5.0.0",
 | 
			
		||||
    "terser": "~5.29.2",
 | 
			
		||||
    "vite": "5.2.6",
 | 
			
		||||
    "vite": "5.2.12",
 | 
			
		||||
    "vue-eslint-parser": "~9.4.2"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * job api
 | 
			
		||||
 *
 | 
			
		||||
 * @Author:    huke
 | 
			
		||||
 * @Date:      2024/06/25
 | 
			
		||||
 */
 | 
			
		||||
import { postRequest, getRequest } from '/src/lib/axios';
 | 
			
		||||
 | 
			
		||||
export const jobApi = {
 | 
			
		||||
  // 分页查询 @huke
 | 
			
		||||
  queryJob: (param) => {
 | 
			
		||||
    return postRequest('/support/job/query', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 定时任务-查询详情 @huke
 | 
			
		||||
  queryJobInfo: (param) => {
 | 
			
		||||
    return getRequest(`/support/job/${param}`);
 | 
			
		||||
  },
 | 
			
		||||
  // 执行任务 @huke
 | 
			
		||||
  executeJob: (param) => {
 | 
			
		||||
    return postRequest('/support/job/execute', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 定时任务-更新-任务信息 @huke
 | 
			
		||||
  updateJob: (param) => {
 | 
			
		||||
    return postRequest('/support/job/update', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 定时任务-更新-开启状态 @huke
 | 
			
		||||
  updateJobEnabled: (param) => {
 | 
			
		||||
    return postRequest('/support/job/update/enabled', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 定时任务-执行记录-分页查询 @huke
 | 
			
		||||
  queryJobLog: (param) => {
 | 
			
		||||
    return postRequest('/support/job/log/query', param);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -14,4 +14,8 @@ export const loginLogApi = {
 | 
			
		||||
  queryList: (param) => {
 | 
			
		||||
    return postRequest('/support/loginLog/page/query', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 分页查询当前登录人信息 @author 善逸
 | 
			
		||||
  queryListLogin: (param) => {
 | 
			
		||||
    return postRequest('/support/loginLog/page/query/login', param);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
import { getRequest, postRequest } from '/src/lib/axios';
 | 
			
		||||
 | 
			
		||||
export const messageApi = {
 | 
			
		||||
  // 通知消息-分页查询
 | 
			
		||||
  queryMessage: (param) => {
 | 
			
		||||
    return postRequest('/support/message/queryMyMessage', param);
 | 
			
		||||
  },
 | 
			
		||||
  // 通知消息-查询未读消息数
 | 
			
		||||
  queryUnreadCount: () => {
 | 
			
		||||
    return getRequest('/support/message/getUnreadCount');
 | 
			
		||||
  },
 | 
			
		||||
  // 通知消息-标记已读
 | 
			
		||||
  updateReadFlag: (messageId) => {
 | 
			
		||||
    return getRequest(`/support/message/read/${messageId}`);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -18,4 +18,8 @@ export const operateLogApi = {
 | 
			
		||||
  detail: (id) => {
 | 
			
		||||
    return getRequest(`/support/operateLog/detail/${id}`);
 | 
			
		||||
  },
 | 
			
		||||
  // 分页查询当前登录人信息 @author 善逸
 | 
			
		||||
  queryListLogin: (param) => {
 | 
			
		||||
    return postRequest('/support/operateLog/page/query/login', param);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,18 @@ export const employeeApi = {
 | 
			
		||||
  updateEmployee: (params) => {
 | 
			
		||||
    return postRequest('/employee/update', params);
 | 
			
		||||
  },
 | 
			
		||||
  /**
 | 
			
		||||
   * 更新登录人信息
 | 
			
		||||
   */
 | 
			
		||||
  updateByLogin: (params) => {
 | 
			
		||||
    return postRequest('/employee/update/login', params);
 | 
			
		||||
  },
 | 
			
		||||
  /**
 | 
			
		||||
   * 更新登录人头像
 | 
			
		||||
   */
 | 
			
		||||
  updateAvatar: (params) => {
 | 
			
		||||
    return postRequest('/employee/update/avatar', params);
 | 
			
		||||
  },
 | 
			
		||||
  /**
 | 
			
		||||
   * 删除员工
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 职务表 api 封装
 | 
			
		||||
 *
 | 
			
		||||
 * @Author:    kaiyun
 | 
			
		||||
 * @Date:      2024-06-23 23:31:38
 | 
			
		||||
 * @Copyright  <a href="https://1024lab.net">1024创新实验室</a>
 | 
			
		||||
 */
 | 
			
		||||
import { postRequest, getRequest } from '/@/lib/axios';
 | 
			
		||||
 | 
			
		||||
export const positionApi = {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 分页查询  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  queryPage : (param) => {
 | 
			
		||||
    return postRequest('/position/queryPage', param);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 增加  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  add: (param) => {
 | 
			
		||||
      return postRequest('/position/add', param);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 修改  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  update: (param) => {
 | 
			
		||||
      return postRequest('/position/update', param);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 删除  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  delete: (id) => {
 | 
			
		||||
      return getRequest(`/position/delete/${id}`);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 批量删除  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  batchDelete: (idList) => {
 | 
			
		||||
    return postRequest('/position/batchDelete', idList);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 查询列表  @author  kaiyun
 | 
			
		||||
   */
 | 
			
		||||
  queryList: () => {
 | 
			
		||||
    return getRequest('/position/queryList');
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
<!--
 | 
			
		||||
  * 职位
 | 
			
		||||
  *
 | 
			
		||||
  * @Author:    开云
 | 
			
		||||
  * @Date:      2024-06-27 23:09:02
 | 
			
		||||
  * @Wechat:    kaiyun
 | 
			
		||||
  * @Email:     lab1024@163.com
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <a-select
 | 
			
		||||
    v-model:value="selectValue"
 | 
			
		||||
    :style="`width: ${width}`"
 | 
			
		||||
    :placeholder="props.placeholder"
 | 
			
		||||
    :showSearch="true"
 | 
			
		||||
    :allowClear="true"
 | 
			
		||||
    :size="size"
 | 
			
		||||
    @change="onChange"
 | 
			
		||||
  >
 | 
			
		||||
    <a-select-option v-for="item in positionList" :key="item.positionId" :value="item.positionId">
 | 
			
		||||
      {{ item.positionName }}
 | 
			
		||||
    </a-select-option>
 | 
			
		||||
  </a-select>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { onMounted, ref, watch } from 'vue';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import { positionApi } from '/@/api/system/position-api.js';
 | 
			
		||||
 | 
			
		||||
  // =========== 属性定义 和 事件方法暴露 =============
 | 
			
		||||
 | 
			
		||||
  const props = defineProps({
 | 
			
		||||
    value: [Number, Array],
 | 
			
		||||
    placeholder: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '请选择',
 | 
			
		||||
    },
 | 
			
		||||
    width: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '100%',
 | 
			
		||||
    },
 | 
			
		||||
    size: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'default',
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const emit = defineEmits(['update:value', 'change']);
 | 
			
		||||
 | 
			
		||||
  // =========== 查询数据 =============
 | 
			
		||||
 | 
			
		||||
  //员工列表数据
 | 
			
		||||
  const positionList = ref([]);
 | 
			
		||||
  async function query() {
 | 
			
		||||
    try {
 | 
			
		||||
      let resp = await positionApi.queryList();
 | 
			
		||||
      positionList.value = resp.data;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      smartSentry.captureError(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  onMounted(query);
 | 
			
		||||
 | 
			
		||||
  // =========== 选择 监听、事件 =============
 | 
			
		||||
 | 
			
		||||
  const selectValue = ref(props.value);
 | 
			
		||||
  watch(
 | 
			
		||||
    () => props.value,
 | 
			
		||||
    (newValue) => {
 | 
			
		||||
      selectValue.value = newValue;
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  function onChange(value) {
 | 
			
		||||
    emit('update:value', value);
 | 
			
		||||
    emit('change', value);
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user