From 8bfe1168a42d4e3750a15b0c0fb0a7629d6cf91c Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 01 四月 2026 17:46:53 +0800
Subject: [PATCH] #日志清理与手动操作权限
---
src/main/webapp/views/config/config.html | 58 ++
src/main/java/com/zy/system/model/LogCleanupExecutionResult.java | 20 +
src/main/resources/sql/20260401_add_log_cleanup_menu_and_config.sql | 80 ++++
src/main/webapp/static/js/wrkMast/wrkMast.js | 4
src/main/java/com/zy/asrs/controller/WrkMastController.java | 25 +
src/main/java/com/zy/system/service/impl/HighPrivilegeGrantServiceImpl.java | 114 +++++
src/main/java/com/zy/system/domain/param/LogCleanupConfigParam.java | 9
src/main/resources/mapper/LogCleanupMapper.xml | 11
src/main/java/com/zy/system/mapper/LogCleanupMapper.java | 15
src/main/java/com/zy/system/model/HighPrivilegeGrantStatus.java | 13
src/main/java/com/zy/system/service/LogCleanupService.java | 20 +
src/main/java/com/zy/system/controller/HighPrivilegeGrantController.java | 33 +
src/main/java/com/zy/system/controller/LogCleanupController.java | 63 +++
src/main/webapp/static/js/config/config.js | 158 +++++++
src/main/java/com/zy/core/enums/RedisKeyType.java | 1
src/main/java/com/zy/system/service/HighPrivilegeGrantService.java | 13
src/main/java/com/zy/system/domain/param/HighPrivilegeGrantParam.java | 11
src/main/java/com/zy/system/domain/param/LogCleanupRunParam.java | 13
src/main/java/com/zy/system/timer/LogCleanupScheduler.java | 24 +
src/main/java/com/zy/system/service/impl/LogCleanupServiceImpl.java | 168 ++++++++
src/main/webapp/static/js/logCleanup/logCleanup.js | 164 ++++++++
src/main/webapp/views/logCleanup/logCleanup.html | 165 ++++++++
22 files changed, 1,179 insertions(+), 3 deletions(-)
diff --git a/src/main/java/com/zy/asrs/controller/WrkMastController.java b/src/main/java/com/zy/asrs/controller/WrkMastController.java
index 51496f6..9ef23d0 100644
--- a/src/main/java/com/zy/asrs/controller/WrkMastController.java
+++ b/src/main/java/com/zy/asrs/controller/WrkMastController.java
@@ -7,10 +7,13 @@
import com.core.common.DateUtils;
import com.core.common.R;
import com.zy.asrs.domain.param.CreateOutTaskBatchParam;
+import com.zy.asrs.domain.param.CancelTaskParam;
+import com.zy.asrs.domain.param.CompleteTaskParam;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.service.CommonService;
import com.zy.common.web.BaseController;
+import com.zy.system.service.HighPrivilegeGrantService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
@@ -30,6 +33,8 @@
private WrkMastService wrkMastService;
@Autowired
private CommonService commonService;
+ @Autowired
+ private HighPrivilegeGrantService highPrivilegeGrantService;
@RequestMapping(value = "/wrkMast/list/auth")
@ManagerAuth
@@ -68,6 +73,26 @@
return R.error("鐢熸垚鎵归噺鍑哄簱浠诲姟澶辫触");
}
+ @PostMapping(value = "/wrkMast/complete/auth")
+ @ManagerAuth(memo = "宸ヤ綔妗e畬鎴愪换鍔�")
+ public R completeTask(@RequestBody CompleteTaskParam param) {
+ if (param == null) {
+ return R.error("鍙傛暟涓嶈兘涓虹┖");
+ }
+ highPrivilegeGrantService.assertGranted(request.getHeader("token"), "瀹屾垚浠诲姟");
+ return commonService.completeTask(param) ? R.ok() : R.error("浠诲姟瀹屾垚澶辫触");
+ }
+
+ @PostMapping(value = "/wrkMast/cancel/auth")
+ @ManagerAuth(memo = "宸ヤ綔妗e彇娑堜换鍔�")
+ public R cancelTask(@RequestBody CancelTaskParam param) {
+ if (param == null) {
+ return R.error("鍙傛暟涓嶈兘涓虹┖");
+ }
+ highPrivilegeGrantService.assertGranted(request.getHeader("token"), "鍙栨秷浠诲姟");
+ return commonService.cancelTask(param) ? R.ok() : R.error("浠诲姟鍙栨秷澶辫触");
+ }
+
private <T> void convert(Map<String, Object> map, QueryWrapper<T> wrapper){
for (Map.Entry<String, Object> entry : map.entrySet()){
String val = String.valueOf(entry.getValue());
diff --git a/src/main/java/com/zy/core/enums/RedisKeyType.java b/src/main/java/com/zy/core/enums/RedisKeyType.java
index f41324b..c4c9cfc 100644
--- a/src/main/java/com/zy/core/enums/RedisKeyType.java
+++ b/src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -74,6 +74,7 @@
CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"),
MAIN_PROCESS_PSEUDOCODE("main_process_pseudocode"),
PLANNER_SCHEDULE("planner_schedule_"),
+ HIGH_PRIVILEGE_GRANT("high_privilege_grant_"),
;
public String key;
diff --git a/src/main/java/com/zy/system/controller/HighPrivilegeGrantController.java b/src/main/java/com/zy/system/controller/HighPrivilegeGrantController.java
new file mode 100644
index 0000000..5cd3815
--- /dev/null
+++ b/src/main/java/com/zy/system/controller/HighPrivilegeGrantController.java
@@ -0,0 +1,33 @@
+package com.zy.system.controller;
+
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.common.web.BaseController;
+import com.zy.system.domain.param.HighPrivilegeGrantParam;
+import com.zy.system.model.HighPrivilegeGrantStatus;
+import com.zy.system.service.HighPrivilegeGrantService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HighPrivilegeGrantController extends BaseController {
+
+ @Autowired
+ private HighPrivilegeGrantService highPrivilegeGrantService;
+
+ @PostMapping("/highPrivilege/grant/auth")
+ @ManagerAuth(memo = "鏈�楂樻潈闄愭巿鏉�")
+ public R grant(@RequestBody HighPrivilegeGrantParam param) {
+ HighPrivilegeGrantStatus status = highPrivilegeGrantService.grant(request.getHeader("token"), param);
+ return R.ok().add(status);
+ }
+
+ @GetMapping("/highPrivilege/status/auth")
+ @ManagerAuth
+ public R status() {
+ return R.ok().add(highPrivilegeGrantService.getStatus(request.getHeader("token")));
+ }
+}
diff --git a/src/main/java/com/zy/system/controller/LogCleanupController.java b/src/main/java/com/zy/system/controller/LogCleanupController.java
new file mode 100644
index 0000000..d6abaf4
--- /dev/null
+++ b/src/main/java/com/zy/system/controller/LogCleanupController.java
@@ -0,0 +1,63 @@
+package com.zy.system.controller;
+
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.common.web.BaseController;
+import com.zy.system.domain.param.LogCleanupConfigParam;
+import com.zy.system.domain.param.LogCleanupRunParam;
+import com.zy.system.model.LogCleanupExecutionResult;
+import com.zy.system.service.HighPrivilegeGrantService;
+import com.zy.system.service.LogCleanupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@RestController
+public class LogCleanupController extends BaseController {
+
+ @Autowired
+ private LogCleanupService logCleanupService;
+ @Autowired
+ private HighPrivilegeGrantService highPrivilegeGrantService;
+
+ @GetMapping("/logCleanup/config/auth")
+ @ManagerAuth
+ public R getConfig() {
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("expireDays", logCleanupService.getExpireDays());
+ result.put("autoTime", "姣忔棩 23:00");
+ result.put("tables", logCleanupService.getSupportedTables());
+ return R.ok().add(result);
+ }
+
+ @PostMapping("/logCleanup/config/save/auth")
+ @ManagerAuth(memo = "淇濆瓨鏃ュ織娓呯悊閰嶇疆")
+ public R saveConfig(@RequestBody LogCleanupConfigParam param) {
+ if (param == null) {
+ return R.error("鍙傛暟涓嶈兘涓虹┖");
+ }
+ logCleanupService.saveExpireDays(param.getExpireDays());
+ return R.ok();
+ }
+
+ @PostMapping("/logCleanup/run/auth")
+ @ManagerAuth(memo = "鎵嬪姩娓呯悊鏃ュ織")
+ public R run(@RequestBody LogCleanupRunParam param) {
+ if (param == null) {
+ return R.error("鍙傛暟涓嶈兘涓虹┖");
+ }
+ highPrivilegeGrantService.assertGranted(request.getHeader("token"), "鎵嬪姩娓呯悊鏃ュ織");
+ LogCleanupExecutionResult result;
+ if ("selected".equals(param.getMode())) {
+ result = logCleanupService.cleanupSelected(logCleanupService.getExpireDays(), param.getTables());
+ } else {
+ result = logCleanupService.cleanupAll(logCleanupService.getExpireDays());
+ }
+ return R.ok().add(result);
+ }
+}
diff --git a/src/main/java/com/zy/system/domain/param/HighPrivilegeGrantParam.java b/src/main/java/com/zy/system/domain/param/HighPrivilegeGrantParam.java
new file mode 100644
index 0000000..5524849
--- /dev/null
+++ b/src/main/java/com/zy/system/domain/param/HighPrivilegeGrantParam.java
@@ -0,0 +1,11 @@
+package com.zy.system.domain.param;
+
+import lombok.Data;
+
+@Data
+public class HighPrivilegeGrantParam {
+
+ private String account;
+
+ private String password;
+}
diff --git a/src/main/java/com/zy/system/domain/param/LogCleanupConfigParam.java b/src/main/java/com/zy/system/domain/param/LogCleanupConfigParam.java
new file mode 100644
index 0000000..8d5f830
--- /dev/null
+++ b/src/main/java/com/zy/system/domain/param/LogCleanupConfigParam.java
@@ -0,0 +1,9 @@
+package com.zy.system.domain.param;
+
+import lombok.Data;
+
+@Data
+public class LogCleanupConfigParam {
+
+ private Integer expireDays;
+}
diff --git a/src/main/java/com/zy/system/domain/param/LogCleanupRunParam.java b/src/main/java/com/zy/system/domain/param/LogCleanupRunParam.java
new file mode 100644
index 0000000..53e7bfb
--- /dev/null
+++ b/src/main/java/com/zy/system/domain/param/LogCleanupRunParam.java
@@ -0,0 +1,13 @@
+package com.zy.system.domain.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class LogCleanupRunParam {
+
+ private String mode;
+
+ private List<String> tables;
+}
diff --git a/src/main/java/com/zy/system/mapper/LogCleanupMapper.java b/src/main/java/com/zy/system/mapper/LogCleanupMapper.java
new file mode 100644
index 0000000..3fdb494
--- /dev/null
+++ b/src/main/java/com/zy/system/mapper/LogCleanupMapper.java
@@ -0,0 +1,15 @@
+package com.zy.system.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface LogCleanupMapper {
+
+ int deleteExpiredBatch(@Param("table") String table,
+ @Param("timeColumn") String timeColumn,
+ @Param("expireDays") int expireDays,
+ @Param("limit") int limit);
+}
diff --git a/src/main/java/com/zy/system/model/HighPrivilegeGrantStatus.java b/src/main/java/com/zy/system/model/HighPrivilegeGrantStatus.java
new file mode 100644
index 0000000..048fc6a
--- /dev/null
+++ b/src/main/java/com/zy/system/model/HighPrivilegeGrantStatus.java
@@ -0,0 +1,13 @@
+package com.zy.system.model;
+
+import lombok.Data;
+
+@Data
+public class HighPrivilegeGrantStatus {
+
+ private boolean granted;
+
+ private long remainingSeconds;
+
+ private Long expireAt;
+}
diff --git a/src/main/java/com/zy/system/model/LogCleanupExecutionResult.java b/src/main/java/com/zy/system/model/LogCleanupExecutionResult.java
new file mode 100644
index 0000000..b1a14fb
--- /dev/null
+++ b/src/main/java/com/zy/system/model/LogCleanupExecutionResult.java
@@ -0,0 +1,20 @@
+package com.zy.system.model;
+
+import lombok.Data;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Data
+public class LogCleanupExecutionResult {
+
+ private String mode;
+
+ private Integer expireDays;
+
+ private long totalDeleted;
+
+ private long executeTime;
+
+ private Map<String, Long> detail = new LinkedHashMap<>();
+}
diff --git a/src/main/java/com/zy/system/service/HighPrivilegeGrantService.java b/src/main/java/com/zy/system/service/HighPrivilegeGrantService.java
new file mode 100644
index 0000000..b125bf5
--- /dev/null
+++ b/src/main/java/com/zy/system/service/HighPrivilegeGrantService.java
@@ -0,0 +1,13 @@
+package com.zy.system.service;
+
+import com.zy.system.domain.param.HighPrivilegeGrantParam;
+import com.zy.system.model.HighPrivilegeGrantStatus;
+
+public interface HighPrivilegeGrantService {
+
+ HighPrivilegeGrantStatus grant(String token, HighPrivilegeGrantParam param);
+
+ HighPrivilegeGrantStatus getStatus(String token);
+
+ void assertGranted(String token, String actionName);
+}
diff --git a/src/main/java/com/zy/system/service/LogCleanupService.java b/src/main/java/com/zy/system/service/LogCleanupService.java
new file mode 100644
index 0000000..85685cc
--- /dev/null
+++ b/src/main/java/com/zy/system/service/LogCleanupService.java
@@ -0,0 +1,20 @@
+package com.zy.system.service;
+
+import com.zy.system.model.LogCleanupExecutionResult;
+
+import java.util.Map;
+
+public interface LogCleanupService {
+
+ Integer getExpireDays();
+
+ void saveExpireDays(Integer expireDays);
+
+ LogCleanupExecutionResult cleanupAll(Integer expireDays);
+
+ LogCleanupExecutionResult cleanupSelected(Integer expireDays, java.util.List<String> tables);
+
+ LogCleanupExecutionResult cleanupScheduled();
+
+ Map<String, String> getSupportedTables();
+}
diff --git a/src/main/java/com/zy/system/service/impl/HighPrivilegeGrantServiceImpl.java b/src/main/java/com/zy/system/service/impl/HighPrivilegeGrantServiceImpl.java
new file mode 100644
index 0000000..f4016ec
--- /dev/null
+++ b/src/main/java/com/zy/system/service/impl/HighPrivilegeGrantServiceImpl.java
@@ -0,0 +1,114 @@
+package com.zy.system.service.impl;
+
+import com.core.common.Cools;
+import com.core.exception.CoolException;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.system.domain.param.HighPrivilegeGrantParam;
+import com.zy.system.entity.Role;
+import com.zy.system.entity.User;
+import com.zy.system.model.HighPrivilegeGrantStatus;
+import com.zy.system.service.HighPrivilegeGrantService;
+import com.zy.system.service.RoleService;
+import com.zy.system.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service("highPrivilegeGrantService")
+public class HighPrivilegeGrantServiceImpl implements HighPrivilegeGrantService {
+
+ private static final String ADMIN_ROLE_CODE = "admin";
+ private static final int USER_ENABLED = 1;
+ private static final long GRANT_TTL_SECONDS = 30L * 60L;
+
+ @Autowired
+ private UserService userService;
+ @Autowired
+ private RoleService roleService;
+ @Autowired
+ private RedisUtil redisUtil;
+
+ @Override
+ public HighPrivilegeGrantStatus grant(String token, HighPrivilegeGrantParam param) {
+ if (Cools.isEmpty(token)) {
+ throw new CoolException("褰撳墠鐧诲綍宸插け鏁堬紝璇烽噸鏂扮櫥褰�");
+ }
+ if (param == null || Cools.isEmpty(param.getAccount(), param.getPassword())) {
+ throw new CoolException("璐﹀彿鍜屽瘑鐮佷笉鑳戒负绌�");
+ }
+ User user = userService.getByMobileWithSecurity(param.getAccount());
+ if (user == null) {
+ throw new CoolException("璐﹀彿鎴栧瘑鐮侀敊璇�");
+ }
+ if (!Integer.valueOf(USER_ENABLED).equals(user.getStatus())) {
+ throw new CoolException("鎺堟潈璐﹀彿宸茬鐢�");
+ }
+ Role role = roleService.getById(user.getRoleId());
+ if (role == null || !ADMIN_ROLE_CODE.equals(role.getCode())) {
+ throw new CoolException("浠卆dmin绠$悊鍛樿处鍙峰彲鑾峰彇鏈�楂樻潈闄�");
+ }
+ if (!Cools.eq(user.getPassword(), param.getPassword())) {
+ throw new CoolException("璐﹀彿鎴栧瘑鐮侀敊璇�");
+ }
+
+ long now = System.currentTimeMillis();
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("account", user.getMobile());
+ payload.put("userId", user.getId());
+ payload.put("grantTime", now);
+ payload.put("expireAt", now + GRANT_TTL_SECONDS * 1000L);
+ redisUtil.set(buildRedisKey(token), payload, GRANT_TTL_SECONDS);
+ return getStatus(token);
+ }
+
+ @Override
+ public HighPrivilegeGrantStatus getStatus(String token) {
+ HighPrivilegeGrantStatus status = new HighPrivilegeGrantStatus();
+ if (Cools.isEmpty(token)) {
+ status.setGranted(false);
+ return status;
+ }
+ String redisKey = buildRedisKey(token);
+ long remainingSeconds = redisUtil.getExpire(redisKey);
+ if (remainingSeconds <= 0 || !redisUtil.hasKey(redisKey)) {
+ status.setGranted(false);
+ status.setRemainingSeconds(0L);
+ status.setExpireAt(null);
+ return status;
+ }
+ status.setGranted(true);
+ status.setRemainingSeconds(remainingSeconds);
+ Object payload = redisUtil.get(redisKey);
+ if (payload instanceof Map) {
+ Object expireAt = ((Map<?, ?>) payload).get("expireAt");
+ if (expireAt instanceof Number) {
+ status.setExpireAt(((Number) expireAt).longValue());
+ } else if (expireAt != null) {
+ try {
+ status.setExpireAt(Long.parseLong(String.valueOf(expireAt)));
+ } catch (NumberFormatException ignore) {
+ status.setExpireAt(System.currentTimeMillis() + remainingSeconds * 1000L);
+ }
+ }
+ }
+ if (status.getExpireAt() == null) {
+ status.setExpireAt(System.currentTimeMillis() + remainingSeconds * 1000L);
+ }
+ return status;
+ }
+
+ @Override
+ public void assertGranted(String token, String actionName) {
+ HighPrivilegeGrantStatus status = getStatus(token);
+ if (!status.isGranted()) {
+ throw new CoolException(actionName + "闇�瑕佹渶楂樻潈闄愭巿鏉冿紝璇峰厛鍦ㄥ紑鍙戜笓鐢�->绯荤粺閰嶇疆瀹屾垚鎺堟潈");
+ }
+ }
+
+ private String buildRedisKey(String token) {
+ return RedisKeyType.HIGH_PRIVILEGE_GRANT.key + token;
+ }
+}
diff --git a/src/main/java/com/zy/system/service/impl/LogCleanupServiceImpl.java b/src/main/java/com/zy/system/service/impl/LogCleanupServiceImpl.java
new file mode 100644
index 0000000..ff3943d
--- /dev/null
+++ b/src/main/java/com/zy/system/service/impl/LogCleanupServiceImpl.java
@@ -0,0 +1,168 @@
+package com.zy.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.core.exception.CoolException;
+import com.zy.system.entity.Config;
+import com.zy.system.mapper.LogCleanupMapper;
+import com.zy.system.model.LogCleanupExecutionResult;
+import com.zy.system.service.ConfigService;
+import com.zy.system.service.LogCleanupService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service("logCleanupService")
+public class LogCleanupServiceImpl implements LogCleanupService {
+
+ private static final String CONFIG_CODE_EXPIRE_DAYS = "logCleanupExpireDays";
+ private static final int DEFAULT_EXPIRE_DAYS = 180;
+ private static final int DELETE_BATCH_LIMIT = 50000;
+ private static final String MODE_ALL = "all";
+ private static final String MODE_SELECTED = "selected";
+ private static final Map<String, String> SUPPORTED_TABLES = new LinkedHashMap<>();
+ private static final Map<String, String> TIME_COLUMNS = new LinkedHashMap<>();
+
+ static {
+ register("asr_bas_crnp_opt", "鍫嗗灈鏈烘搷浣滄棩蹇�", "update_time");
+ register("asr_bas_dual_crnp_opt", "鍙屽伐浣嶅爢鍨涙満鎿嶄綔鏃ュ織", "update_time");
+ register("asr_bas_rgv_err_log", "RGV鏁呴殰鏃ュ織", "create_time");
+ register("asr_wrk_mast_log", "宸ヤ綔妗f棩蹇�", "modi_time");
+ register("asr_bas_dual_crnp_err_log", "鍙屽伐浣嶅爢鍨涙満鏁呴殰鏃ュ織", "create_time");
+ register("asr_bas_station_err_log", "绔欑偣鏁呴殰鏃ュ織", "create_time");
+ register("asr_bas_station_opt", "绔欑偣鎿嶄綔鏃ュ織", "update_time");
+ register("asr_wrk_analysis", "浠诲姟鎵ц鍒嗘瀽", "finish_time");
+ register("sys_http_request_log", "HTTP璇锋眰鏃ュ織", "create_time");
+ register("sys_operate_log", "鎿嶄綔鏃ュ織", "create_time");
+ }
+
+ @Autowired
+ private ConfigService configService;
+ @Autowired
+ private LogCleanupMapper logCleanupMapper;
+
+ @Override
+ public Integer getExpireDays() {
+ Config config = configService.getOne(new QueryWrapper<Config>().eq("code", CONFIG_CODE_EXPIRE_DAYS));
+ if (config == null || config.getValue() == null || config.getValue().trim().isEmpty()) {
+ return DEFAULT_EXPIRE_DAYS;
+ }
+ try {
+ int value = Integer.parseInt(config.getValue().trim());
+ return value > 0 ? value : DEFAULT_EXPIRE_DAYS;
+ } catch (NumberFormatException ignore) {
+ return DEFAULT_EXPIRE_DAYS;
+ }
+ }
+
+ @Override
+ public void saveExpireDays(Integer expireDays) {
+ int normalized = normalizeExpireDays(expireDays);
+ Config config = configService.getOne(new QueryWrapper<Config>().eq("code", CONFIG_CODE_EXPIRE_DAYS));
+ if (config == null) {
+ config = new Config();
+ config.setName("鏃ュ織娴佹按娓呯悊淇濈暀澶╂暟");
+ config.setCode(CONFIG_CODE_EXPIRE_DAYS);
+ config.setValue(String.valueOf(normalized));
+ config.setType((short) 1);
+ config.setStatus((short) 1);
+ config.setSelectType("develop");
+ configService.save(config);
+ return;
+ }
+ config.setValue(String.valueOf(normalized));
+ configService.updateById(config);
+ }
+
+ @Override
+ public LogCleanupExecutionResult cleanupAll(Integer expireDays) {
+ return executeCleanup(MODE_ALL, expireDays, new ArrayList<>(SUPPORTED_TABLES.keySet()));
+ }
+
+ @Override
+ public LogCleanupExecutionResult cleanupSelected(Integer expireDays, List<String> tables) {
+ if (tables == null || tables.isEmpty()) {
+ throw new CoolException("璇烽�夋嫨鑷冲皯涓�寮犳棩蹇楄〃");
+ }
+ LinkedHashSet<String> normalized = new LinkedHashSet<>();
+ for (String table : tables) {
+ if (table == null || table.trim().isEmpty()) {
+ continue;
+ }
+ String tableName = table.trim();
+ if (!SUPPORTED_TABLES.containsKey(tableName)) {
+ throw new CoolException("瀛樺湪涓嶆敮鎸佹竻鐞嗙殑鏃ュ織琛�: " + tableName);
+ }
+ normalized.add(tableName);
+ }
+ if (normalized.isEmpty()) {
+ throw new CoolException("璇烽�夋嫨鑷冲皯涓�寮犳棩蹇楄〃");
+ }
+ return executeCleanup(MODE_SELECTED, expireDays, new ArrayList<>(normalized));
+ }
+
+ @Override
+ public LogCleanupExecutionResult cleanupScheduled() {
+ try {
+ return cleanupAll(getExpireDays());
+ } catch (Exception ex) {
+ log.error("鏃ュ織娴佹按瀹氭椂娓呯悊澶辫触", ex);
+ throw ex;
+ }
+ }
+
+ @Override
+ public Map<String, String> getSupportedTables() {
+ return new LinkedHashMap<>(SUPPORTED_TABLES);
+ }
+
+ private LogCleanupExecutionResult executeCleanup(String mode, Integer expireDays, List<String> tables) {
+ int normalized = normalizeExpireDays(expireDays);
+ LogCleanupExecutionResult result = new LogCleanupExecutionResult();
+ result.setMode(mode);
+ result.setExpireDays(normalized);
+ result.setExecuteTime(System.currentTimeMillis());
+ long totalDeleted = 0L;
+ for (String table : tables) {
+ long tableDeleted = deleteTable(table, TIME_COLUMNS.get(table), normalized);
+ result.getDetail().put(table, tableDeleted);
+ totalDeleted += tableDeleted;
+ }
+ result.setTotalDeleted(totalDeleted);
+ return result;
+ }
+
+ private long deleteTable(String table, String timeColumn, int expireDays) {
+ long deleted = 0L;
+ while (true) {
+ int affected = logCleanupMapper.deleteExpiredBatch(table, timeColumn, expireDays, DELETE_BATCH_LIMIT);
+ if (affected <= 0) {
+ break;
+ }
+ deleted += affected;
+ if (affected < DELETE_BATCH_LIMIT) {
+ break;
+ }
+ }
+ return deleted;
+ }
+
+ private int normalizeExpireDays(Integer expireDays) {
+ if (expireDays == null || expireDays <= 0) {
+ throw new CoolException("鏃ュ織淇濈暀澶╂暟蹇呴』澶т簬0");
+ }
+ return expireDays;
+ }
+
+ private static void register(String table, String label, String timeColumn) {
+ SUPPORTED_TABLES.put(table, label);
+ TIME_COLUMNS.put(table, timeColumn);
+ }
+}
diff --git a/src/main/java/com/zy/system/timer/LogCleanupScheduler.java b/src/main/java/com/zy/system/timer/LogCleanupScheduler.java
new file mode 100644
index 0000000..715f152
--- /dev/null
+++ b/src/main/java/com/zy/system/timer/LogCleanupScheduler.java
@@ -0,0 +1,24 @@
+package com.zy.system.timer;
+
+import com.zy.system.service.LogCleanupService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class LogCleanupScheduler {
+
+ @Autowired
+ private LogCleanupService logCleanupService;
+
+ @Scheduled(cron = "0 0 23 * * ?")
+ public void cleanupExpiredLogs() {
+ try {
+ logCleanupService.cleanupScheduled();
+ } catch (Exception ex) {
+ log.error("鏃ュ織娴佹按鑷姩娓呯悊澶辫触", ex);
+ }
+ }
+}
diff --git a/src/main/resources/mapper/LogCleanupMapper.xml b/src/main/resources/mapper/LogCleanupMapper.xml
new file mode 100644
index 0000000..3b2be38
--- /dev/null
+++ b/src/main/resources/mapper/LogCleanupMapper.xml
@@ -0,0 +1,11 @@
+<?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="com.zy.system.mapper.LogCleanupMapper">
+
+ <delete id="deleteExpiredBatch">
+ DELETE FROM ${table}
+ WHERE ${timeColumn} IS NOT NULL
+ AND ${timeColumn} < DATE_SUB(NOW(), INTERVAL #{expireDays} DAY)
+ LIMIT #{limit}
+ </delete>
+</mapper>
diff --git a/src/main/resources/sql/20260401_add_log_cleanup_menu_and_config.sql b/src/main/resources/sql/20260401_add_log_cleanup_menu_and_config.sql
new file mode 100644
index 0000000..4e7c485
--- /dev/null
+++ b/src/main/resources/sql/20260401_add_log_cleanup_menu_and_config.sql
@@ -0,0 +1,80 @@
+-- 鏂板 鏃ュ織娴佹按娓呯悊 鑿滃崟锛屽苟鍒濆鍖栨棩蹇椾繚鐣欏ぉ鏁伴厤缃�
+-- 璇存槑锛氭墽琛屾湰鑴氭湰鍚庯紝璇峰湪鈥滆鑹叉巿鏉冣�濋噷缁欏搴旇鑹插嬀閫夋柊鑿滃崟鍜屸�滄煡鐪嬧�濇潈闄愩��
+
+INSERT INTO sys_config(name, code, value, type, status, select_type)
+SELECT '鏃ュ織娴佹按娓呯悊淇濈暀澶╂暟', 'logCleanupExpireDays', '180', 1, 1, 'develop'
+FROM dual
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM sys_config
+ WHERE code = 'logCleanupExpireDays'
+);
+
+SET @log_cleanup_parent_id := COALESCE(
+ (
+ SELECT id
+ FROM sys_resource
+ WHERE code = 'develop' AND level = 1
+ ORDER BY id
+ LIMIT 1
+ ),
+ (
+ SELECT id
+ FROM sys_resource
+ WHERE code = 'logReport' AND level = 1
+ ORDER BY id
+ LIMIT 1
+ )
+);
+
+INSERT INTO sys_resource(code, name, resource_id, level, sort, status)
+SELECT 'logCleanup/logCleanup.html', '鏃ュ織娴佹按娓呯悊', @log_cleanup_parent_id, 2, 995, 1
+FROM dual
+WHERE @log_cleanup_parent_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM sys_resource
+ WHERE code = 'logCleanup/logCleanup.html' AND level = 2
+ );
+
+UPDATE sys_resource
+SET name = '鏃ュ織娴佹按娓呯悊',
+ resource_id = @log_cleanup_parent_id,
+ level = 2,
+ sort = 995,
+ status = 1
+WHERE code = 'logCleanup/logCleanup.html' AND level = 2;
+
+SET @log_cleanup_id := (
+ SELECT id
+ FROM sys_resource
+ WHERE code = 'logCleanup/logCleanup.html' AND level = 2
+ ORDER BY id
+ LIMIT 1
+);
+
+INSERT INTO sys_resource(code, name, resource_id, level, sort, status)
+SELECT 'logCleanup/logCleanup.html#view', '鏌ョ湅', @log_cleanup_id, 3, 1, 1
+FROM dual
+WHERE @log_cleanup_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1
+ FROM sys_resource
+ WHERE code = 'logCleanup/logCleanup.html#view' AND level = 3
+ );
+
+UPDATE sys_resource
+SET name = '鏌ョ湅',
+ resource_id = @log_cleanup_id,
+ level = 3,
+ sort = 1,
+ status = 1
+WHERE code = 'logCleanup/logCleanup.html#view' AND level = 3;
+
+SELECT id, code, name, resource_id, level, sort, status
+FROM sys_resource
+WHERE code IN (
+ 'logCleanup/logCleanup.html',
+ 'logCleanup/logCleanup.html#view'
+)
+ORDER BY level, sort, id;
diff --git a/src/main/webapp/static/js/config/config.js b/src/main/webapp/static/js/config/config.js
index d4594da..05688ee 100644
--- a/src/main/webapp/static/js/config/config.js
+++ b/src/main/webapp/static/js/config/config.js
@@ -1437,9 +1437,31 @@
tableHeight: 420,
layoutTimer: null,
tableResizeHandler: null,
+ grantTimer: null,
dialogForm: createFormDefaults(),
dialogDisplay: createDisplayDefaults(),
- dialogRules: createFormRules()
+ dialogRules: createFormRules(),
+ grantStatus: {
+ granted: false,
+ remainingSeconds: 0,
+ expireAt: null
+ },
+ grantDialog: {
+ visible: false,
+ submitting: false,
+ form: {
+ account: '',
+ password: ''
+ },
+ rules: {
+ account: [
+ { required: true, message: '璇疯緭鍏ョ鐞嗗憳璐﹀彿', trigger: 'blur' }
+ ],
+ password: [
+ { required: true, message: '璇疯緭鍏ュ瘑鐮�', trigger: 'blur' }
+ ]
+ }
+ }
};
},
computed: {
@@ -1496,11 +1518,18 @@
},
isDialogReadonly: function () {
return this.dialog.mode === 'detail';
+ },
+ grantStatusText: function () {
+ if (!this.grantStatus.granted) {
+ return '褰撳墠鏃犳渶楂樻潈闄愭巿鏉�';
+ }
+ return '鍓╀綑 ' + this.formatRemainingSeconds(this.grantStatus.remainingSeconds);
}
},
created: function () {
this.fetchSelectTypeOptions();
this.loadTable();
+ this.loadGrantStatus();
},
mounted: function () {
var self = this;
@@ -1519,8 +1548,135 @@
window.removeEventListener('resize', this.tableResizeHandler);
this.tableResizeHandler = null;
}
+ this.stopGrantCountdown();
},
methods: $.extend({}, sharedMethods, {
+ formatRemainingSeconds: function (seconds) {
+ var total = Number(seconds || 0);
+ var minutes;
+ var remainSeconds;
+ if (total <= 0) {
+ return '0绉�';
+ }
+ minutes = Math.floor(total / 60);
+ remainSeconds = total % 60;
+ if (minutes <= 0) {
+ return remainSeconds + '绉�';
+ }
+ if (remainSeconds === 0) {
+ return minutes + '鍒嗛挓';
+ }
+ return minutes + '鍒�' + remainSeconds + '绉�';
+ },
+ stopGrantCountdown: function () {
+ if (this.grantTimer) {
+ clearInterval(this.grantTimer);
+ this.grantTimer = null;
+ }
+ },
+ startGrantCountdown: function () {
+ var self = this;
+ self.stopGrantCountdown();
+ if (!self.grantStatus.granted || Number(self.grantStatus.remainingSeconds) <= 0) {
+ return;
+ }
+ self.grantTimer = setInterval(function () {
+ if (!self.grantStatus.granted) {
+ self.stopGrantCountdown();
+ return;
+ }
+ if (self.grantStatus.remainingSeconds <= 1) {
+ self.grantStatus = {
+ granted: false,
+ remainingSeconds: 0,
+ expireAt: null
+ };
+ self.stopGrantCountdown();
+ return;
+ }
+ self.grantStatus.remainingSeconds -= 1;
+ }, 1000);
+ },
+ applyGrantStatus: function (payload) {
+ var status = payload || {};
+ this.grantStatus = {
+ granted: !!status.granted,
+ remainingSeconds: Number(status.remainingSeconds || 0),
+ expireAt: status.expireAt || null
+ };
+ this.startGrantCountdown();
+ },
+ loadGrantStatus: function () {
+ var self = this;
+ $.ajax({
+ url: baseUrl + '/highPrivilege/status/auth',
+ method: 'GET',
+ headers: self.authHeaders(),
+ success: function (res) {
+ if (self.handleForbidden(res)) {
+ return;
+ }
+ if (!res || res.code !== 200) {
+ return;
+ }
+ self.applyGrantStatus(res.data || {});
+ }
+ });
+ },
+ resetGrantDialog: function () {
+ this.grantDialog.submitting = false;
+ this.grantDialog.form = {
+ account: '',
+ password: ''
+ };
+ if (this.$refs.grantForm) {
+ this.$refs.grantForm.clearValidate();
+ }
+ },
+ openGrantDialog: function () {
+ this.grantDialog.visible = true;
+ this.$nextTick(this.resetGrantDialog);
+ },
+ submitGrant: function () {
+ var self = this;
+ if (!self.$refs.grantForm) {
+ return;
+ }
+ self.$refs.grantForm.validate(function (valid) {
+ if (!valid) {
+ return false;
+ }
+ self.grantDialog.submitting = true;
+ $.ajax({
+ url: baseUrl + '/highPrivilege/grant/auth',
+ method: 'POST',
+ contentType: 'application/json;charset=UTF-8',
+ headers: self.authHeaders(),
+ data: JSON.stringify({
+ account: self.grantDialog.form.account,
+ password: hex_md5(self.grantDialog.form.password || '')
+ }),
+ success: function (res) {
+ self.grantDialog.submitting = false;
+ if (self.handleForbidden(res)) {
+ return;
+ }
+ if (!res || res.code !== 200) {
+ self.$message.error((res && res.msg) ? res.msg : '鎺堟潈澶辫触');
+ return;
+ }
+ self.$message.success('鎺堟潈鎴愬姛');
+ self.grantDialog.visible = false;
+ self.applyGrantStatus(res.data || {});
+ },
+ error: function () {
+ self.grantDialog.submitting = false;
+ self.$message.error('鎺堟潈澶辫触');
+ }
+ });
+ return true;
+ });
+ },
calculateTableHeight: function () {
var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 860;
var tableWrap = this.$refs.tableWrap;
diff --git a/src/main/webapp/static/js/logCleanup/logCleanup.js b/src/main/webapp/static/js/logCleanup/logCleanup.js
new file mode 100644
index 0000000..1c01402
--- /dev/null
+++ b/src/main/webapp/static/js/logCleanup/logCleanup.js
@@ -0,0 +1,164 @@
+(function () {
+ new Vue({
+ el: "#app",
+ data: function () {
+ return {
+ loading: false,
+ saving: false,
+ running: false,
+ tableOptions: [],
+ result: null,
+ form: {
+ expireDays: 180,
+ mode: "all",
+ tables: []
+ }
+ };
+ },
+ computed: {
+ resultDetails: function () {
+ var vm = this;
+ if (!vm.result || !vm.result.detail) {
+ return [];
+ }
+ return Object.keys(vm.result.detail).map(function (table) {
+ return {
+ table: table,
+ label: vm.resolveTableLabel(table),
+ count: vm.result.detail[table]
+ };
+ });
+ }
+ },
+ created: function () {
+ this.loadConfig();
+ },
+ methods: {
+ authHeaders: function () {
+ return { token: localStorage.getItem("token") };
+ },
+ handleForbidden: function (res) {
+ if (res && Number(res.code) === 403) {
+ top.location.href = baseUrl + "/";
+ return true;
+ }
+ return false;
+ },
+ resolveTableLabel: function (table) {
+ var index;
+ for (index = 0; index < this.tableOptions.length; index += 1) {
+ if (this.tableOptions[index].value === table) {
+ return this.tableOptions[index].label;
+ }
+ }
+ return table;
+ },
+ loadConfig: function () {
+ var vm = this;
+ vm.loading = true;
+ $.ajax({
+ url: baseUrl + "/logCleanup/config/auth",
+ method: "GET",
+ headers: vm.authHeaders(),
+ success: function (res) {
+ var tables;
+ vm.loading = false;
+ if (vm.handleForbidden(res)) {
+ return;
+ }
+ if (!res || res.code !== 200) {
+ vm.$message.error((res && res.msg) ? res.msg : "鍔犺浇鏃ュ織娓呯悊閰嶇疆澶辫触");
+ return;
+ }
+ vm.form.expireDays = Number((res.data && res.data.expireDays) || 180);
+ tables = (res.data && res.data.tables) || {};
+ vm.tableOptions = Object.keys(tables).map(function (key) {
+ return {
+ value: key,
+ label: tables[key]
+ };
+ });
+ },
+ error: function () {
+ vm.loading = false;
+ vm.$message.error("鍔犺浇鏃ュ織娓呯悊閰嶇疆澶辫触");
+ }
+ });
+ },
+ saveConfig: function () {
+ var vm = this;
+ if (!vm.form.expireDays || Number(vm.form.expireDays) <= 0) {
+ vm.$message.warning("鏃ュ織淇濈暀澶╂暟蹇呴』澶т簬0");
+ return;
+ }
+ vm.saving = true;
+ $.ajax({
+ url: baseUrl + "/logCleanup/config/save/auth",
+ method: "POST",
+ contentType: "application/json;charset=UTF-8",
+ headers: vm.authHeaders(),
+ data: JSON.stringify({
+ expireDays: Number(vm.form.expireDays)
+ }),
+ success: function (res) {
+ vm.saving = false;
+ if (vm.handleForbidden(res)) {
+ return;
+ }
+ if (!res || res.code !== 200) {
+ vm.$message.error((res && res.msg) ? res.msg : "淇濆瓨鏃ュ織娓呯悊閰嶇疆澶辫触");
+ return;
+ }
+ vm.$message.success("淇濆瓨鎴愬姛");
+ },
+ error: function () {
+ vm.saving = false;
+ vm.$message.error("淇濆瓨鏃ュ織娓呯悊閰嶇疆澶辫触");
+ }
+ });
+ },
+ runCleanup: function () {
+ var vm = this;
+ var requestBody;
+ if (vm.form.mode === "selected" && (!vm.form.tables || !vm.form.tables.length)) {
+ vm.$message.warning("璇烽�夋嫨鑷冲皯涓�寮犳棩蹇楄〃");
+ return;
+ }
+ requestBody = {
+ mode: vm.form.mode,
+ tables: vm.form.mode === "selected" ? vm.form.tables : []
+ };
+ vm.$confirm("纭畾绔嬪嵆鎵ц鏃ュ織娓呯悊鍚楋紵", "鎻愮ず", {
+ type: "warning",
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷"
+ }).then(function () {
+ vm.running = true;
+ $.ajax({
+ url: baseUrl + "/logCleanup/run/auth",
+ method: "POST",
+ contentType: "application/json;charset=UTF-8",
+ headers: vm.authHeaders(),
+ data: JSON.stringify(requestBody),
+ success: function (res) {
+ vm.running = false;
+ if (vm.handleForbidden(res)) {
+ return;
+ }
+ if (!res || res.code !== 200) {
+ vm.$message.error((res && res.msg) ? res.msg : "鎵嬪姩娓呯悊澶辫触");
+ return;
+ }
+ vm.result = res.data || null;
+ vm.$message.success("鎵嬪姩娓呯悊鎵ц瀹屾垚");
+ },
+ error: function () {
+ vm.running = false;
+ vm.$message.error("鎵嬪姩娓呯悊澶辫触");
+ }
+ });
+ }).catch(function () {});
+ }
+ }
+ });
+})();
diff --git a/src/main/webapp/static/js/wrkMast/wrkMast.js b/src/main/webapp/static/js/wrkMast/wrkMast.js
index 6e48597..a2811c7 100644
--- a/src/main/webapp/static/js/wrkMast/wrkMast.js
+++ b/src/main/webapp/static/js/wrkMast/wrkMast.js
@@ -308,7 +308,7 @@
cancelButtonText: "鍙栨秷"
}).then(function () {
$.ajax({
- url: baseUrl + "/openapi/completeTask",
+ url: baseUrl + "/wrkMast/complete/auth",
contentType: "application/json",
headers: { token: localStorage.getItem("token") },
data: JSON.stringify({ wrkNo: row.wrkNo }),
@@ -339,7 +339,7 @@
cancelButtonText: "鍙栨秷"
}).then(function () {
$.ajax({
- url: baseUrl + "/openapi/cancelTask",
+ url: baseUrl + "/wrkMast/cancel/auth",
contentType: "application/json",
headers: { token: localStorage.getItem("token") },
data: JSON.stringify({ wrkNo: row.wrkNo }),
diff --git a/src/main/webapp/views/config/config.html b/src/main/webapp/views/config/config.html
index ba8fc6a..46425eb 100644
--- a/src/main/webapp/views/config/config.html
+++ b/src/main/webapp/views/config/config.html
@@ -114,6 +114,7 @@
.toolbar-ops {
justify-content: flex-end;
+ align-items: center;
}
.list-toolbar .el-input__inner,
@@ -246,6 +247,15 @@
max-height: 280px;
overflow: auto;
padding-right: 4px;
+ }
+
+ .grant-status-text {
+ display: inline-flex;
+ align-items: center;
+ min-height: 32px;
+ padding: 0 6px;
+ color: #5c6b7a;
+ font-size: 12px;
}
.dialog-panel .el-dialog {
@@ -468,6 +478,11 @@
</el-popover>
<el-button size="small" plain icon="el-icon-download" :loading="exporting" @click="exportRows">瀵煎嚭</el-button>
<el-button size="small" plain type="warning" icon="el-icon-refresh-right" @click="refreshCache">鍒锋柊缂撳瓨</el-button>
+ <el-button size="small" plain type="success" icon="el-icon-key" @click="openGrantDialog">鏈�楂樻潈闄愭巿鏉�</el-button>
+ <el-tag size="small" :type="grantStatus.granted ? 'success' : 'info'">
+ {{ grantStatus.granted ? '宸叉巿鏉�' : '鏈巿鏉�' }}
+ </el-tag>
+ <span class="grant-status-text">{{ grantStatusText }}</span>
</div>
</div>
</div>
@@ -717,10 +732,53 @@
<el-button v-if="!isDialogReadonly" type="primary" :loading="dialog.submitting" @click="submitDialog">淇濆瓨</el-button>
</div>
</el-dialog>
+
+ <el-dialog
+ class="dialog-panel"
+ title="鏈�楂樻潈闄愭巿鏉�"
+ :visible.sync="grantDialog.visible"
+ width="460px"
+ :close-on-click-modal="false"
+ @closed="resetGrantDialog">
+ <el-alert
+ title="浠呰鑹茬紪鐮佷负admin鐨勫惎鐢ㄧ鐞嗗憳璐﹀彿鍙巿鏉冿紝鎺堟潈鏈夋晥鏈�30鍒嗛挓銆�"
+ type="warning"
+ :closable="false"
+ show-icon
+ style="margin-bottom: 16px;">
+ </el-alert>
+ <el-form
+ ref="grantForm"
+ :model="grantDialog.form"
+ :rules="grantDialog.rules"
+ label-width="90px"
+ size="small">
+ <el-form-item label="璐﹀彿" prop="account">
+ <el-input
+ v-model.trim="grantDialog.form.account"
+ placeholder="璇疯緭鍏ョ鐞嗗憳璐﹀彿">
+ </el-input>
+ </el-form-item>
+ <el-form-item label="瀵嗙爜" prop="password">
+ <el-input
+ v-model="grantDialog.form.password"
+ type="password"
+ show-password
+ placeholder="璇疯緭鍏ュ瘑鐮�"
+ @keyup.enter.native="submitGrant">
+ </el-input>
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="grantDialog.visible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="grantDialog.submitting" @click="submitGrant">纭鎺堟潈</el-button>
+ </div>
+ </el-dialog>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/tools/md5.js"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/config/config.js" charset="utf-8"></script>
diff --git a/src/main/webapp/views/logCleanup/logCleanup.html b/src/main/webapp/views/logCleanup/logCleanup.html
new file mode 100644
index 0000000..16864ef
--- /dev/null
+++ b/src/main/webapp/views/logCleanup/logCleanup.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+ <meta charset="UTF-8">
+ <title>鏃ュ織娴佹按娓呯悊</title>
+ <meta name="renderer" content="webkit">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <link rel="stylesheet" href="../../static/vue/element/element.css">
+ <link rel="stylesheet" href="../../static/css/cool.css">
+ <style>
+ [v-cloak] { display: none; }
+ html, body {
+ margin: 0;
+ min-height: 100%;
+ color: #243447;
+ font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
+ background:
+ radial-gradient(1000px 420px at 0% -10%, rgba(44, 107, 193, 0.12), transparent 56%),
+ radial-gradient(900px 400px at 100% 0%, rgba(28, 150, 126, 0.10), transparent 58%),
+ linear-gradient(180deg, #f2f6fb 0%, #f8fafc 100%);
+ }
+ .page-shell {
+ max-width: 1380px;
+ margin: 0 auto;
+ padding: 14px;
+ box-sizing: border-box;
+ }
+ .card-shell {
+ border-radius: 24px;
+ border: 1px solid rgba(216, 226, 238, 0.95);
+ background: rgba(255, 255, 255, 0.94);
+ box-shadow: 0 16px 32px rgba(44, 67, 96, 0.08);
+ overflow: hidden;
+ }
+ .section-head {
+ padding: 18px 20px 10px;
+ border-bottom: 1px solid rgba(222, 230, 239, 0.92);
+ }
+ .section-title {
+ margin: 0;
+ font-size: 22px;
+ font-weight: 700;
+ }
+ .section-subtitle {
+ margin-top: 8px;
+ font-size: 13px;
+ color: #6c7d90;
+ }
+ .content-wrap {
+ padding: 18px 20px 20px;
+ }
+ .config-card,
+ .result-card {
+ border: 1px solid rgba(220, 229, 239, 0.96);
+ border-radius: 20px;
+ background: rgba(255, 255, 255, 0.96);
+ padding: 18px;
+ }
+ .result-card {
+ margin-top: 16px;
+ }
+ .inline-tip {
+ font-size: 12px;
+ color: #6f7d8c;
+ margin-left: 10px;
+ }
+ .table-tag-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+ @media (max-width: 900px) {
+ .page-shell {
+ padding: 10px;
+ }
+ .content-wrap,
+ .section-head {
+ padding-left: 14px;
+ padding-right: 14px;
+ }
+ }
+ </style>
+</head>
+<body>
+<div id="app" class="page-shell" v-cloak>
+ <section class="card-shell">
+ <div class="section-head">
+ <h1 class="section-title">鏃ュ織娴佹按娓呯悊</h1>
+ <div class="section-subtitle">绯荤粺鍥哄畾姣忓ぉ 23:00 鑷姩娓呯悊杩囨湡鏃ュ織銆傛墜鍔ㄦ竻鐞嗛渶瑕佸厛鍦ㄧ郴缁熼厤缃腑鑾峰彇鏈�楂樻潈闄愭巿鏉冦��</div>
+ </div>
+ <div class="content-wrap" v-loading="loading">
+ <div class="config-card">
+ <el-alert
+ title="鎵嬪姩娓呯悊浼氭寜褰撳墠淇濈暀澶╂暟鍒犻櫎杩囨湡鏁版嵁锛屼笉浼氬垹闄や繚鐣欐湡鍐呮棩蹇椼��"
+ type="warning"
+ :closable="false"
+ show-icon
+ style="margin-bottom: 18px;">
+ </el-alert>
+ <el-form label-width="120px" size="small">
+ <el-form-item label="淇濈暀澶╂暟">
+ <el-input-number v-model="form.expireDays" :min="1" :step="1" controls-position="right"></el-input-number>
+ <span class="inline-tip">甯哥敤鍊硷細180銆�360</span>
+ </el-form-item>
+ <el-form-item label="鑷姩娓呯悊鏃堕棿">
+ <el-tag type="info">姣忔棩 23:00</el-tag>
+ </el-form-item>
+ <el-form-item label="鎵嬪姩娓呯悊妯″紡">
+ <el-radio-group v-model="form.mode">
+ <el-radio label="all">鎵�鏈夋棩蹇楄〃</el-radio>
+ <el-radio label="selected">鎸囧畾鏃ュ織琛�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item v-if="form.mode === 'selected'" label="鏃ュ織琛ㄩ�夋嫨">
+ <el-select
+ v-model="form.tables"
+ multiple
+ collapse-tags
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨瑕佹竻鐞嗙殑鏃ュ織琛�"
+ style="width: 100%;">
+ <el-option
+ v-for="item in tableOptions"
+ :key="item.value"
+ :label="item.label + ' (' + item.value + ')'"
+ :value="item.value">
+ </el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" :loading="saving" @click="saveConfig">淇濆瓨閰嶇疆</el-button>
+ <el-button type="danger" plain :loading="running" @click="runCleanup">鎵嬪姩娓呯悊</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <div v-if="result" class="result-card">
+ <el-descriptions title="鏈�杩戜竴娆℃墜鍔ㄦ竻鐞嗙粨鏋�" :column="3" border size="small">
+ <el-descriptions-item label="娓呯悊妯″紡">{{ result.mode === 'selected' ? '鎸囧畾鏃ュ織琛�' : '鎵�鏈夋棩蹇楄〃' }}</el-descriptions-item>
+ <el-descriptions-item label="淇濈暀澶╂暟">{{ result.expireDays }}</el-descriptions-item>
+ <el-descriptions-item label="鍒犻櫎鎬绘暟">{{ result.totalDeleted }}</el-descriptions-item>
+ </el-descriptions>
+ <div style="margin-top: 16px;" class="table-tag-list">
+ <el-tag
+ v-for="item in resultDetails"
+ :key="item.table"
+ type="success"
+ effect="plain">
+ {{ item.label }}: {{ item.count }}
+ </el-tag>
+ </div>
+ </div>
+ </div>
+ </section>
+</div>
+
+<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
+<script type="text/javascript" src="../../static/vue/element/element.js"></script>
+<script type="text/javascript" src="../../static/js/logCleanup/logCleanup.js" charset="utf-8"></script>
+</body>
+</html>
--
Gitblit v1.9.1