diff --git a/core/src/main/java/com/dite/znpt/config/Schedule.java b/core/src/main/java/com/dite/znpt/config/Schedule.java index 6e38623..6b3c65a 100644 --- a/core/src/main/java/com/dite/znpt/config/Schedule.java +++ b/core/src/main/java/com/dite/znpt/config/Schedule.java @@ -7,6 +7,7 @@ import com.dite.znpt.domain.vo.PartResp; import com.dite.znpt.enums.FilePathEnum; import com.dite.znpt.service.ImageService; import com.dite.znpt.service.PartService; +import com.dite.znpt.service.job.AutoExpirationJobService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -26,6 +27,7 @@ public class Schedule { private final ImageService imageService; private final PartService partService; private final ExtUtilConfig extUtilConfig; + private final AutoExpirationJobService autoExpirationJobService; /** * 功能描述:图像预处理,持续运行 @@ -67,4 +69,16 @@ public class Schedule { } imageService.updateBatchById(successList); } + + /** + * 功能描述:自动到期检测,每天凌晨2点执行 + * + * @author System + * @date 2025/1/1 + **/ + @Scheduled(cron = "0 0 2 * * ?") + public void autoExpirationCheck() { + log.info("开始执行自动到期检测定时任务..."); + autoExpirationJobService.executeAutoExpirationJob(); + } } diff --git a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java index c09ff82..718f7ff 100644 --- a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java +++ b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java @@ -2,10 +2,14 @@ package com.dite.znpt.config; import cn.hutool.core.collection.ListUtil; import com.dite.znpt.enums.FilePathEnum; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -28,6 +32,14 @@ public class WebMvcConfig implements WebMvcConfigurer { } } + /** + * 配置HTTP消息转换器,确保UTF-8编码 + */ + @Override + public void configureMessageConverters(List> converters) { + converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); + } + // @Override // public void addInterceptors(InterceptorRegistry registry) { // // 注册 Sa-Token 拦截器,定义详细认证规则 diff --git a/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java new file mode 100644 index 0000000..7d9f608 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/AutoExpirationConfigEntity.java @@ -0,0 +1,84 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测配置实体 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("auto_expiration_config") +@ApiModel(value="AutoExpirationConfigEntity对象", description="自动到期检测配置") +public class AutoExpirationConfigEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ApiModelProperty("配置ID") + @TableId(value = "config_id", type = IdType.ASSIGN_UUID) + private String configId; + + @ApiModelProperty("表名") + @TableField("table_name") + private String tableName; + + @ApiModelProperty("表描述") + @TableField("table_desc") + private String tableDesc; + + @ApiModelProperty("主键字段") + @TableField("primary_key_field") + private String primaryKeyField; + + @ApiModelProperty("到期时间字段") + @TableField("expire_date_field") + private String expireDateField; + + @ApiModelProperty("关联用户字段") + @TableField("user_id_field") + private String userIdField; + + @ApiModelProperty("用户姓名字段") + @TableField("user_name_field") + private String userNameField; + + @ApiModelProperty("业务名称字段") + @TableField("business_name_field") + private String businessNameField; + + @ApiModelProperty("提前提醒天数") + @TableField("remind_days") + private Integer remindDays; + + @ApiModelProperty("是否启用(0-禁用,1-启用)") + @TableField("enabled") + private String enabled; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + @ApiModelProperty("删除标志(0代表存在,1代表删除)") + @TableField("del_flag") + private String delFlag; + + /** + * 保存前处理 + */ + public void preSave() { + // 可以在这里添加保存前的逻辑处理 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java new file mode 100644 index 0000000..fb2fc23 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/ExpirationResultEntity.java @@ -0,0 +1,85 @@ +package com.dite.znpt.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @author System + * @date 2025/1/1 + * @description 到期检测结果实体 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("expiration_result") +@ApiModel(value="ExpirationResultEntity对象", description="到期检测结果") +public class ExpirationResultEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ApiModelProperty("结果ID") + @TableId(value = "result_id", type = IdType.ASSIGN_UUID) + private String resultId; + + @ApiModelProperty("配置ID") + @TableField("config_id") + private String configId; + + @ApiModelProperty("表名") + @TableField("table_name") + private String tableName; + + @ApiModelProperty("业务ID") + @TableField("business_id") + private String businessId; + + @ApiModelProperty("业务名称") + @TableField("business_name") + private String businessName; + + @ApiModelProperty("关联用户ID") + @TableField("user_id") + private String userId; + + @ApiModelProperty("关联用户姓名") + @TableField("user_name") + private String userName; + + @ApiModelProperty("到期日期") + @TableField("expire_date") + private LocalDate expireDate; + + @ApiModelProperty("剩余天数") + @TableField("remaining_days") + private Integer remainingDays; + + @ApiModelProperty("状态(0-正常,1-即将到期,2-已到期)") + @TableField("status") + private String status; + + @ApiModelProperty("检测时间") + @TableField("check_time") + private java.time.LocalDateTime checkTime; + + @ApiModelProperty("删除标志(0代表存在,1代表删除)") + @TableField("del_flag") + private String delFlag; + + /** + * 保存前处理 + */ + public void preSave() { + // 可以在这里添加保存前的逻辑处理 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java b/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java new file mode 100644 index 0000000..5ea8531 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/AutoExpirationConfigMapper.java @@ -0,0 +1,14 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测配置Mapper + */ +@Mapper +public interface AutoExpirationConfigMapper extends BaseMapper { +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java b/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java new file mode 100644 index 0000000..b6d3991 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/ExpirationResultMapper.java @@ -0,0 +1,14 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author System + * @date 2025/1/1 + * @description 到期检测结果Mapper + */ +@Mapper +public interface ExpirationResultMapper extends BaseMapper { +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java b/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java new file mode 100644 index 0000000..35d2b76 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/AutoExpirationService.java @@ -0,0 +1,61 @@ +package com.dite.znpt.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; + +import java.util.List; +import java.util.Map; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测服务接口 + */ +public interface AutoExpirationService extends IService { + + /** + * 执行自动到期检测 + */ + void executeAutoExpirationCheck(); + + /** + * 获取到期检测结果 + * @param status 状态筛选 + * @return 检测结果列表 + */ + List getExpirationResults(String status); + + /** + * 获取统计信息 + * @return 统计信息 + */ + Map getStatistics(); + + /** + * 手动检测指定表 + * @param tableName 表名 + */ + void checkTableExpiration(String tableName); + + /** + * 检查表是否已配置到期检测 + * @param tableName 表名 + * @return 配置信息,如果未配置返回null + */ + AutoExpirationConfigEntity getTableConfig(String tableName); + + /** + * 批量检查多个表的配置状态 + * @param tableNames 表名列表 + * @return 配置状态映射 + */ + Map getTablesConfigStatus(List tableNames); + + /** + * 智能配置表检测 + * @param tableName 表名 + * @return 配置结果 + */ + AutoExpirationConfigEntity autoConfigureTable(String tableName); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java new file mode 100644 index 0000000..ffa2ba7 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/AutoExpirationServiceImpl.java @@ -0,0 +1,341 @@ +package com.dite.znpt.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import com.dite.znpt.mapper.AutoExpirationConfigMapper; +import com.dite.znpt.mapper.ExpirationResultMapper; +import com.dite.znpt.service.AutoExpirationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测服务实现类 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class AutoExpirationServiceImpl extends ServiceImpl implements AutoExpirationService { + + @Autowired + private ExpirationResultMapper expirationResultMapper; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public void executeAutoExpirationCheck() { + log.info("开始执行自动到期检测..."); + + // 获取所有启用的配置 + List configs = lambdaQuery() + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .list(); + + if (CollUtil.isEmpty(configs)) { + log.info("没有找到启用的到期检测配置"); + return; + } + + // 清空之前的检测结果 + expirationResultMapper.delete(null); + + // 逐个检测每个配置的表 + for (AutoExpirationConfigEntity config : configs) { + try { + checkTableExpiration(config); + } catch (Exception e) { + log.error("检测表 {} 时发生错误: {}", config.getTableName(), e.getMessage(), e); + } + } + + log.info("自动到期检测完成,共检测 {} 个表", configs.size()); + } + + @Override + public List getExpirationResults(String status) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ExpirationResultEntity::getDelFlag, "0"); + + if (StrUtil.isNotBlank(status)) { + wrapper.eq(ExpirationResultEntity::getStatus, status); + } + + wrapper.orderByDesc(ExpirationResultEntity::getExpireDate); + + return expirationResultMapper.selectList(wrapper); + } + + @Override + public Map getStatistics() { + Map statistics = new HashMap<>(); + + // 统计各状态的数量 + List allResults = expirationResultMapper.selectList(null); + + long normalCount = allResults.stream() + .filter(r -> "0".equals(r.getStatus())) + .count(); + long expiringCount = allResults.stream() + .filter(r -> "1".equals(r.getStatus())) + .count(); + long expiredCount = allResults.stream() + .filter(r -> "2".equals(r.getStatus())) + .count(); + + statistics.put("total", allResults.size()); + statistics.put("normal", normalCount); + statistics.put("expiring", expiringCount); + statistics.put("expired", expiredCount); + + return statistics; + } + + @Override + public void checkTableExpiration(String tableName) { + AutoExpirationConfigEntity config = lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, tableName) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .one(); + + if (config == null) { + throw new RuntimeException("未找到表 " + tableName + " 的配置信息"); + } + + checkTableExpiration(config); + } + + /** + * 检测指定配置的表 + */ + private void checkTableExpiration(AutoExpirationConfigEntity config) { + log.info("开始检测表: {}", config.getTableName()); + + // 构建查询SQL + String sql = buildQuerySql(config); + + try { + List> results = jdbcTemplate.queryForList(sql); + + for (Map row : results) { + processExpirationResult(config, row); + } + + log.info("表 {} 检测完成,共处理 {} 条记录", config.getTableName(), results.size()); + + } catch (Exception e) { + log.error("检测表 {} 时发生错误: {}", config.getTableName(), e.getMessage(), e); + } + } + + /** + * 构建查询SQL + */ + private String buildQuerySql(AutoExpirationConfigEntity config) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT "); + sql.append(config.getPrimaryKeyField()).append(" as business_id, "); + sql.append(config.getExpireDateField()).append(" as expire_date "); + + if (StrUtil.isNotBlank(config.getUserIdField())) { + sql.append(", ").append(config.getUserIdField()).append(" as user_id "); + } + if (StrUtil.isNotBlank(config.getUserNameField())) { + sql.append(", ").append(config.getUserNameField()).append(" as user_name "); + } + if (StrUtil.isNotBlank(config.getBusinessNameField())) { + sql.append(", ").append(config.getBusinessNameField()).append(" as business_name "); + } + + sql.append(" FROM ").append(config.getTableName()); + sql.append(" WHERE ").append(config.getExpireDateField()).append(" IS NOT NULL"); + + return sql.toString(); + } + + /** + * 处理到期检测结果 + */ + private void processExpirationResult(AutoExpirationConfigEntity config, Map row) { + try { + ExpirationResultEntity result = new ExpirationResultEntity(); + result.preSave(); + + result.setConfigId(config.getConfigId()); + result.setTableName(config.getTableName()); + result.setBusinessId(String.valueOf(row.get("business_id"))); + result.setBusinessName(String.valueOf(row.get("business_name"))); + result.setUserId(String.valueOf(row.get("user_id"))); + result.setUserName(String.valueOf(row.get("user_name"))); + + // 处理到期日期 + Object expireDateObj = row.get("expire_date"); + if (expireDateObj != null) { + LocalDate expireDate = parseDate(expireDateObj); + result.setExpireDate(expireDate); + + // 计算剩余天数和状态 + LocalDate today = LocalDate.now(); + long remainingDays = ChronoUnit.DAYS.between(today, expireDate); + result.setRemainingDays((int) remainingDays); + + // 设置状态 + if (remainingDays < 0) { + result.setStatus("2"); // 已到期 + } else if (remainingDays <= config.getRemindDays()) { + result.setStatus("1"); // 即将到期 + } else { + result.setStatus("0"); // 正常 + } + } + + result.setCheckTime(LocalDateTime.now()); + result.setDelFlag("0"); + + expirationResultMapper.insert(result); + + } catch (Exception e) { + log.error("处理检测结果时发生错误: {}", e.getMessage(), e); + } + } + + /** + * 解析日期 + */ + private LocalDate parseDate(Object dateObj) { + if (dateObj instanceof LocalDate) { + return (LocalDate) dateObj; + } else if (dateObj instanceof java.sql.Date) { + return ((java.sql.Date) dateObj).toLocalDate(); + } else if (dateObj instanceof java.util.Date) { + // 使用 DateUtil.toLocalDateTime 然后转换为 LocalDate + return DateUtil.toLocalDateTime((java.util.Date) dateObj).toLocalDate(); + } else { + return LocalDate.parse(dateObj.toString()); + } + } + + @Override + public AutoExpirationConfigEntity getTableConfig(String tableName) { + return lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, tableName) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .one(); + } + + @Override + public Map getTablesConfigStatus(List tableNames) { + Map statusMap = new HashMap<>(); + + if (CollUtil.isEmpty(tableNames)) { + return statusMap; + } + + // 查询所有已配置的表 + List configs = lambdaQuery() + .in(AutoExpirationConfigEntity::getTableName, tableNames) + .eq(AutoExpirationConfigEntity::getEnabled, "1") + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .list(); + + // 构建已配置的表名集合 + Set configuredTables = configs.stream() + .map(AutoExpirationConfigEntity::getTableName) + .collect(Collectors.toSet()); + + // 为每个表设置配置状态 + for (String tableName : tableNames) { + statusMap.put(tableName, configuredTables.contains(tableName)); + } + + return statusMap; + } + + @Override + public AutoExpirationConfigEntity autoConfigureTable(String tableName) { + // 检查是否已配置 + AutoExpirationConfigEntity existingConfig = getTableConfig(tableName); + if (existingConfig != null) { + log.info("表 {} 已存在配置", tableName); + return existingConfig; + } + + // 根据表名生成默认配置 + AutoExpirationConfigEntity config = generateDefaultConfig(tableName); + + try { + config.preSave(); + save(config); + log.info("为表 {} 自动创建配置", tableName); + return config; + } catch (Exception e) { + log.error("自动配置表 {} 失败: {}", tableName, e.getMessage(), e); + throw new RuntimeException("自动配置失败: " + e.getMessage()); + } + } + + /** + * 根据表名生成默认配置 + */ + private AutoExpirationConfigEntity generateDefaultConfig(String tableName) { + AutoExpirationConfigEntity config = new AutoExpirationConfigEntity(); + config.setConfigId("config-" + System.currentTimeMillis()); + config.setTableName(tableName); + config.setEnabled("1"); + config.setDelFlag("0"); + config.setRemindDays(30); + config.setRemark("自动配置的到期检测"); + + // 根据表名设置默认字段 + switch (tableName) { + case "certification": + config.setTableDesc("证书到期检测"); + config.setPrimaryKeyField("certification_id"); + config.setExpireDateField("validity_date_end"); + config.setUserIdField("user_id"); + config.setBusinessNameField("certification_name"); + break; + case "insurance_info": + config.setTableDesc("保险到期检测"); + config.setPrimaryKeyField("insurance_id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("user_id"); + config.setBusinessNameField("insurance_name"); + break; + case "regulation": + config.setTableDesc("规章制度到期检测"); + config.setPrimaryKeyField("regulation_id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("create_by"); + config.setBusinessNameField("regulation_name"); + break; + default: + // 通用配置 + config.setTableDesc(tableName + "到期检测"); + config.setPrimaryKeyField("id"); + config.setExpireDateField("expire_date"); + config.setUserIdField("user_id"); + config.setBusinessNameField("name"); + break; + } + + return config; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java b/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java new file mode 100644 index 0000000..dba4ee1 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/job/AutoExpirationJobService.java @@ -0,0 +1,14 @@ +package com.dite.znpt.service.job; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测定时任务服务接口 + */ +public interface AutoExpirationJobService { + + /** + * 执行自动到期检测任务 + */ + void executeAutoExpirationJob(); +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java b/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java new file mode 100644 index 0000000..a0bd57b --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/job/impl/AutoExpirationJobServiceImpl.java @@ -0,0 +1,31 @@ +package com.dite.znpt.service.job.impl; + +import com.dite.znpt.service.AutoExpirationService; +import com.dite.znpt.service.job.AutoExpirationJobService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author System + * @date 2025/1/1 + * @description 自动到期检测定时任务服务实现类 + */ +@Slf4j +@Service +public class AutoExpirationJobServiceImpl implements AutoExpirationJobService { + + @Autowired + private AutoExpirationService autoExpirationService; + + @Override + public void executeAutoExpirationJob() { + log.info("开始执行自动到期检测定时任务..."); + try { + autoExpirationService.executeAutoExpirationCheck(); + log.info("自动到期检测定时任务执行完成"); + } catch (Exception e) { + log.error("自动到期检测定时任务执行失败: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/doc/auto_expiration_readme.md b/doc/auto_expiration_readme.md new file mode 100644 index 0000000..c3a0041 --- /dev/null +++ b/doc/auto_expiration_readme.md @@ -0,0 +1,138 @@ +# 自动到期检测功能使用说明 + +## 功能概述 + +自动到期检测功能是一个智能的到期时间监控系统,能够自动检测数据库中各种表格的到期时间字段,并提供到期提醒功能。 + +## 主要特性 + +1. **自动检测**: 系统会自动扫描配置的表格,检测其中的到期时间字段 +2. **灵活配置**: 可以配置任意表格的到期检测规则 +3. **智能提醒**: 支持提前提醒天数配置 +4. **状态管理**: 自动计算剩余天数并设置状态(正常/即将到期/已到期) +5. **定时执行**: 每天凌晨2点自动执行检测任务 + +## 数据库表结构 + +### 1. 自动到期检测配置表 (auto_expiration_config) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| config_id | varchar(64) | 配置ID | +| table_name | varchar(100) | 表名 | +| table_desc | varchar(200) | 表描述 | +| primary_key_field | varchar(100) | 主键字段 | +| expire_date_field | varchar(100) | 到期时间字段 | +| user_id_field | varchar(100) | 关联用户字段 | +| user_name_field | varchar(100) | 用户姓名字段 | +| business_name_field | varchar(100) | 业务名称字段 | +| remind_days | int | 提前提醒天数 | +| enabled | varchar(1) | 是否启用(0-禁用,1-启用) | + +### 2. 到期检测结果表 (expiration_result) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| result_id | varchar(64) | 结果ID | +| table_name | varchar(100) | 表名 | +| business_id | varchar(64) | 业务ID | +| business_name | varchar(200) | 业务名称 | +| user_id | varchar(64) | 关联用户ID | +| user_name | varchar(100) | 关联用户姓名 | +| expire_date | date | 到期日期 | +| remaining_days | int | 剩余天数 | +| status | varchar(1) | 状态(0-正常,1-即将到期,2-已到期) | + +## API接口 + +### 1. 手动执行到期检测 +``` +POST /auto-expiration/execute +``` + +### 2. 获取到期检测结果 +``` +GET /auto-expiration/results?status=1 +``` +参数说明: +- status: 状态筛选(0-正常,1-即将到期,2-已到期) + +### 3. 获取统计信息 +``` +GET /auto-expiration/statistics +``` + +### 4. 检测指定表 +``` +POST /auto-expiration/check-table/{tableName} +``` + +### 5. 保存配置 +``` +POST /auto-expiration/config +``` + +### 6. 获取所有配置 +``` +GET /auto-expiration/configs +``` + +### 7. 删除配置 +``` +DELETE /auto-expiration/config/{configId} +``` + +## 配置示例 + +### 人员资质证书检测配置 +```json +{ + "tableName": "certification", + "tableDesc": "人员资质证书", + "primaryKeyField": "certification_id", + "expireDateField": "validity_date_end", + "userIdField": "user_id", + "businessNameField": "certification_name", + "remindDays": 30, + "enabled": "1" +} +``` + +### 人员保险检测配置 +```json +{ + "tableName": "insurance_info", + "tableDesc": "人员保险", + "primaryKeyField": "insurance_info_id", + "expireDateField": "expire_date", + "userIdField": "user_id", + "userNameField": "name", + "businessNameField": "insurance_type_name", + "remindDays": 30, + "enabled": "1" +} +``` + +## 使用步骤 + +1. **创建数据库表**: 执行 `auto_expiration_tables.sql` 创建必要的表结构 +2. **配置检测规则**: 通过API或直接插入数据配置需要检测的表格 +3. **启动定时任务**: 系统会自动在每天凌晨2点执行检测 +4. **查看检测结果**: 通过API接口查看检测结果和统计信息 + +## 扩展说明 + +系统支持检测任意表格的到期时间字段,只需要在配置表中添加相应的配置即可。支持的字段类型包括: +- LocalDate +- LocalDateTime +- Date +- Timestamp + +系统会自动识别并处理这些日期类型。 + +## 注意事项 + +1. 确保配置的字段名在目标表中存在 +2. 到期时间字段不能为空 +3. 建议为检测结果表添加适当的索引以提高查询性能 +4. 可以根据业务需要调整提前提醒天数 \ No newline at end of file diff --git a/doc/auto_expiration_tables.sql b/doc/auto_expiration_tables.sql new file mode 100644 index 0000000..793373c --- /dev/null +++ b/doc/auto_expiration_tables.sql @@ -0,0 +1,51 @@ +-- 自动到期检测配置表 +CREATE TABLE `auto_expiration_config` ( + `config_id` varchar(64) NOT NULL COMMENT '配置ID', + `table_name` varchar(100) NOT NULL COMMENT '表名', + `table_desc` varchar(200) DEFAULT NULL COMMENT '表描述', + `primary_key_field` varchar(100) NOT NULL COMMENT '主键字段', + `expire_date_field` varchar(100) NOT NULL COMMENT '到期时间字段', + `user_id_field` varchar(100) DEFAULT NULL COMMENT '关联用户字段', + `user_name_field` varchar(100) DEFAULT NULL COMMENT '用户姓名字段', + `business_name_field` varchar(100) DEFAULT NULL COMMENT '业务名称字段', + `remind_days` int(11) DEFAULT 30 COMMENT '提前提醒天数', + `enabled` varchar(1) DEFAULT '1' COMMENT '是否启用(0-禁用,1-启用)', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', + `del_flag` varchar(1) DEFAULT '0' COMMENT '删除标志(0代表存在,1代表删除)', + PRIMARY KEY (`config_id`), + UNIQUE KEY `uk_table_name` (`table_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自动到期检测配置表'; + +-- 到期检测结果表 +CREATE TABLE `expiration_result` ( + `result_id` varchar(64) NOT NULL COMMENT '结果ID', + `config_id` varchar(64) DEFAULT NULL COMMENT '配置ID', + `table_name` varchar(100) NOT NULL COMMENT '表名', + `business_id` varchar(64) NOT NULL COMMENT '业务ID', + `business_name` varchar(200) DEFAULT NULL COMMENT '业务名称', + `user_id` varchar(64) DEFAULT NULL COMMENT '关联用户ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '关联用户姓名', + `expire_date` date NOT NULL COMMENT '到期日期', + `remaining_days` int(11) DEFAULT NULL COMMENT '剩余天数', + `status` varchar(1) DEFAULT '0' COMMENT '状态(0-正常,1-即将到期,2-已到期)', + `check_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '检测时间', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', + `del_flag` varchar(1) DEFAULT '0' COMMENT '删除标志(0代表存在,1代表删除)', + PRIMARY KEY (`result_id`), + KEY `idx_table_name` (`table_name`), + KEY `idx_status` (`status`), + KEY `idx_expire_date` (`expire_date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='到期检测结果表'; + +-- 插入示例配置数据 +INSERT INTO `auto_expiration_config` (`config_id`, `table_name`, `table_desc`, `primary_key_field`, `expire_date_field`, `user_id_field`, `user_name_field`, `business_name_field`, `remind_days`, `enabled`, `remark`) VALUES +('1', 'certification', '人员资质证书', 'certification_id', 'validity_date_end', 'user_id', NULL, 'certification_name', 30, '1', '人员资质证书到期检测'), +('2', 'insurance_info', '人员保险', 'insurance_info_id', 'expire_date', 'user_id', 'name', 'insurance_type_name', 30, '1', '人员保险到期检测'), +('3', 'regulation', '制度管理', 'regulation_id', 'expire_time', NULL, NULL, 'regulation_name', 30, '1', '制度到期检测'); \ No newline at end of file diff --git a/web/pom.xml b/web/pom.xml index c6d6423..4dd81fc 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -20,11 +20,11 @@ - - com.dite.znpt - sip - 1.0.0-SNAPSHOT - + + + + + com.dite.znpt core diff --git a/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java b/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java new file mode 100644 index 0000000..9b27a29 --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/AutoExpirationController.java @@ -0,0 +1,178 @@ +package com.dite.znpt.web.controller; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.AutoExpirationConfigEntity; +import com.dite.znpt.domain.entity.ExpirationResultEntity; +import com.dite.znpt.service.AutoExpirationService; +import com.dite.znpt.service.job.AutoExpirationJobService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * @author wangna + * @date 2025/08/05 + * @description 自动到期检测控制器 + */ +@Slf4j +@RestController +@RequestMapping("/auto-expiration") +@Api(tags = "自动到期检测") +public class AutoExpirationController { + + @Autowired + private AutoExpirationService autoExpirationService; + + @Autowired + private AutoExpirationJobService autoExpirationJobService; + + @PostMapping("/execute") + @ApiOperation("手动执行到期检测") + public Result executeExpirationCheck() { + try { + autoExpirationService.executeAutoExpirationCheck(); + return Result.okM("到期检测执行成功"); + } catch (Exception e) { + log.error("执行到期检测失败: {}", e.getMessage(), e); + return Result.error("执行到期检测失败: " + e.getMessage()); + } + } + + @GetMapping("/results") + @ApiOperation("获取到期检测结果") + public Result> getExpirationResults( + @ApiParam("状态筛选:0-正常,1-即将到期,2-已到期") @RequestParam(required = false) String status) { + try { + List results = autoExpirationService.getExpirationResults(status); + return Result.ok(results); + } catch (Exception e) { + log.error("获取到期检测结果失败: {}", e.getMessage(), e); + return Result.error("获取到期检测结果失败: " + e.getMessage()); + } + } + + @GetMapping("/statistics") + @ApiOperation("获取统计信息") + public Result> getStatistics() { + try { + Map statistics = autoExpirationService.getStatistics(); + return Result.ok(statistics); + } catch (Exception e) { + log.error("获取统计信息失败: {}", e.getMessage(), e); + return Result.error("获取统计信息失败: " + e.getMessage()); + } + } + + @PostMapping("/check-table/{tableName}") + @ApiOperation("检测指定表") + public Result checkTableExpiration(@PathVariable String tableName) { + try { + autoExpirationService.checkTableExpiration(tableName); + return Result.okM("表 " + tableName + " 检测完成"); + } catch (Exception e) { + log.error("检测表 {} 失败: {}", tableName, e.getMessage(), e); + return Result.error("检测表失败: " + e.getMessage()); + } + } + + @PostMapping("/config") + @ApiOperation("保存配置") + public Result saveConfig(@RequestBody AutoExpirationConfigEntity config) { + try { + // 检查是否已存在相同表的配置 + AutoExpirationConfigEntity existingConfig = autoExpirationService.lambdaQuery() + .eq(AutoExpirationConfigEntity::getTableName, config.getTableName()) + .eq(AutoExpirationConfigEntity::getDelFlag, "0") + .one(); + + if (existingConfig != null) { + // 如果存在,更新配置 + config.setConfigId(existingConfig.getConfigId()); + config.setCreateTime(existingConfig.getCreateTime()); + config.setCreateBy(existingConfig.getCreateBy()); + log.info("更新已存在的配置: {}", config.getTableName()); + } else { + // 如果不存在,生成新的配置ID + config.setConfigId("config-" + System.currentTimeMillis()); + log.info("创建新配置: {}", config.getTableName()); + } + + config.preSave(); + autoExpirationService.saveOrUpdate(config); + return Result.ok(config); + } catch (Exception e) { + log.error("保存配置失败: {}", e.getMessage(), e); + return Result.error("保存配置失败: " + e.getMessage()); + } + } + + @GetMapping("/configs") + @ApiOperation("获取所有配置") + public Result> getConfigs() { + try { + List configs = autoExpirationService.list(); + return Result.ok(configs); + } catch (Exception e) { + log.error("获取配置失败: {}", e.getMessage(), e); + return Result.error("获取配置失败: " + e.getMessage()); + } + } + + @DeleteMapping("/config/{configId}") + @ApiOperation("删除配置") + public Result deleteConfig(@PathVariable String configId) { + try { + autoExpirationService.removeById(configId); + return Result.okM("配置删除成功"); + } catch (Exception e) { + log.error("删除配置失败: {}", e.getMessage(), e); + return Result.error("删除配置失败: " + e.getMessage()); + } + } + + @GetMapping("/check-table/{tableName}") + @ApiOperation("检查表是否已配置到期检测") + public Result checkTableConfig(@PathVariable String tableName) { + try { + AutoExpirationConfigEntity config = autoExpirationService.getTableConfig(tableName); + if (config != null) { + return Result.ok(config); + } else { + return Result.ok(null); + } + } catch (Exception e) { + log.error("检查表配置失败: {}", e.getMessage(), e); + return Result.error("检查表配置失败: " + e.getMessage()); + } + } + + @PostMapping("/check-tables") + @ApiOperation("批量检查多个表的配置状态") + public Result> checkTablesConfig(@RequestBody List tableNames) { + try { + Map statusMap = autoExpirationService.getTablesConfigStatus(tableNames); + return Result.ok(statusMap); + } catch (Exception e) { + log.error("批量检查表配置失败: {}", e.getMessage(), e); + return Result.error("批量检查表配置失败: " + e.getMessage()); + } + } + + @PostMapping("/auto-config/{tableName}") + @ApiOperation("智能配置表检测") + public Result autoConfigureTable(@PathVariable String tableName) { + try { + AutoExpirationConfigEntity config = autoExpirationService.autoConfigureTable(tableName); + return Result.ok(config); + } catch (Exception e) { + log.error("智能配置表失败: {}", e.getMessage(), e); + return Result.error("智能配置表失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java index b3318b5..238e447 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java @@ -65,4 +65,5 @@ public class EquipmentController { equipmentService.deleteById(equipmentId); return Result.ok(); } + } diff --git a/web/src/main/resources/application.yml b/web/src/main/resources/application.yml index 694c057..7adbb7d 100644 --- a/web/src/main/resources/application.yml +++ b/web/src/main/resources/application.yml @@ -22,6 +22,7 @@ spring: allow-circular-references: true allow-bean-definition-overriding: true + # flowable相关表 flowable: # true 会对数据库中所有表进行更新操作。如果表不存在,则自动创建(建议开发时使用) diff --git a/web/src/main/resources/logback.xml b/web/src/main/resources/logback.xml index 5f99223..c99788b 100644 --- a/web/src/main/resources/logback.xml +++ b/web/src/main/resources/logback.xml @@ -7,7 +7,7 @@ - + ${log.pattern} @@ -24,7 +24,7 @@ 10mb 200mb - + ${log.pattern} @@ -48,7 +48,7 @@ 10mb 300mb - + ${log.pattern} @@ -72,7 +72,7 @@ 10mb 500mb - + ${log.pattern}