Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4f5d632
feat: add resource API
lu-yg Sep 5, 2025
df6ba91
fix: modify source API
lu-yg Sep 5, 2025
0fef229
fix: modify source API
lu-yg Sep 5, 2025
ab24c40
fix: modify source API
lu-yg Sep 5, 2025
787d1d1
fix: modify source API
lu-yg Sep 5, 2025
5fed78f
fix: modify source API
lu-yg Sep 5, 2025
d483f04
fix: modify source API
lu-yg Sep 5, 2025
72e9c67
fix: modify source API
lu-yg Sep 5, 2025
1fe61d5
fix: modify source API
lu-yg Sep 5, 2025
fd74ac8
fix: modify source API
lu-yg Sep 5, 2025
398e5c0
feat: add scheduled tasks
lu-yg Sep 9, 2025
0b8105c
fix: modify resource download
lu-yg Sep 9, 2025
6c9c7e4
fix: modify resource group update
lu-yg Sep 9, 2025
6178ed2
fix: modify resource group update
lu-yg Sep 9, 2025
3d723ae
fix: modify resource group update
lu-yg Sep 9, 2025
797045c
Merge branch 'opentiny:develop' into feat/resourceManagement
lu-yg Sep 10, 2025
3e2e616
fix: modify resource download
lu-yg Sep 10, 2025
603f65d
fix: modify block group
lu-yg Sep 15, 2025
f8d37c0
fix: modify AI chat
lu-yg Sep 16, 2025
e7c73d8
Merge branch 'opentiny:develop' into feat/resourceManagement
lu-yg Sep 16, 2025
6c4f3ea
fix: modify AI chat
lu-yg Sep 16, 2025
35a0b45
fix: modify AI chat
lu-yg Sep 16, 2025
4d7848b
fix: modify AI chat
lu-yg Sep 16, 2025
ad807d9
fix: modify AI chat
lu-yg Sep 16, 2025
53313bf
fix: modify AI chat
lu-yg Sep 16, 2025
ca4ff1b
fix: modify AI chat
lu-yg Sep 16, 2025
bff65fe
fix: modify AI chat
lu-yg Sep 16, 2025
f1fe5d0
Merge branch 'opentiny:develop' into feat/resourceManagement
lu-yg Sep 17, 2025
4eaee7a
fix: modify resource upload
lu-yg Sep 17, 2025
d211902
fix: modify resource upload
lu-yg Sep 17, 2025
28bef56
fix: modify resource upload
lu-yg Sep 17, 2025
a93100a
fix: modify resource upload
lu-yg Sep 17, 2025
1014efc
fix: modify resource upload
lu-yg Sep 17, 2025
68193ce
fix: modify resource upload
lu-yg Sep 17, 2025
64a7397
fix: modify resource upload
lu-yg Sep 17, 2025
363f728
fix: modify resource upload
lu-yg Sep 17, 2025
e0883be
fix: modify resource upload and download
lu-yg Sep 17, 2025
e6f23f6
Merge branch 'opentiny:develop' into feat/resourceManagement
lu-yg Sep 18, 2025
629eca3
fix: modify resource download
lu-yg Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add scheduled tasks
  • Loading branch information
lu-yg committed Sep 9, 2025
commit 398e5c0a8c2e4ed71bdf2542f50836b62b0fe326
30 changes: 30 additions & 0 deletions app/src/main/java/com/tinyengine/it/task/CleanupProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.tinyengine.it.task;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "cleanup")
public class CleanupProperties {

private boolean enabled = true;
private boolean useTruncate = false;
private List<String> whitelistTables;
private String cronExpression = "0 0 0 * * ?";
private boolean sendWarning = true;

public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }

public boolean isUseTruncate() { return useTruncate; }
public void setUseTruncate(boolean useTruncate) { this.useTruncate = useTruncate; }

public List<String> getWhitelistTables() { return whitelistTables; }
public void setWhitelistTables(List<String> whitelistTables) { this.whitelistTables = whitelistTables; }

public String getCronExpression() { return cronExpression; }
public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; }

public boolean isSendWarning() { return sendWarning; }
public void setSendWarning(boolean sendWarning) { this.sendWarning = sendWarning; }
}
284 changes: 284 additions & 0 deletions app/src/main/java/com/tinyengine/it/task/DatabaseCleanupService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package com.tinyengine.it.task;


import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class DatabaseCleanupService {

private static final Logger logger = LoggerFactory.getLogger(DatabaseCleanupService.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Autowired
private JdbcTemplate jdbcTemplate;

@Autowired
private CleanupProperties cleanupProperties;
// 执行统计
private final Map<String, ExecutionStats> executionStats = new ConcurrentHashMap<>();
private final AtomicInteger totalExecutions = new AtomicInteger(0);

// 默认白名单表(如果配置文件未设置)
private static final List<String> DEFAULT_TABLES = Arrays.asList(
"t_resource", "t_resource_group", "r_resource_group_resource", "t_app_extension",
"t_block", "t_block_carriers_relation", "t_block_group", "t_block_history",
"r_material_block", "r_material_history_block", "r_block_group_block", "t_datasource",
"t_i18n_entry", "t_model", "t_page", "t_page_history", "t_page_template"
);

/**
* 每天24:00自动执行清空操作
*/
@Scheduled(cron = "${cleanup.cron-expression:0 0 0 * * ?}")
public void autoCleanupAtMidnight() {
if (!cleanupProperties.isEnabled()) {
logger.info("⏸️ 清空任务已禁用,跳过执行");
return;
}

String executionId = UUID.randomUUID().toString().substring(0, 8);
String startTime = LocalDateTime.now().format(formatter);

logger.info("🎯 ======= 开始执行数据库清空任务 [{}] =======", executionId);
logger.info("⏰ 执行时间: {}", startTime);
logger.info("📋 目标表: {}", getWhitelistTables());

ExecutionStats stats = new ExecutionStats(executionId, startTime);
executionStats.put(executionId, stats);
totalExecutions.incrementAndGet();

int successCount = 0;
int failedCount = 0;
long totalRowsCleaned = 0;

for (String tableName : getWhitelistTables()) {
try {
validateTableName(tableName);

if (!tableExists(tableName)) {
logger.warn("⚠️ 表 {} 不存在,跳过", tableName);
stats.recordSkipped(tableName, "表不存在");
continue;
}

long beforeCount = getTableRecordCount(tableName);
long rowsCleaned;

if (cleanupProperties.isUseTruncate()) {
truncateTable(tableName);
rowsCleaned = beforeCount; // TRUNCATE会清空所有数据
} else {
rowsCleaned = clearTableData(tableName);
}

totalRowsCleaned += rowsCleaned;
successCount++;

logger.info("✅ 表 {} 清空完成: 删除 {} 条记录", tableName, rowsCleaned);
stats.recordSuccess(tableName, rowsCleaned);

} catch (Exception e) {
failedCount++;
logger.error("❌ 表 {} 清空失败: {}", tableName, e.getMessage(), e);
stats.recordFailure(tableName, e.getMessage());
}
}

String endTime = LocalDateTime.now().format(formatter);
stats.setEndTime(endTime);
stats.setTotalRowsCleaned(totalRowsCleaned);

logger.info("📊 ======= 任务完成统计 [{}] =======", executionId);
logger.info("✅ 成功表数: {}", successCount);
logger.info("❌ 失败表数: {}", failedCount);
logger.info("📈 总共删除记录: {}", totalRowsCleaned);
logger.info("⏰ 耗时: {} 秒", stats.getDurationSeconds());
logger.info("🕐 开始: {}, 结束: {}", startTime, endTime);
logger.info("🎉 ======= 任务执行完成 =======\n");
}

/**
* 每天23:55发送预警通知
*/
@Scheduled(cron = "0 55 23 * * ?")
public void sendCleanupWarning() {
if (!cleanupProperties.isEnabled() || !cleanupProperties.isSendWarning()) {
return;
}

logger.warn("⚠️ ⚠️ ⚠️ 重要通知:5分钟后将自动清空数据库表!");
logger.warn("📋 目标表: {}", getWhitelistTables());
logger.warn("⏰ 执行时间: 00:00:00");
logger.warn("💡 如需取消,请修改配置: cleanup.enabled=false");
logger.warn("==========================================");
}

/**
* 应用启动时初始化
*/
@PostConstruct
public void init() {
logger.info("🚀 数据库自动清空服务初始化完成");
logger.info("📋 配置表: {}", getWhitelistTables());
logger.info("⏰ 执行时间: {}", cleanupProperties.getCronExpression());
logger.info("🔧 使用模式: {}", cleanupProperties.isUseTruncate() ? "TRUNCATE" : "DELETE");
logger.info("✅ 服务状态: {}", cleanupProperties.isEnabled() ? "已启用" : "已禁用");
logger.info("==========================================");
}

/**
* 获取白名单表列表
*/
public List<String> getWhitelistTables() {
List<String> tables = cleanupProperties.getWhitelistTables();
return tables != null && !tables.isEmpty() ? tables : DEFAULT_TABLES;
}

/**
* 清空表数据(DELETE方式)
*/
private long clearTableData(String tableName) {
validateTableName(tableName);
String sql = "DELETE FROM " + tableName;
int affectedRows = jdbcTemplate.update(sql);
return affectedRows;
}

/**
* 清空表数据(TRUNCATE方式)
*/
private void truncateTable(String tableName) {
validateTableName(tableName);
String sql = "TRUNCATE TABLE " + tableName;
jdbcTemplate.execute(sql);
}

/**
* 检查表是否存在
*/
public boolean tableExists(String tableName) {
try {
String sql = "SELECT COUNT(*) FROM information_schema.tables " +
"WHERE table_schema = DATABASE() AND table_name = ?";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, tableName.toUpperCase());
return count != null && count > 0;
} catch (Exception e) {
logger.warn("检查表存在失败: {}", e.getMessage());
return false;
}
}

/**
* 获取表记录数量
*/
public long getTableRecordCount(String tableName) {
try {
validateTableName(tableName);
String sql = "SELECT COUNT(*) FROM " + tableName;
Long count = jdbcTemplate.queryForObject(sql, Long.class);
return count != null ? count : 0;
} catch (Exception e) {
logger.error("获取表记录数失败: {}", e.getMessage());
return -1;
}
}

/**
* 验证表名安全性
*/
private void validateTableName(String tableName) {
if (tableName == null || tableName.trim().isEmpty()) {
throw new IllegalArgumentException("表名不能为空");
}
if (!tableName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
throw new IllegalArgumentException("无效的表名格式: " + tableName);
}
}

/**
* 获取执行统计
*/
public Map<String, ExecutionStats> getExecutionStats() {
return new LinkedHashMap<>(executionStats);
}

public int getTotalExecutions() {
return totalExecutions.get();
}

/**
* 执行统计内部类
*/
public static class ExecutionStats {
private final String executionId;
private final String startTime;
private String endTime;
private long totalRowsCleaned;
private final Map<String, TableResult> tableResults = new LinkedHashMap<>();

public ExecutionStats(String executionId, String startTime) {
this.executionId = executionId;
this.startTime = startTime;
}

public void recordSuccess(String tableName, long rowsCleaned) {
tableResults.put(tableName, new TableResult("SUCCESS", rowsCleaned, null));
}

public void recordFailure(String tableName, String errorMessage) {
tableResults.put(tableName, new TableResult("FAILED", 0, errorMessage));
}

public void recordSkipped(String tableName, String reason) {
tableResults.put(tableName, new TableResult("SKIPPED", 0, reason));
}

// Getters and setters
public String getExecutionId() { return executionId; }
public String getStartTime() { return startTime; }
public String getEndTime() { return endTime; }
public void setEndTime(String endTime) { this.endTime = endTime; }
public long getTotalRowsCleaned() { return totalRowsCleaned; }
public void setTotalRowsCleaned(long totalRowsCleaned) { this.totalRowsCleaned = totalRowsCleaned; }
public Map<String, TableResult> getTableResults() { return tableResults; }

public long getDurationSeconds() {
if (startTime != null && endTime != null) {
LocalDateTime start = LocalDateTime.parse(startTime, formatter);
LocalDateTime end = LocalDateTime.parse(endTime, formatter);
return java.time.Duration.between(start, end).getSeconds();
}
return 0;
}
}

/**
* 表结果内部类
*/
public static class TableResult {
private final String status;
private final long rowsCleaned;
private final String message;

public TableResult(String status, long rowsCleaned, String message) {
this.status = status;
this.rowsCleaned = rowsCleaned;
this.message = message;
}

// Getters
public String getStatus() { return status; }
public long getRowsCleaned() { return rowsCleaned; }
public String getMessage() { return message; }
}
}
20 changes: 20 additions & 0 deletions app/src/main/java/com/tinyengine/it/task/SchedulerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tinyengine.it.task;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@EnableScheduling
public class SchedulerConfig {
// 启用Spring定时任务功能
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("cleanup-scheduler-");
return scheduler;
}
}
31 changes: 27 additions & 4 deletions app/src/main/resources/application-alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ server:
port: 9090

spring:
config:
activate:
on-profile: alpha
servlet:
multipart:
max-file-size: 10MB
Expand All @@ -25,9 +28,30 @@ spring:
min-evictable-idle-time-millis: 300000 # 连接在池中保持空闲的最小时间(单位:毫秒)。如果空闲时间超过这个值,连接将被回收,默认值为 1800000。
pool-prepared-statements: true # 是否缓存 PreparedStatement 对象,默认值为 true。
max-open-prepared-statements: 20 # 最大缓存的 PreparedStatement 数量,默认值为 -1,表示无限制。如果 `pool-prepared-statements` 设置为 true,设置此值以限制缓存数量。
config:
activate:
on-profile: alpha
# 清空任务配置
cleanup:
enabled: false
use-truncate: false
cron-expression: "0 0 0 * * ?"
send-warning: true
whitelist-tables:
- t_resource
- t_resource_group
- r_resource_group_resource
- t_app_extension
- t_block
- t_block_carriers_relation
- t_block_group
- t_block_history
- r_material_block
- r_material_history_block
- r_block_group_block
- t_datasource
- t_i18n_entry
- t_model
- t_page
- t_page_history
- t_page_template
#切面启用
aop:
proxy-target-class: true #默认为false
Expand All @@ -50,7 +74,6 @@ mybatis-plus:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
auto-mapping-behavior: full
com.tinyengine.it.mapper: debug
type-handlers-package: com.tinyengine.it.common.handler


Expand Down
Loading