合并第一期银行流水真实规则后端实现
This commit is contained in:
@@ -190,17 +190,21 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
* 单笔购汇金额超限
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 单笔购汇阈值
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectForexBuyAmtStatements(@Param("projectId") Long projectId);
|
||||
List<BankTagStatementHitVO> selectForexBuyAmtStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 单笔结汇金额超限
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 单笔结汇阈值
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectForexSellAmtStatements(@Param("projectId") Long projectId);
|
||||
List<BankTagStatementHitVO> selectForexSellAmtStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 单笔跨境汇款金额超限
|
||||
@@ -238,17 +242,21 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
* 可疑银证大额转账
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 银证转账阈值
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectStockTfrLargeStatements(@Param("projectId") Long projectId);
|
||||
List<BankTagStatementHitVO> selectStockTfrLargeStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 微信支付宝频繁提现
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param frequencyThreshold 提现频次阈值
|
||||
* @return 对象命中结果
|
||||
*/
|
||||
List<BankTagObjectHitVO> selectWithdrawCntObjects(@Param("projectId") Long projectId);
|
||||
List<BankTagObjectHitVO> selectWithdrawCntObjects(@Param("projectId") Long projectId,
|
||||
@Param("frequencyThreshold") Integer frequencyThreshold);
|
||||
|
||||
/**
|
||||
* 微信支付宝提现超额
|
||||
@@ -278,9 +286,11 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
* 大额炒股
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 三方资管交易阈值
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectLargeStockTradingStatements(@Param("projectId") Long projectId);
|
||||
List<BankTagStatementHitVO> selectLargeStockTradingStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 疑似代理他人账户
|
||||
|
||||
@@ -24,13 +24,18 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Component
|
||||
public class BankTagRuleConfigResolver {
|
||||
|
||||
private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.of(
|
||||
"SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT"),
|
||||
"CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT"),
|
||||
"ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER"),
|
||||
"LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT"),
|
||||
"FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT"),
|
||||
"LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")
|
||||
private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries(
|
||||
Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")),
|
||||
Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
|
||||
Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
|
||||
Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")),
|
||||
Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")),
|
||||
Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")),
|
||||
Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")),
|
||||
Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")),
|
||||
Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")),
|
||||
Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")),
|
||||
Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE"))
|
||||
);
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -233,12 +233,20 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId);
|
||||
case "TAX_ASSET_REGISTRATION_MISMATCH" -> analysisMapper.selectTaxAssetRegistrationMismatchStatements(projectId);
|
||||
case "INCOME_ASSET_MISMATCH" -> analysisMapper.selectIncomeAssetMismatchStatements(projectId);
|
||||
case "FOREX_BUY_AMT" -> analysisMapper.selectForexBuyAmtStatements(projectId);
|
||||
case "FOREX_SELL_AMT" -> analysisMapper.selectForexSellAmtStatements(projectId);
|
||||
case "FOREX_BUY_AMT" -> analysisMapper.selectForexBuyAmtStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("SINGLE_PURCHASE_AMOUNT"))
|
||||
);
|
||||
case "FOREX_SELL_AMT" -> analysisMapper.selectForexSellAmtStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("SINGLE_SETTLEMENT_AMOUNT"))
|
||||
);
|
||||
case "CROSS_BORDER_AMT" -> analysisMapper.selectCrossBorderAmtStatements(projectId);
|
||||
case "LARGE_PURCHASE_TRANSACTION" -> analysisMapper.selectLargePurchaseTransactionStatements(projectId);
|
||||
case "STOCK_TFR_LARGE" -> analysisMapper.selectStockTfrLargeStatements(projectId);
|
||||
case "LARGE_STOCK_TRADING" -> analysisMapper.selectLargeStockTradingStatements(projectId);
|
||||
case "STOCK_TFR_LARGE" -> analysisMapper.selectStockTfrLargeStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("STOCK_TFR_LARGE"))
|
||||
);
|
||||
case "LARGE_STOCK_TRADING" -> analysisMapper.selectLargeStockTradingStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("STOCK_TFR_LARGE"))
|
||||
);
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
@@ -264,7 +272,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
case "FIXED_COUNTERPARTY_TRANSFER" -> analysisMapper.selectFixedCounterpartyTransferObjects(projectId);
|
||||
case "INTEREST_PAYMENT_BY_OTHERS" -> analysisMapper.selectInterestPaymentByOthersObjects(projectId);
|
||||
case "SUPPLIER_CONCENTRATION" -> analysisMapper.selectSupplierConcentrationObjects(projectId);
|
||||
case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects(projectId);
|
||||
case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects(
|
||||
projectId, toInteger(config.getThresholdValue("WITHDRAW_CNT"))
|
||||
);
|
||||
case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(projectId);
|
||||
case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId);
|
||||
case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId);
|
||||
|
||||
@@ -396,9 +396,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'摘要/对手命中赌博敏感词,摘要“', IFNULL(bs.USER_MEMO, ''),
|
||||
'”,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,支出金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_DR, 0) > 0
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|投注'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|投注'
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectSpecialAmountTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
@@ -406,9 +416,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'与非配偶/子女交易出现特殊金额 ',
|
||||
CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,关系类型“', IFNULL(relation.relation_type, '非亲属'), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
left join ccdi_staff_fmy_relation relation
|
||||
on relation.person_id = staff.id_card
|
||||
and relation.relation_name = bs.CUSTOMER_ACCOUNT_NAME
|
||||
and relation.status = 1
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.LE_ACCOUNT_NAME, '') <> IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
|
||||
and (IFNULL(relation.relation_type, '') = '' or relation.relation_type not in ('配偶', '子女'))
|
||||
and (
|
||||
IFNULL(bs.AMOUNT_DR, 0) in (520, 1314)
|
||||
or IFNULL(bs.AMOUNT_CR, 0) in (520, 1314)
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectMonthlyFixedIncomeObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -434,9 +460,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'摘要命中收入关键词,摘要“', IFNULL(bs.USER_MEMO, ''),
|
||||
'”,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,流入金额 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > 0
|
||||
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') <> '浙江兰溪农村商业银行股份有限公司'
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '代发|工资|分红|红利|奖金|薪酬|薪金|补贴|薪|年终奖|年金|加班费|劳务费|劳务外包|提成|劳务派遣|绩效|酬劳|批量代付|PAYROLL|SALA|CPF|directors.*fees'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '代发|工资|劳务费'
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectHouseRegistrationMismatchStatements" resultMap="BankTagStatementHitResultMap">
|
||||
@@ -484,9 +521,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'购汇交易金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,摘要“', IFNULL(bs.USER_MEMO, ''), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
|
||||
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '银行|外汇|售汇|国家外汇管理局'
|
||||
and IFNULL(bs.USER_MEMO, '') REGEXP '购汇|换汇|外汇|汇率|外币|现汇|人民币兑换外币|外汇买入|购外币|购买外汇'
|
||||
</select>
|
||||
|
||||
<select id="selectForexSellAmtStatements" resultMap="BankTagStatementHitResultMap">
|
||||
@@ -494,9 +540,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'结汇交易金额 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,摘要“', IFNULL(bs.USER_MEMO, ''), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
|
||||
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '银行|外汇|结汇|国家外汇管理局'
|
||||
and IFNULL(bs.USER_MEMO, '') REGEXP '购汇|结汇|换汇|外汇|汇率|外币|现汇|结汇水单|外币兑换人民币|结汇入账|外汇结汇'
|
||||
</select>
|
||||
|
||||
<select id="selectCrossBorderAmtStatements" resultMap="BankTagStatementHitResultMap">
|
||||
@@ -520,12 +575,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="selectLargePurchaseTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
CAST(NULL AS SIGNED) AS bankStatementId,
|
||||
CAST(NULL AS SIGNED) AS groupId,
|
||||
CAST(NULL AS SIGNED) AS logId,
|
||||
CONCAT(
|
||||
'采购事项“', IFNULL(t.subjectName, ''),
|
||||
'”实际采购金额 ', CAST(IFNULL(t.actualAmount, 0) AS CHAR),
|
||||
' 元,供应商“', IFNULL(t.supplierName, ''), '”'
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select distinct
|
||||
pt.purchase_id AS purchaseId,
|
||||
pt.subject_name AS subjectName,
|
||||
pt.supplier_name AS supplierName,
|
||||
pt.actual_amount AS actualAmount
|
||||
from ccdi_purchase_transaction pt
|
||||
inner join ccdi_base_staff staff
|
||||
on CAST(staff.staff_id AS CHAR) = pt.applicant_id
|
||||
where IFNULL(pt.actual_amount, 0) > 100000
|
||||
union
|
||||
select distinct
|
||||
pt.purchase_id AS purchaseId,
|
||||
pt.subject_name AS subjectName,
|
||||
pt.supplier_name AS supplierName,
|
||||
pt.actual_amount AS actualAmount
|
||||
from ccdi_purchase_transaction pt
|
||||
inner join ccdi_base_staff staff
|
||||
on CAST(staff.staff_id AS CHAR) = pt.purchase_leader_id
|
||||
where pt.purchase_leader_id is not null
|
||||
and IFNULL(pt.actual_amount, 0) > 100000
|
||||
) t
|
||||
</select>
|
||||
|
||||
<select id="selectSupplierConcentrationObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -542,18 +621,51 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'银证转账金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,摘要“', IFNULL(bs.USER_MEMO, ''), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and (
|
||||
IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
|
||||
or IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
|
||||
)
|
||||
and (
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectWithdrawCntObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'STAFF_ID_CARD' AS objectType,
|
||||
'' AS objectKey,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
t.objectKey AS objectKey,
|
||||
CONCAT(
|
||||
'单日微信/支付宝提现 ', CAST(t.withdrawCount AS CHAR),
|
||||
' 次,超过阈值 ', CAST(#{frequencyThreshold} AS CHAR),
|
||||
' 次,交易日:', t.transDate
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select
|
||||
staff.id_card AS objectKey,
|
||||
LEFT(TRIM(bs.TRX_DATE), 10) AS transDate,
|
||||
COUNT(1) AS withdrawCount
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) >= 0
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
|
||||
)
|
||||
group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
|
||||
having COUNT(1) > #{frequencyThreshold}
|
||||
) t
|
||||
</select>
|
||||
|
||||
<select id="selectWithdrawAmtObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -588,9 +700,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
CONCAT(
|
||||
'三方资管交易金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,摘要“', IFNULL(bs.USER_MEMO, ''), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
|
||||
and (
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectProxyAccountOperationObjects" resultMap="BankTagObjectHitResultMap">
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.io.StringReader;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -15,31 +17,33 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
class CcdiBankTagAnalysisMapperXmlTest {
|
||||
|
||||
private static final String RESOURCE = "mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml";
|
||||
private static final List<String> PHASE_ONE_STATEMENT_SELECT_IDS = List.of(
|
||||
"selectGamblingSensitiveKeywordStatements",
|
||||
"selectSpecialAmountTransactionStatements",
|
||||
"selectSuspiciousIncomeKeywordStatements",
|
||||
"selectForexBuyAmtStatements",
|
||||
"selectForexSellAmtStatements",
|
||||
"selectLargePurchaseTransactionStatements",
|
||||
"selectStockTfrLargeStatements",
|
||||
"selectLargeStockTradingStatements"
|
||||
);
|
||||
private static final List<String> PLACEHOLDER_SELECT_IDS = List.of(
|
||||
"selectAbnormalCustomerTransactionStatements",
|
||||
"selectLowIncomeRelativeLargeTransactionObjects",
|
||||
"selectMultiPartyGamblingTransferObjects",
|
||||
"selectGamblingSensitiveKeywordStatements",
|
||||
"selectSpecialAmountTransactionStatements",
|
||||
"selectMonthlyFixedIncomeObjects",
|
||||
"selectFixedCounterpartyTransferObjects",
|
||||
"selectSuspiciousIncomeKeywordStatements",
|
||||
"selectHouseRegistrationMismatchStatements",
|
||||
"selectPropertyFeeRegistrationMismatchStatements",
|
||||
"selectTaxAssetRegistrationMismatchStatements",
|
||||
"selectIncomeAssetMismatchStatements",
|
||||
"selectForexBuyAmtStatements",
|
||||
"selectForexSellAmtStatements",
|
||||
"selectCrossBorderAmtStatements",
|
||||
"selectInterestPaymentByOthersObjects",
|
||||
"selectLargePurchaseTransactionStatements",
|
||||
"selectSupplierConcentrationObjects",
|
||||
"selectStockTfrLargeStatements",
|
||||
"selectWithdrawCntObjects",
|
||||
"selectWithdrawAmtObjects",
|
||||
"selectSalaryQuickTransferObjects",
|
||||
"selectSalaryUnusedObjects",
|
||||
"selectLargeStockTradingStatements",
|
||||
"selectProxyAccountOperationObjects"
|
||||
);
|
||||
|
||||
@@ -74,6 +78,9 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
@Test
|
||||
void allPlaceholderRules_shouldExistInAnalysisMapperXml() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
for (String selectId : PHASE_ONE_STATEMENT_SELECT_IDS) {
|
||||
assertTrue(xml.contains(selectId), () -> "缺少第一期规则 SQL: " + selectId);
|
||||
}
|
||||
for (String selectId : PLACEHOLDER_SELECT_IDS) {
|
||||
assertTrue(xml.contains(selectId), () -> "缺少占位规则 SQL: " + selectId);
|
||||
}
|
||||
@@ -83,7 +90,30 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
void placeholderRules_shouldUseEmptyResultSqlTemplate() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
assertTrue(xml.contains("占位SQL,待补充真实规则"));
|
||||
assertEquals(25, countMatches(xml, "where 1 = 0"));
|
||||
assertEquals(16, countMatches(xml, "where 1 = 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void phaseOneStatementRules_shouldUseRealSqlAndKeepHitFields() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
for (String selectId : PHASE_ONE_STATEMENT_SELECT_IDS) {
|
||||
String selectSql = extractSelectSql(xml, selectId);
|
||||
assertTrue(selectSql.contains("AS bankStatementId"), () -> selectId + " 缺少 bankStatementId");
|
||||
assertTrue(selectSql.contains("AS groupId"), () -> selectId + " 缺少 groupId");
|
||||
assertTrue(selectSql.contains("AS logId"), () -> selectId + " 缺少 logId");
|
||||
assertTrue(selectSql.contains("reasonDetail"), () -> selectId + " 缺少 reasonDetail");
|
||||
assertTrue(!selectSql.contains("where 1 = 0"), () -> selectId + " 仍是占位 SQL");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void withdrawCntObjectRule_shouldUseRealSqlAndKeepObjectHitFields() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
String selectSql = extractSelectSql(xml, "selectWithdrawCntObjects");
|
||||
assertTrue(selectSql.contains("'STAFF_ID_CARD' AS objectType"));
|
||||
assertTrue(selectSql.contains("AS objectKey"));
|
||||
assertTrue(selectSql.contains("reasonDetail"));
|
||||
assertTrue(!selectSql.contains("where 1 = 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,4 +140,13 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private String extractSelectSql(String xml, String selectId) {
|
||||
Pattern pattern = Pattern.compile(
|
||||
"<select\\s+id=\"" + selectId + "\"[\\s\\S]*?</select>"
|
||||
);
|
||||
Matcher matcher = pattern.matcher(xml);
|
||||
assertTrue(matcher.find(), () -> "未找到 select: " + selectId);
|
||||
return matcher.group();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -130,6 +131,38 @@ class BankTagRuleConfigResolverTest {
|
||||
assertEquals("8888", config.getThresholdValue("ANNUAL_TURNOVER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_shouldMapPhaseOneThresholdRulesToUppercaseParamCodes() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
project.setConfigType("default");
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
assertSingleThresholdRule("SUSPICIOUS_FOREIGN_EXCHANGE", "FOREX_BUY_AMT",
|
||||
"SINGLE_PURCHASE_AMOUNT", "50000");
|
||||
assertSingleThresholdRule("SUSPICIOUS_FOREIGN_EXCHANGE", "FOREX_SELL_AMT",
|
||||
"SINGLE_SETTLEMENT_AMOUNT", "60000");
|
||||
assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "WITHDRAW_CNT",
|
||||
"WITHDRAW_CNT", "3");
|
||||
assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "STOCK_TFR_LARGE",
|
||||
"STOCK_TFR_LARGE", "1000000");
|
||||
assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "LARGE_STOCK_TRADING",
|
||||
"STOCK_TFR_LARGE", "1000000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_shouldKeepEmptyThresholdsForPhaseOneRulesWithoutParams() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
project.setConfigType("default");
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
assertRuleHasNoThresholds("SUSPICIOUS_GAMBLING", "GAMBLING_SENSITIVE_KEYWORD");
|
||||
assertRuleHasNoThresholds("SUSPICIOUS_RELATION", "SPECIAL_AMOUNT_TRANSACTION");
|
||||
assertRuleHasNoThresholds("SUSPICIOUS_PART_TIME", "SUSPICIOUS_INCOME_KEYWORD");
|
||||
assertRuleHasNoThresholds("SUSPICIOUS_PURCHASE", "LARGE_PURCHASE_TRANSACTION");
|
||||
}
|
||||
|
||||
private CcdiModelParam buildParam(String paramCode, String paramValue) {
|
||||
CcdiModelParam param = new CcdiModelParam();
|
||||
param.setProjectId(0L);
|
||||
@@ -138,4 +171,42 @@ class BankTagRuleConfigResolverTest {
|
||||
param.setParamValue(paramValue);
|
||||
return param;
|
||||
}
|
||||
|
||||
private void assertSingleThresholdRule(String modelCode, String ruleCode, String paramCode, String paramValue) {
|
||||
when(modelParamMapper.selectByProjectAndModel(0L, modelCode)).thenReturn(List.of(
|
||||
buildParam(modelCode, paramCode, paramValue)
|
||||
));
|
||||
|
||||
CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
|
||||
ruleMeta.setModelCode(modelCode);
|
||||
ruleMeta.setRuleCode(ruleCode);
|
||||
ruleMeta.setIndicatorCode(paramCode);
|
||||
|
||||
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
|
||||
|
||||
assertEquals(Map.of(paramCode, paramValue), config.getThresholdValues());
|
||||
}
|
||||
|
||||
private void assertRuleHasNoThresholds(String modelCode, String ruleCode) {
|
||||
when(modelParamMapper.selectByProjectAndModel(0L, modelCode)).thenReturn(List.of(
|
||||
buildParam(modelCode, "IGNORED_PARAM", "999")
|
||||
));
|
||||
|
||||
CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
|
||||
ruleMeta.setModelCode(modelCode);
|
||||
ruleMeta.setRuleCode(ruleCode);
|
||||
|
||||
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
|
||||
|
||||
assertTrue(config.getThresholdValues().isEmpty());
|
||||
}
|
||||
|
||||
private CcdiModelParam buildParam(String modelCode, String paramCode, String paramValue) {
|
||||
CcdiModelParam param = new CcdiModelParam();
|
||||
param.setProjectId(0L);
|
||||
param.setModelCode(modelCode);
|
||||
param.setParamCode(paramCode);
|
||||
param.setParamValue(paramValue);
|
||||
return param;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import ch.qos.logback.core.read.ListAppender;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagRule;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
import com.ruoyi.ccdi.project.domain.vo.BankTagObjectHitVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.BankTagRuleExecutionConfig;
|
||||
import com.ruoyi.ccdi.project.domain.vo.BankTagStatementHitVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagAnalysisMapper;
|
||||
@@ -24,6 +25,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -243,6 +245,52 @@ class CcdiBankTagServiceImplTest {
|
||||
verify(taskMapper).updateTask(argThat(task -> "SUCCESS".equals(task.getStatus()) && task.getFailedRuleCount() == 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildProject_shouldDispatchWithdrawCntObjectRuleWithResolvedThreshold() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule rule = buildRule("ABNORMAL_BEHAVIOR", "异常行为",
|
||||
"WITHDRAW_CNT", "微信支付宝频繁提现", "OBJECT");
|
||||
BankTagRuleExecutionConfig config = buildConfig(40L, rule);
|
||||
config.setThresholdValues(Map.of("WITHDRAW_CNT", "3"));
|
||||
|
||||
BankTagObjectHitVO hit = new BankTagObjectHitVO();
|
||||
hit.setObjectType("STAFF_ID_CARD");
|
||||
hit.setObjectKey("330101199001011234");
|
||||
hit.setReasonDetail("单日微信提现 4 次,超过阈值 3 次");
|
||||
|
||||
when(ruleMapper.selectEnabledRules("ABNORMAL_BEHAVIOR")).thenReturn(List.of(rule));
|
||||
when(configResolver.resolve(40L, rule)).thenReturn(config);
|
||||
when(analysisMapper.selectWithdrawCntObjects(40L, 3)).thenReturn(List.of(hit));
|
||||
|
||||
service.rebuildProject(40L, "ABNORMAL_BEHAVIOR", "admin", TriggerType.MANUAL);
|
||||
|
||||
verify(analysisMapper).selectWithdrawCntObjects(40L, 3);
|
||||
verify(resultMapper).insertBatch(argThat(results -> results.size() == 1
|
||||
&& "STAFF_ID_CARD".equals(results.get(0).getObjectType())
|
||||
&& "330101199001011234".equals(results.get(0).getObjectKey())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildProject_shouldTreatEmptyWithdrawCntHitsAsSuccess() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule rule = buildRule("ABNORMAL_BEHAVIOR", "异常行为",
|
||||
"WITHDRAW_CNT", "微信支付宝频繁提现", "OBJECT");
|
||||
BankTagRuleExecutionConfig config = buildConfig(40L, rule);
|
||||
config.setThresholdValues(Map.of("WITHDRAW_CNT", "3"));
|
||||
|
||||
when(ruleMapper.selectEnabledRules("ABNORMAL_BEHAVIOR")).thenReturn(List.of(rule));
|
||||
when(configResolver.resolve(40L, rule)).thenReturn(config);
|
||||
when(analysisMapper.selectWithdrawCntObjects(40L, 3)).thenReturn(List.of());
|
||||
|
||||
service.rebuildProject(40L, "ABNORMAL_BEHAVIOR", "admin", TriggerType.MANUAL);
|
||||
|
||||
verify(analysisMapper).selectWithdrawCntObjects(40L, 3);
|
||||
verify(resultMapper, never()).insertBatch(anyList());
|
||||
verify(taskMapper).updateTask(argThat(task -> "SUCCESS".equals(task.getStatus()) && task.getHitCount() == 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkProjectTaggingBeforeExecutingAndCompletedAfterSuccess() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# 银行流水真实规则第一期后端实施记录
|
||||
|
||||
## 第一期规则范围
|
||||
- `GAMBLING_SENSITIVE_KEYWORD`
|
||||
- `SPECIAL_AMOUNT_TRANSACTION`
|
||||
- `SUSPICIOUS_INCOME_KEYWORD`
|
||||
- `FOREX_BUY_AMT`
|
||||
- `FOREX_SELL_AMT`
|
||||
- `LARGE_PURCHASE_TRANSACTION`
|
||||
- `STOCK_TFR_LARGE`
|
||||
- `WITHDRAW_CNT`
|
||||
- `LARGE_STOCK_TRADING`
|
||||
|
||||
## 修改内容
|
||||
- 补齐第一期规则参数映射
|
||||
- 替换第一期 8 条明细型占位 SQL
|
||||
- 接通 `WITHDRAW_CNT` 对象型真实 SQL 与阈值分发
|
||||
- 对齐第一期规则元数据脚本与实施/验证文档
|
||||
|
||||
## 参数映射调整
|
||||
- 在 `BankTagRuleConfigResolver` 中补齐第一期阈值规则映射:
|
||||
- `FOREX_BUY_AMT -> SINGLE_PURCHASE_AMOUNT`
|
||||
- `FOREX_SELL_AMT -> SINGLE_SETTLEMENT_AMOUNT`
|
||||
- `WITHDRAW_CNT -> WITHDRAW_CNT`
|
||||
- `STOCK_TFR_LARGE -> STOCK_TFR_LARGE`
|
||||
- `LARGE_STOCK_TRADING -> STOCK_TFR_LARGE`
|
||||
- 明确无阈值规则仍返回空参数集,不为第一期无参规则补虚假参数。
|
||||
|
||||
## XML 真实 SQL 替换
|
||||
- 在 `CcdiBankTagAnalysisMapper.xml` 中将以下占位 SQL 替换为真实规则:
|
||||
- 赌博敏感词支出流水
|
||||
- 非配偶/子女特殊金额交易流水
|
||||
- 收入关键词转入流水
|
||||
- 单笔购汇超阈值流水
|
||||
- 单笔结汇超阈值流水
|
||||
- 单笔大额采购事项
|
||||
- 银证转账超阈值流水
|
||||
- 三方资管超阈值流水
|
||||
- 微信/支付宝单日频繁提现对象
|
||||
- 明细型规则统一继续输出 `bankStatementId/groupId/logId/reasonDetail`。
|
||||
- `LARGE_PURCHASE_TRANSACTION` 由于来源为采购交易表,不直接来自银行流水表,保留明细结果字段别名并在 `reasonDetail` 中输出采购事项、金额和供应商信息。
|
||||
|
||||
## Service 分发调整
|
||||
- `CcdiBankTagServiceImpl` 为以下规则改为显式透传解析后的阈值:
|
||||
- `FOREX_BUY_AMT`
|
||||
- `FOREX_SELL_AMT`
|
||||
- `STOCK_TFR_LARGE`
|
||||
- `LARGE_STOCK_TRADING`
|
||||
- `WITHDRAW_CNT`
|
||||
- `WITHDRAW_CNT` 命中为空时仍保持任务成功,不回退任务状态与风险人数刷新链路。
|
||||
|
||||
## 元数据脚本调整
|
||||
- 在 `sql/2026-03-16-bank-tagging.sql` 中对齐第一期规则元数据:
|
||||
- `FOREX_BUY_AMT.indicator_code` 改为 `SINGLE_PURCHASE_AMOUNT`
|
||||
- `FOREX_SELL_AMT.indicator_code` 改为 `SINGLE_SETTLEMENT_AMOUNT`
|
||||
- `LARGE_STOCK_TRADING.indicator_code` 改为 `STOCK_TFR_LARGE`
|
||||
- 将第一期已落地规则的 `remark` 从“占位规则,待补充真实SQL”更新为真实规则描述。
|
||||
|
||||
## 与第二期边界
|
||||
- 第二期规则仍保持原有占位 SQL,不在本次修改范围内:
|
||||
- `LOW_INCOME_RELATIVE_LARGE_TRANSACTION`
|
||||
- `MULTI_PARTY_GAMBLING_TRANSFER`
|
||||
- `MONTHLY_FIXED_INCOME`
|
||||
- `FIXED_COUNTERPARTY_TRANSFER`
|
||||
- `HOUSE_REGISTRATION_MISMATCH`
|
||||
- `PROPERTY_FEE_REGISTRATION_MISMATCH`
|
||||
- `TAX_ASSET_REGISTRATION_MISMATCH`
|
||||
- `SUPPLIER_CONCENTRATION`
|
||||
- `SALARY_QUICK_TRANSFER`
|
||||
- `SALARY_UNUSED`
|
||||
|
||||
## 验证执行
|
||||
- 执行 `mvn test -pl ccdi-project -Dtest=BankTagRuleConfigResolverTest`,补齐参数映射后通过。
|
||||
- 执行 `mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest`,第一期 8 条明细规则结构测试通过。
|
||||
- 执行 `mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,CcdiBankTagServiceImplTest`,对象规则与分发测试通过。
|
||||
- 执行 `mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,CcdiBankTagServiceRiskCountRefreshTest`,第一期回归共 27 个测试全部通过。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 银行流水真实规则第一期后端验证记录
|
||||
|
||||
## 执行命令
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -Dtest=BankTagRuleConfigResolverTest
|
||||
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest
|
||||
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,CcdiBankTagServiceImplTest
|
||||
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,CcdiBankTagServiceRiskCountRefreshTest
|
||||
```
|
||||
|
||||
## 执行时间
|
||||
- 2026-03-20 12:38 执行 `BankTagRuleConfigResolverTest` 红灯验证,确认第一期规则参数映射未补齐。
|
||||
- 2026-03-20 13:21 重新执行 `BankTagRuleConfigResolverTest`,6 个测试全部通过。
|
||||
- 2026-03-20 13:23 执行 `CcdiBankTagAnalysisMapperXmlTest` 红灯验证,确认第一期 8 条明细规则仍存在占位 SQL。
|
||||
- 2026-03-20 13:26 重新执行 `CcdiBankTagAnalysisMapperXmlTest`,7 个测试全部通过。
|
||||
- 2026-03-20 13:27 执行 `CcdiBankTagAnalysisMapperXmlTest,CcdiBankTagServiceImplTest` 红灯验证,确认 `WITHDRAW_CNT` 仍是占位 SQL,且阈值未接入分发链路。
|
||||
- 2026-03-20 13:28 重新执行 `CcdiBankTagAnalysisMapperXmlTest,CcdiBankTagServiceImplTest`,19 个测试全部通过。
|
||||
- 2026-03-20 13:30 执行最终回归命令 `mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,CcdiBankTagServiceRiskCountRefreshTest`,27 个测试全部通过,0 failure,0 error。
|
||||
- 2026-03-20 13:31 在补齐规则元数据脚本与文档后再次执行最终回归命令,27 个测试全部通过,0 failure,0 error。
|
||||
|
||||
## 结果摘要
|
||||
- 第一期开启真实 SQL 的 9 条规则中,8 条明细型规则已完成 XML 替换,1 条对象型规则已完成阈值分发与对象命中 SQL。
|
||||
- 第一期开启真实 SQL 后,`CcdiBankTagServiceRiskCountRefreshTest` 继续通过,风险人数回写链路未回退。
|
||||
- 第一期规则元数据脚本已对齐真实状态,`indicator_code` 与参数编码保持全大写。
|
||||
|
||||
## 结论
|
||||
- 第一阶段后端实现已完成,第一期 9 条规则均已接入现有项目级打标链路。
|
||||
- 当前验证仅执行 Maven 单元测试,未启动额外前后端进程,因此无需执行进程清理。
|
||||
@@ -82,25 +82,25 @@ VALUES
|
||||
('ABNORMAL_TRANSACTION', '异常交易', 'ABNORMAL_CUSTOMER_TRANSACTION', '与客户之间非正常资金往来', NULL, 'STATEMENT', 'HIGH', '员工及关系人与客户及关系人之间有超过1000元以上的资金往来;客户指信贷类客户包括贷款户、担保人,中介库人员,包括中介注册的主体及主体关系人。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_TRANSACTION', '异常交易', 'LOW_INCOME_RELATIVE_LARGE_TRANSACTION', '低收入亲属大额交易', NULL, 'OBJECT', 'GENERAL', '关系人中没有收入或月收入低于3000元的人员,累计交易金额超过10万元。', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_GAMBLING', '疑似赌博', 'MULTI_PARTY_GAMBLING_TRANSFER', '疑似赌博交易', NULL, 'OBJECT', 'HIGH', '多人(2人及以上)、多次(2次以上)、相近时间(同一天)内,有转账、微信转账、支付宝转账发生,且额度在可疑区间。金额区间可在排查设置页面进行设置', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_GAMBLING', '疑似赌博', 'GAMBLING_SENSITIVE_KEYWORD', '疑似敏感交易', NULL, 'STATEMENT', 'HIGH', '备注或交易摘要、对手有“游戏、抖币、体彩、福彩”等字眼。', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_RELATION', '可疑关系', 'SPECIAL_AMOUNT_TRANSACTION', '特殊金额交易', NULL, 'STATEMENT', NULL, '除与配偶、子女外,发生特殊金额交易,如1314元、520元等具有特殊含义的金额。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_GAMBLING', '疑似赌博', 'GAMBLING_SENSITIVE_KEYWORD', '疑似敏感交易', NULL, 'STATEMENT', 'HIGH', '备注或交易摘要、对手有“游戏、抖币、体彩、福彩”等字眼。', 1, 20, 'system', '真实规则:识别摘要或对手方命中赌博敏感词的支出流水'),
|
||||
('SUSPICIOUS_RELATION', '可疑关系', 'SPECIAL_AMOUNT_TRANSACTION', '特殊金额交易', NULL, 'STATEMENT', NULL, '除与配偶、子女外,发生特殊金额交易,如1314元、520元等具有特殊含义的金额。', 1, 10, 'system', '真实规则:识别与非配偶子女发生的特殊金额交易'),
|
||||
('SUSPICIOUS_PART_TIME', '可疑兼职', 'MONTHLY_FIXED_INCOME', '疑似兼职', 'MONTHLY_FIXED_INCOME', 'OBJECT', NULL, '除本行工资收入外,每月有固定收入,固定收入金额自行设置。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PART_TIME', '可疑兼职', 'FIXED_COUNTERPARTY_TRANSFER', '疑似兼职', 'FIXED_COUNTERPARTY_TRANSFER', 'OBJECT', NULL, '每季或每年从固定交易对手转入金额,金额可设区间值,如5000-10000。', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PART_TIME', '可疑兼职', 'SUSPICIOUS_INCOME_KEYWORD', '疑似兼职', NULL, 'STATEMENT', 'HIGH', '转入资金摘要有“工资”、“分红”、“红利”、“利息(非银行结息)”等收入', 1, 30, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PART_TIME', '可疑兼职', 'SUSPICIOUS_INCOME_KEYWORD', '疑似兼职', NULL, 'STATEMENT', 'HIGH', '转入资金摘要有“工资”、“分红”、“红利”、“利息(非银行结息)”等收入', 1, 30, 'system', '真实规则:识别非本行工资代发的收入关键词转入流水'),
|
||||
('SUSPICIOUS_PROPERTY', '可疑财产', 'HOUSE_REGISTRATION_MISMATCH', '购房交易与房产登记不匹配', NULL, 'STATEMENT', NULL, '员工及关系人有购房交易,但名下房产无新增登记;有新增登记购房,但无相关购房交易记录。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PROPERTY', '可疑财产', 'PROPERTY_FEE_REGISTRATION_MISMATCH', '物业缴费与房产登记不匹配', NULL, 'STATEMENT', NULL, '员工及关系人有物业缴费记录,但名下房产无新增登记。', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PROPERTY', '可疑财产', 'TAX_ASSET_REGISTRATION_MISMATCH', '大额纳税与资产登记不匹配', NULL, 'STATEMENT', NULL, '员工及关系人有5000元以上的纳税记录,但名下无房产车产新增登记。', 1, 30, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PROPERTY', '可疑财产', 'INCOME_ASSET_MISMATCH', '收入资产不符', NULL, 'STATEMENT', 'HIGH', '豪华房产价值超家庭年收入10倍', 1, 40, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_FOREIGN_EXCHANGE', '可疑外汇交易', 'FOREX_BUY_AMT', '可疑外汇交易', 'FOREX_BUY_AMT', 'STATEMENT', NULL, '单笔购汇金额超限', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_FOREIGN_EXCHANGE', '可疑外汇交易', 'FOREX_SELL_AMT', '可疑外汇交易', 'FOREX_SELL_AMT', 'STATEMENT', NULL, '单笔结汇金额超限', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_FOREIGN_EXCHANGE', '可疑外汇交易', 'FOREX_BUY_AMT', '可疑外汇交易', 'SINGLE_PURCHASE_AMOUNT', 'STATEMENT', NULL, '单笔购汇金额超限', 1, 10, 'system', '真实规则:识别单笔购汇金额超过阈值的流水'),
|
||||
('SUSPICIOUS_FOREIGN_EXCHANGE', '可疑外汇交易', 'FOREX_SELL_AMT', '可疑外汇交易', 'SINGLE_SETTLEMENT_AMOUNT', 'STATEMENT', NULL, '单笔结汇金额超限', 1, 20, 'system', '真实规则:识别单笔结汇金额超过阈值的流水'),
|
||||
('SUSPICIOUS_FOREIGN_EXCHANGE', '可疑外汇交易', 'CROSS_BORDER_AMT', '可疑外汇交易', 'CROSS_BORDER_AMT', 'STATEMENT', NULL, '单笔跨境汇款金额超限', 1, 30, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_INTEREST_PAYMENT', '可疑付息', 'INTEREST_PAYMENT_BY_OTHERS', '可疑付息', NULL, 'OBJECT', 'HIGH', '客户经理管户的客户在智柜、柜面连续代交利息,且代交人数超过2人。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PURCHASE', '可疑采购', 'LARGE_PURCHASE_TRANSACTION', '可疑采购', NULL, 'STATEMENT', NULL, '单笔采购金额超过10万元。', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('SUSPICIOUS_PURCHASE', '可疑采购', 'LARGE_PURCHASE_TRANSACTION', '可疑采购', NULL, 'STATEMENT', NULL, '单笔采购金额超过10万元。', 1, 10, 'system', '真实规则:识别单笔采购金额超过10万元的采购事项'),
|
||||
('SUSPICIOUS_PURCHASE', '可疑采购', 'SUPPLIER_CONCENTRATION', '可疑采购', NULL, 'OBJECT', NULL, '单个供应商采购额占总采购额比例超过70%。', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'STOCK_TFR_LARGE', '可疑银证大额转账', 'STOCK_TFR_LARGE', 'STATEMENT', NULL, '家庭老人/非关系人银证大额转账', 1, 10, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'WITHDRAW_CNT', '微信支付宝频繁提现', 'WITHDRAW_CNT', 'OBJECT', NULL, '微信、支付宝单日提现次数超过设置次数', 1, 20, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'STOCK_TFR_LARGE', '可疑银证大额转账', 'STOCK_TFR_LARGE', 'STATEMENT', NULL, '家庭老人/非关系人银证大额转账', 1, 10, 'system', '真实规则:识别银证转账金额超过阈值的流水'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'WITHDRAW_CNT', '微信支付宝频繁提现', 'WITHDRAW_CNT', 'OBJECT', NULL, '微信、支付宝单日提现次数超过设置次数', 1, 20, 'system', '真实规则:识别微信支付宝单日提现次数超过阈值的对象'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'WITHDRAW_AMT', '微信支付宝提现超额', 'WITHDRAW_AMT', 'OBJECT', NULL, '微信、支付宝单日累计提现金额超过限额', 1, 30, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'SALARY_QUICK_TRANSFER', '工资快速转出', NULL, 'OBJECT', NULL, '工资发放后24小时内转出超过80%的资金', 1, 40, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'SALARY_UNUSED', '工资无使用记录', NULL, 'OBJECT', NULL, '工资发放后除代扣项目外,连续30天犖奕魏蜗鸦蜃思锹肌', 1, 50, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'LARGE_STOCK_TRADING', '大额炒股', NULL, 'STATEMENT', 'HIGH', '单次三方资管交易金额超过100万元。', 1, 60, 'system', '占位规则,待补充真实SQL'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'LARGE_STOCK_TRADING', '大额炒股', 'STOCK_TFR_LARGE', 'STATEMENT', 'HIGH', '单次三方资管交易金额超过100万元。', 1, 60, 'system', '真实规则:识别单笔三方资管交易金额超过阈值的流水'),
|
||||
('ABNORMAL_BEHAVIOR', '异常行为', 'PROXY_ACCOUNT_OPERATION', '疑似代理他人账户', NULL, 'OBJECT', NULL, NULL, 1, 70, 'system', '占位规则,待补充真实SQL');
|
||||
|
||||
Reference in New Issue
Block a user