程式碼回現 | 如何實現交易反欺詐?
一、背景概述
二、例項回現
2.1準備工作
<Topics enabled="true"> <properties> <property name="port">9999</property> <property name="group.initial.rebalance.delay.ms">0</property> <property name="retention.policy.threads">1</property> </properties> <profiles> <profile name="retain_compact"> <retention policy="compact" limit="2048" /> </profile> </profiles> </Topics>
CREATE Topic TRAINTOPIC execute procedure train_events.insert; CREATE TOPIC RECHARGE execute procedure RechargeCard; CREATE TOPIC using stream CARD_ALERT_EXPORT properties(topic.format=avro); create topic using stream FRAUD properties(topic.format=avro,consumer.keys=TRANS_ID);
csvloader --file $PROJ_HOME/data/redline.csv --reportdir log stations csvloader --file $PROJ_HOME/data/trains.csv --reportdir log trains
2.2 程式碼分析-列車執行
java metro.pub.TrainProducer localhost:9999 TRAINTOPIC 8
public static void main(String[] args) { ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(10); TrainProducer producer = new TrainProducer(args[0], args[1], Integer.parseInt(args[2])); System.out.println("Scheduling trains"); EXECUTOR.scheduleAtFixedRate ( () -> { producer.publish(producer.getNewEvents()); }, 1, 50, MILLISECONDS); }
private Producer<String, TrainEvent> createProducer() { Properties props = new Properties(); props.put("bootstrap.servers", brokers); props.put("acks", "all"); props.put("retries", 0); props.put("batch.size", 16384); props.put("linger.ms", 1); props.put("buffer.memory", 33554432); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "metro.serde.TrainEventSer"); Producer<String, TrainEvent> producer = new KafkaProducer <String, TrainEvent>(props); return producer; }
static HashMap<Integer, Station> idToStationMap = new HashMap<>(); static { idToStationMap.put(1, new Station(1, 1200000, 450000)); idToStationMap.put(2, new Station(2, 1050000, 250000)); idToStationMap.put(3, new Station(3, 850000, 300000)); idToStationMap.put(4, new Station(4, 900000, 350000)); idToStationMap.put(5, new Station(5, 500000, 260000)); idToStationMap.put(6, new Station(6, 950000, 190000)); idToStationMap.put(7, new Station(7, 450000, 130000)); idToStationMap.put(8, new Station(8, 200000, 280000)); idToStationMap.put(9, new Station(9, 200000, 110000)); idToStationMap.put(10, new Station(10, 450000, 300000)); idToStationMap.put(11, new Station(11, 550000, 200000)); idToStationMap.put(12, new Station(12, 550000, 200000)); idToStationMap.put(13, new Station(13, 800000, 150000)); idToStationMap.put(14, new Station(14, 950000, 100000)); idToStationMap.put(15, new Station(15, 1000000, 130000)); idToStationMap.put(16, new Station(16, 1200000, 220000)); idToStationMap.put(17, new Station(17, 1500000, 500000)); } public static class Station { public final int stationId; public final int nextStnDuration; public final int stnWaitDuration; public Station(int stationId, int nextStnDuration, int stnWaitDuration) { this.stationId = stationId; this.nextStnDuration = nextStnDuration; this.stnWaitDuration = stnWaitDuration; } }
public List<TrainEvent> getNewEvents() { ArrayList<TrainEvent> records = new ArrayList<>(); for(TrainEvent trainEvent : idToTrainMap.values()) { LastKnownLocation prevLoc = trainEvent.location; LastKnownLocation curLoc = next(prevLoc, LocalDateTime.now()); if(!prevLoc.equals(curLoc)) { trainEvent = new TrainEvent(trainEvent.trainId, curLoc); idToTrainMap.put(trainEvent.trainId, trainEvent); records.add(trainEvent); } } return records; }
CREATE Topic TRAINTOPIC execute procedure train_events.insert;
2.23 程式碼分析-公交卡充值
java metro.pub.CardsProducer --mode=recharge --servers=localhost:9999 --Topic=RECHARGE
public static void main(String[] args) throws IOException { CONFIG.parse("CardsProducer", args); if(CONFIG.mode.equals("new")) { genCards(CONFIG); return; } ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(10); CardsProducer producer = new CardsProducer(CONFIG.servers, CONFIG.Topic); System.out.println("Recharging Cards"); EXECUTOR.scheduleAtFixedRate ( () -> { producer.publish(producer.getRechargeActivityRecords(1)); }, 1, 5, MILLISECONDS); }
public List<CardEvent> getRechargeActivityRecords(int count) { final ArrayList<CardEvent> records = new ArrayList<>(); int amt = (ThreadLocalRandom.current().nextInt(18)+2)*1000; int stationId = ThreadLocalRandom.current().nextInt(1, 18); ThreadLocalRandom.current().ints(count, 0, CONFIG.cardcount).forEach((cardId) -> { records.add(new CardEvent(cardId, amt, stationId)); } ); return records; }
CREATE TOPIC RECHARGE execute procedure RechargeCard;
sqlcmd --query="load classes $PROJ_HOME/dist/procs.jar" CREATE PROCEDURE PARTITION ON TABLE cards COLUMN card_id PARAMETER 0 FROM CLASS metro.cards.RechargeCard;
public class RechargeCard extends VoltProcedure { public final SQLStmt updateBalance = new SQLStmt("UPDATE cards SET balance = balance + ? WHERE card_id = ? AND card_type = 0"); public final SQLStmt getCard = new SQLStmt("SELECT * from cards WHERE card_id = ?"); public final SQLStmt exportNotif = new SQLStmt("INSERT INTO CARD_ALERT_EXPORT values (?, NOW, ?, ?, ?, ?, ?, ?)"); public final SQLStmt getStationName = new SQLStmt("SELECT name FROM stations WHERE station_id = ?"); public long run(int cardId, int amt, int stationId) { voltQueueSQL(getStationName, stationId); voltQueueSQL(getCard, cardId); String station = "UNKNOWN"; final VoltTable[] results = voltExecuteSQL(); if(results.length == 0) exportError(cardId, station); VoltTable stationResult = results[0]; if(stationResult.advanceRow()) station = stationResult.getString(0); VoltTable card = results[1]; if(card.advanceRow()) { voltQueueSQL(updateBalance, amt, cardId); String name = card.getString(5); String phone = card.getString(6); String email = card.getString(7); int notify = (int) card.getLong(8); voltQueueSQL(updateBalance, amt, cardId); voltQueueSQL(exportNotif, cardId, station, name, phone, email, notify, "Card recharged successfully"); voltExecuteSQL(true); } else { exportError(cardId, station); } return 0; } private void exportError(int cardId, String station) { exportError(cardId, station, "", "", "", 0, "Could not locate details of card for recharge"); } private void exportError(int cardId, String station, String name, String phone, String email, int notify, String msg) { voltQueueSQL(exportNotif, cardId, station, name, phone, email, notify, msg); voltExecuteSQL(true); } }
public final SQLStmt exportNotif = new SQLStmt("INSERT INTO CARD_ALERT_EXPORT values (?, NOW, ?, ?, ?, ?, ?, ?)");
CREATE VIEW card_export_stats(card_id, station_name, rechargeCount) AS SELECT card_id, station_name, count(*) from CARD_ALERT_EXPORT GROUP BY card_id, station_name;
CREATE TOPIC using stream CARD_ALERT_EXPORT properties(Topic.format=avro);
2.4 程式碼分析-乘客刷卡乘車
void connectToOneServerWithRetry(String server, Client client) { int sleep = 1000; while (true) { try { client.createConnection(server); break; } catch (Exception e) { System.err.printf("Connection failed - retrying in %d second(s).\n", sleep / 1000); try { Thread.sleep(sleep); } catch (Exception interruted) {} if (sleep < 8000) sleep += sleep; } } System.out.printf("Connected to VoltDB node at: %s.\n", server); }
private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(100); public void runBenchmark() throws Exception { int microsPerTrans = 1000000/RidersProducer.config.rate; EXECUTOR.scheduleAtFixedRate ( () -> { List<Object[]> entryRecords = getEntryActivityRecords(config.cardcount);//生成隨機的進站記錄 call(config.cardEntry, entryRecords);//將資料傳送到VoltDB資料庫 }, 10000, microsPerTrans, MICROSECONDS); } public static List<Object[]> getEntryActivityRecords(int count) { final ArrayList<Object[]> records = new ArrayList<>(); long curTime = System.currentTimeMillis(); ThreadLocalRandom.current().ints(1, 0, count).forEach((cardId) -> { records.add(new Object[] {cardId, curTime, Stations.getRandomStation().stationId, ENTER.value, 0}); } ); return records; }
protected static void call(String proc, Object[] args) { try { client.callProcedure(new BenchmarkCallback(proc, args), procName, args); } catch (IOException e) { e.printStackTrace(); } }
@Option(desc = "Proc for card entry swipes") String cardEntry = "ValidateEntry";
//查詢公交卡是否存在 public final SQLStmt checkCard = new SQLStmt( "SELECT enabled, card_type, balance, expires, name, phone, email, notify FROM cards WHERE card_id = ?;"); //卡充值 public final SQLStmt chargeCard = new SQLStmt( "UPDATE cards SET balance = ? WHERE card_id = ?;"); //查詢指定站點的入站費用 public final SQLStmt checkStationFare = new SQLStmt( "SELECT fare, name FROM stations WHERE station_id = ?;"); //記錄進站事件 public final SQLStmt insertActivity = new SQLStmt( "INSERT INTO card_events (card_id, date_time, station_id, activity_code, amount, accept) VALUES (?,?,?,?,?,?);"); //再次用到card_alert_export 這個stream物件,用於傳送公交卡欠費訊息 public final SQLStmt exportActivity = new SQLStmt( "INSERT INTO card_alert_export (card_id, export_time, station_name, name, phone, email, notify, alert_message) VALUES (?,?,?,?,?,?,?,?);"); //將刷卡欺詐行為寫入stream物件fraud中 public final SQLStmt publishFraud = new SQLStmt( "INSERT INTO fraud (trans_id, card_id, date_time, station, activity_type, amt) values (?, ?, ?, ?, ?, ?)" );
CREATE STREAM FRAUD partition on column CARD_ID ( TRANS_ID varchar not null, CARD_ID integer not null, DATE_TIME timestamp not null, STATION integer not null, ACTIVITY_TYPE TINYINT not null, AMT integer not null ); create Topic using stream FRAUD properties(Topic.format=avro,consumer.keys=TRANS_ID);
public VoltTable run(int cardId, long tsl, int stationId, byte activity_code, int amt) throws VoltAbortException { //查詢公交卡是否存在 voltQueueSQL(checkCard, EXPECT_ZERO_OR_ONE_ROW, cardId); //查詢指定站點的交通費用 voltQueueSQL(checkStationFare, EXPECT_ONE_ROW, stationId); VoltTable[] checks = voltExecuteSQL(); VoltTable cardInfo = checks[0]; VoltTable stationInfo = checks[1]; byte accepted = 0; //如果公交卡記錄等於0,說明卡不存在 if (cardInfo.getRowCount() == 0) { //記錄刷卡行為到資料庫表中,將accept欄位置為拒絕“REJECTED” voltQueueSQL(insertActivity, cardId, tsl, stationId, ACTIVITY_ENTER, amt, ACTIVITY_REJECTED); voltExecuteSQL(true); //返回“被拒絕”訊息給客戶端。 return buildResult(accepted,"Card Invalid"); } // 如果卡存在,則取出卡資訊。 cardInfo.advanceRow(); //卡狀態,0不可用,1可用 int enabled = (int)cardInfo.getLong(0); int cardType = (int)cardInfo.getLong(1); //卡餘額 int balance = (int)cardInfo.getLong(2); TimestampType expires = cardInfo.getTimestampAsTimestamp(3); String owner = cardInfo.getString(4); String phone = cardInfo.getString(5); String email = cardInfo.getString(6); int notify = (int)cardInfo.getLong(7); // 查詢指定站點的進站費用 stationInfo.advanceRow(); //指定站點的進站費用 int fare = (int)stationInfo.getLong(0); String stationName = stationInfo.getString(1); // 刷卡時間 TimestampType ts = new TimestampType(tsl); // 如果卡狀態為不可用 if (enabled == 0) { //向客戶端返回“此卡不可用” return buildResult(accepted,"Card Disabled"); } // 如果卡型別為“非月卡” if (cardType == 0) { // 如果卡內餘額充足 if (balance > fare) { //isFrand為反欺詐策略,後面介紹 if (isFraud(cardId, ts, stationId)) { // 如果認定為欺詐,記錄刷卡記錄,記錄型別為“欺詐刷卡” voltQueueSQL(insertActivity, cardId, ts, stationId, ACTIVITY_ENTER, fare, ACTIVITY_FRAUD); //並且把欺詐事件寫入stream,並最終被髮布到VoltDB Topic中。見前面STREAM FRAUD到ddl定義 voltQueueSQL(publishFraud, generateId(cardId, tsl), cardId, ts, stationId, ACTIVITY_ENTER, amt); voltExecuteSQL(true); //向客戶端返回“欺詐交易”訊息 return buildResult(0, "Fraudulent transaction"); } else { // 如果不是欺詐行為,則減少卡內餘額,完成正常消費 voltQueueSQL(chargeCard, balance - fare, cardId); //記錄正常的刷卡事件 voltQueueSQL(insertActivity, cardId, ts, stationId, ACTIVITY_ENTER, fare, ACTIVITY_ACCEPTED); voltExecuteSQL(true); //向客戶端返回卡內餘額 return buildResult(1, "Remaining Balance: " + intToCurrency(balance - fare)); } } else { // 如果卡內餘額不足,記錄刷卡失敗事件。 voltQueueSQL(insertActivity, cardId, ts, stationId, ACTIVITY_ENTER, 0, ACTIVITY_REJECTED); if (notify != 0) { //再次用到card_alert_export 這個stream物件,用於傳送公交卡欠費訊息 voltQueueSQL(exportActivity, cardId, getTransactionTime().getTime(), stationName, owner, phone, email, notify, "Insufficient Balance"); } voltExecuteSQL(true); //向客戶端返回“餘額不足“訊息 return buildResult(0,"Card has insufficient balance: "+intToCurrency(balance)); } } }
isFraud方法根據當前刷卡記錄中的資料,結合資料庫中的歷史記錄實現以上反欺詐規則。歷史刷卡記錄被儲存在card_events表中,另外基於這張表建立了檢視,統計每張卡在一秒鐘內是否有過刷卡記錄。
CREATE VIEW CARD_HISTORY_SECOND as select card_id, TRUNCATE(SECOND, date_time) scnd from card_events group by card_id, scnd; isFraud方法的定義 public final SQLStmt cardHistoryAtStations = new SQLStmt( "SELECT activity_code, COUNT(DISTINCT station_id) AS stations " + "FROM card_events " + "WHERE card_id = ? AND date_time >= DATEADD(HOUR, -1, ?) " + "GROUP BY activity_code;" ); public final SQLStmt cardEntries = new SQLStmt( "SELECT activity_code " + "FROM card_events " + "WHERE card_id = ? AND station_id = ? AND date_time >= DATEADD(HOUR, -1, ?) " + "ORDER BY date_time;" ); public final SQLStmt instantaneousCardActivity = new SQLStmt( "SELECT count(*) as activity_count " + "FROM CARD_HISTORY_SECOND " + "WHERE card_id = ? " + "AND scnd = TRUNCATE(SECOND, ?) " + "GROUP BY scnd;" ); public boolean isFraud(int cardId, TimestampType ts, int stationId) { voltQueueSQL(instantaneousCardActivity, cardId, ts); voltQueueSQL(cardHistoryAtStations, cardId, ts); voltQueueSQL(cardEntries, cardId, stationId, ts); final VoltTable[] results = voltExecuteSQL(); final VoltTable cardInstantaneousActivity = results[0]; final VoltTable cardHistoryAtStationisTable = results[1]; final VoltTable cardEntriesTable = results[2]; //一秒鐘之內已經有一次刷卡記錄的話,返回true while (cardInstantaneousActivity.advanceRow()) { if(cardInstantaneousActivity.getLong("activity_count") > 0) { return true; } } while (cardHistoryAtStationisTable.advanceRow()) { final byte activity_code = (byte) cardHistoryAtStationisTable.getLong("activity_code"); final long stations = cardHistoryAtStationisTable.getLong("stations"); if (activity_code == ACTIVITY_ENTER) { // 過去1小時之內在五個站點刷卡進站,返回true if (stations >= 5) { return true; } } } byte prevActivity = ACTIVITY_INVALID; int entranceCount = 0; while (cardEntriesTable.advanceRow()) { final byte activity_code = (byte) cardHistoryAtStationisTable.getLong("activity_code"); if (prevActivity == ACTIVITY_INVALID || prevActivity == activity_code) { if (activity_code == ACTIVITY_ENTER) { prevActivity = activity_code; entranceCount++; } else { prevActivity = ACTIVITY_INVALID; } } } // 如果在過去1小時內有10次連續的刷卡記錄,返回true。 if (entranceCount >= 10) { return true; } return false; }
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69903219/viewspace-2768059/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 金融反欺詐-交易基礎介紹
- 反欺詐(羊毛盾)API 實現使用者行為分析的思路分析API
- 直播回顧|關聯網路如何反團伙欺詐——標準答案版
- 基於圖資料庫 NebulaGraph 實現的欺詐檢測方案及程式碼示例資料庫
- 基於Vue2和Node.js的反欺詐系統設計與實現VueNode.js
- 面部識別必看!5篇頂級論文了解如何實現人臉反欺詐、跨姿勢識別
- 金融風控反欺詐之圖演算法演算法
- 反欺詐中所用到的機器學習模型有哪些?機器學習模型
- 網易雙11“超級工程”:反欺詐系統應用實踐
- 研究發現Apple Pay欺詐交易量佔總交易數量的6% 是銀行的6倍APP
- 騰訊安全聯合釋出《2020中國移動廣告反欺詐白皮書》,深度揭秘三大反欺詐主流模式模式
- 最新釣魚式網路欺詐手段現身(轉)
- 如何實現程式碼高亮
- Stripe如何解決信用卡欺詐? - Patrick
- 金融欺詐資料分析
- 《大學生金融反欺詐調研報告》釋出!新浪數科助力“無詐校園”
- jQuery實現點選回車執行指定程式碼jQuery
- 基於Flink的超大規模線上實時反欺詐系統的建設與實踐
- ICO調查:80%是欺詐專案,只有8%在交易所上市
- 零壹智庫&猛獁:中國金融反欺詐技術應用報告
- 在 Swoole 伺服器程式中如何實現壓力反饋伺服器
- 騰訊安全釋出《2021年移動廣告反欺詐白皮書》:2021年廣告主因欺詐致損高達220億
- 如何實現LBS軌跡回放功能?含多平臺實現程式碼
- 看我如何用定值 Cookie 實現反爬Cookie
- jQuery實現的監聽回車按鍵程式碼例項jQuery
- 點選回車實現表單提交效果程式碼例項
- 產業安全專家談 | 數字化轉型過程中,企業如何建立頂級反欺詐能力?產業
- 分析|無感驗證:應用適老化與業務反欺詐的“守門員”
- Swift如何純程式碼實現時鐘效果Swift
- DW如何實現程式碼行縮排效果
- Promise 程式碼實現Promise
- 機器學習專案實戰----信用卡欺詐檢測(一)機器學習
- 機器學習專案實戰----信用卡欺詐檢測(二)機器學習
- 秒針與騰訊共建「廣告反欺詐實驗室」致力於淨化網際網路廣告環境
- eMarketer:汽車營銷如何實現投資回報
- 谷歌:2022年Google Play阻止超20億美元欺詐和濫用交易谷歌Go
- 微信去除 防欺詐盜號請勿支付或輸入qq密碼 以及 防欺詐或盜號請不要輸入qq密碼 的方法密碼
- 阻止點選回車實現的表單提交程式碼例項