diff --git a/ccdi-project/pom.xml b/ccdi-project/pom.xml
index d9b29636..77febc35 100644
--- a/ccdi-project/pom.xml
+++ b/ccdi-project/pom.xml
@@ -43,6 +43,12 @@
springdoc-openapi-starter-webmvc-ui
+
+
+ com.alibaba
+ easyexcel
+
+
org.springframework.boot
diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java
index 7985cd43..6559dd54 100644
--- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java
+++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java
@@ -292,6 +292,22 @@ public interface CcdiBankTagAnalysisMapper {
*/
List selectSalaryUnusedObjects(@Param("projectId") Long projectId);
+ /**
+ * 突然销户
+ *
+ * @param projectId 项目ID
+ * @return 对象命中结果
+ */
+ List selectSuddenAccountClosureObjects(@Param("projectId") Long projectId);
+
+ /**
+ * 休眠账户大额启用
+ *
+ * @param projectId 项目ID
+ * @return 对象命中结果
+ */
+ List selectDormantAccountLargeActivationObjects(@Param("projectId") Long projectId);
+
/**
* 大额炒股
*
diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java
index 030e826c..75d879b1 100644
--- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java
+++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java
@@ -288,6 +288,8 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(projectId);
case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId);
case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId);
+ case "SUDDEN_ACCOUNT_CLOSURE" -> analysisMapper.selectSuddenAccountClosureObjects(projectId);
+ case "DORMANT_ACCOUNT_LARGE_ACTIVATION" -> analysisMapper.selectDormantAccountLargeActivationObjects(projectId);
case "PROXY_ACCOUNT_OPERATION" -> analysisMapper.selectProxyAccountOperationObjects(projectId);
default -> List.of();
};
diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java
index 4da5cbfc..cecd6727 100644
--- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java
+++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java
@@ -406,6 +406,38 @@ class CcdiBankTagServiceImplTest {
verify(analysisMapper).selectSalaryUnusedObjects(40L);
}
+ @Test
+ void rebuildProject_shouldDispatchSuddenAccountClosureObjectRule() {
+ ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
+
+ CcdiBankTagRule rule = buildRule("ABNORMAL_ACCOUNT", "异常账户",
+ "SUDDEN_ACCOUNT_CLOSURE", "突然销户", "OBJECT");
+
+ when(ruleMapper.selectEnabledRules("ABNORMAL_ACCOUNT")).thenReturn(List.of(rule));
+ when(configResolver.resolve(40L, rule)).thenReturn(buildConfig(40L, rule));
+ when(analysisMapper.selectSuddenAccountClosureObjects(40L)).thenReturn(List.of());
+
+ service.rebuildProject(40L, "ABNORMAL_ACCOUNT", "admin", TriggerType.MANUAL);
+
+ verify(analysisMapper).selectSuddenAccountClosureObjects(40L);
+ }
+
+ @Test
+ void rebuildProject_shouldDispatchDormantAccountLargeActivationObjectRule() {
+ ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
+
+ CcdiBankTagRule rule = buildRule("ABNORMAL_ACCOUNT", "异常账户",
+ "DORMANT_ACCOUNT_LARGE_ACTIVATION", "休眠账户大额启用", "OBJECT");
+
+ when(ruleMapper.selectEnabledRules("ABNORMAL_ACCOUNT")).thenReturn(List.of(rule));
+ when(configResolver.resolve(40L, rule)).thenReturn(buildConfig(40L, rule));
+ when(analysisMapper.selectDormantAccountLargeActivationObjects(40L)).thenReturn(List.of());
+
+ service.rebuildProject(40L, "ABNORMAL_ACCOUNT", "admin", TriggerType.MANUAL);
+
+ verify(analysisMapper).selectDormantAccountLargeActivationObjects(40L);
+ }
+
private CcdiBankTagRule buildRule(String modelCode, String modelName, String ruleCode, String ruleName, String resultType) {
CcdiBankTagRule rule = new CcdiBankTagRule();
rule.setModelCode(modelCode);
diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java
new file mode 100644
index 00000000..3b708fc1
--- /dev/null
+++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java
@@ -0,0 +1,30 @@
+package com.ruoyi.ccdi.project.sql;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CcdiAbnormalAccountRuleSqlMetadataTest {
+
+ @Test
+ void abnormalAccountMetadataSql_shouldContainModelAndRuleDefinitions() throws IOException {
+ Path path = Path.of("..", "sql", "migration",
+ "2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql");
+
+ assertTrue(Files.exists(path), "异常账户模型迁移脚本应存在");
+
+ String sql = Files.readString(path, StandardCharsets.UTF_8);
+ assertAll(
+ () -> assertTrue(sql.contains("ABNORMAL_ACCOUNT")),
+ () -> assertTrue(sql.contains("SUDDEN_ACCOUNT_CLOSURE")),
+ () -> assertTrue(sql.contains("DORMANT_ACCOUNT_LARGE_ACTIVATION")),
+ () -> assertTrue(sql.contains("'OBJECT'"))
+ );
+ }
+}
diff --git a/sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql b/sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
new file mode 100644
index 00000000..bae497e4
--- /dev/null
+++ b/sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
@@ -0,0 +1,37 @@
+START TRANSACTION;
+
+INSERT INTO ccdi_bank_tag_rule (
+ model_code,
+ model_name,
+ rule_code,
+ rule_name,
+ indicator_code,
+ result_type,
+ risk_level,
+ business_caliber,
+ enabled,
+ sort_order,
+ create_by,
+ remark
+) VALUES
+('ABNORMAL_ACCOUNT', '异常账户', 'SUDDEN_ACCOUNT_CLOSURE', '突然销户', NULL, 'OBJECT', 'HIGH',
+ '员工本人账户已销户,且销户日前30天内仍存在交易记录。', 1, 10, 'system',
+ '异常账户模型规则骨架,后续补充建表与完整业务口径'),
+('ABNORMAL_ACCOUNT', '异常账户', 'DORMANT_ACCOUNT_LARGE_ACTIVATION', '休眠账户大额启用', NULL, 'OBJECT', 'HIGH',
+ '员工本人账户开户后长期未使用,首次启用后出现大额资金流动。', 1, 20, 'system',
+ '异常账户模型规则骨架,后续补充建表与完整业务口径')
+ON DUPLICATE KEY UPDATE
+ model_code = VALUES(model_code),
+ model_name = VALUES(model_name),
+ rule_name = VALUES(rule_name),
+ indicator_code = VALUES(indicator_code),
+ result_type = VALUES(result_type),
+ risk_level = VALUES(risk_level),
+ business_caliber = VALUES(business_caliber),
+ enabled = VALUES(enabled),
+ sort_order = VALUES(sort_order),
+ update_by = 'system',
+ update_time = NOW(),
+ remark = VALUES(remark);
+
+COMMIT;