|
|
@@ -0,0 +1,125 @@
|
|
|
+package com.mokamrp.data.udj;
|
|
|
+
|
|
|
+import com.aliyun.odps.Column;
|
|
|
+import com.aliyun.odps.OdpsType;
|
|
|
+import com.aliyun.odps.Yieldable;
|
|
|
+import com.aliyun.odps.data.ArrayRecord;
|
|
|
+import com.aliyun.odps.data.Record;
|
|
|
+import com.aliyun.odps.udf.DataAttributes;
|
|
|
+import com.aliyun.odps.udf.ExecutionContext;
|
|
|
+import com.aliyun.odps.udf.UDJ;
|
|
|
+import com.aliyun.odps.udf.annotation.Resolve;
|
|
|
+
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.Queue;
|
|
|
+import java.util.concurrent.ConcurrentLinkedQueue;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author wudi
|
|
|
+ */
|
|
|
+@Resolve("->string,string,bigint,bigint") // ID, 指向ID, 分配金额, 分配后余额
|
|
|
+public class FifoStatementJoin extends UDJ {
|
|
|
+ private final static int NEW_ID = 0;
|
|
|
+ private final static int NEW_AMOUNT = 1;
|
|
|
+
|
|
|
+ private final static int REMAIN_ID = 0;
|
|
|
+
|
|
|
+ private final static int REMAIN_BALANCE = 1;
|
|
|
+
|
|
|
+ private final static int RESULT_ID = 0;
|
|
|
+ private final static int RESULT_TARGET = 1;
|
|
|
+ private final static int RESULT_AMOUNT = 2;
|
|
|
+ private final static int RESULT_BALANCE = 3;
|
|
|
+
|
|
|
+ static class RechargeRecord {
|
|
|
+ String id;
|
|
|
+ Long balance;
|
|
|
+
|
|
|
+ RechargeRecord (String id, Long balance) {
|
|
|
+ this.id = id;
|
|
|
+ this.balance = balance;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Record outputRecord;
|
|
|
+ @Override
|
|
|
+ public void setup(ExecutionContext executionContext, DataAttributes dataAttributes) {
|
|
|
+ System.out.println("FIFO JOIN Queue Version 20230830");
|
|
|
+ outputRecord = new ArrayRecord(new Column[]{
|
|
|
+ new Column("id", OdpsType.STRING),
|
|
|
+ new Column("target_id", OdpsType.STRING),
|
|
|
+ new Column("distributed", OdpsType.BIGINT),
|
|
|
+ new Column("balance", OdpsType.BIGINT),
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void join(Record key, Iterator<Record> left, Iterator<Record> right, Yieldable<Record> output) {
|
|
|
+ // key为关联条件(与ON出现次序一致),这里不用考虑
|
|
|
+ // left 为账单记录,升序排序
|
|
|
+ if (!right.hasNext()) {
|
|
|
+ // 左表为账单记录
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Queue<RechargeRecord> recharges = new ConcurrentLinkedQueue<>();
|
|
|
+ // right 为分配总表,倒序取各id的最新值
|
|
|
+ left.forEachRemaining(record -> recharges.add(new RechargeRecord(record.getString(REMAIN_ID), record.getBigint(REMAIN_BALANCE))));
|
|
|
+ // emit
|
|
|
+ while (right.hasNext()) {
|
|
|
+ Record record = right.next();
|
|
|
+ String id = record.getString(NEW_ID);
|
|
|
+// System.out.printf("FIFO JOIN Queue Size: %d%n ID: %s\n", recharges.size(), id);
|
|
|
+ long amount = record.getBigint(NEW_AMOUNT);
|
|
|
+ outputRecord.setString(RESULT_ID, id);
|
|
|
+ if (amount >= 0) {
|
|
|
+ // 充值直接原样照搬一条
|
|
|
+ recharges.add(new RechargeRecord(id, amount));
|
|
|
+// System.out.println("FIFO JOIN Queue Add: " + id);
|
|
|
+ outputRecord.setString(RESULT_TARGET, id);
|
|
|
+ outputRecord.setBigint(RESULT_AMOUNT, amount);
|
|
|
+ outputRecord.setBigint(RESULT_BALANCE, amount);
|
|
|
+ output.yield(outputRecord);
|
|
|
+ } else {
|
|
|
+ // 消费/退款
|
|
|
+ boolean fail = true;
|
|
|
+ do {
|
|
|
+ // 一笔消费对应1-N条充值
|
|
|
+ RechargeRecord current = recharges.peek();
|
|
|
+ if (current == null) {
|
|
|
+ System.err.println("FIFO JOIN Queue Empty: " + id);
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ fail = false;
|
|
|
+ outputRecord.setString(RESULT_TARGET, current.id);
|
|
|
+ if (current.balance >= Math.abs(amount)) {
|
|
|
+ // 该笔充值够用
|
|
|
+ current.balance += amount; // amount is negative
|
|
|
+ outputRecord.setBigint(RESULT_AMOUNT, amount);
|
|
|
+ outputRecord.setBigint(RESULT_BALANCE, current.balance);
|
|
|
+ amount = 0;
|
|
|
+ } else {
|
|
|
+ // 该笔充值不够用,继续
|
|
|
+ amount += current.balance;
|
|
|
+ outputRecord.setBigint(RESULT_AMOUNT, -current.balance);
|
|
|
+ outputRecord.setBigint(RESULT_BALANCE, 0L);
|
|
|
+ recharges.remove();
|
|
|
+ }
|
|
|
+ output.yield(outputRecord);
|
|
|
+ }
|
|
|
+ } while (amount != 0);
|
|
|
+ if (fail) {
|
|
|
+ // 找不到关联,输出空值
|
|
|
+ outputRecord.set(RESULT_TARGET, null);
|
|
|
+ outputRecord.set(RESULT_AMOUNT, null);
|
|
|
+ outputRecord.set(RESULT_BALANCE, null);
|
|
|
+ output.yield(outputRecord);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void close() {
|
|
|
+
|
|
|
+ }
|
|
|
+}
|