diff --git a/build.gradle b/build.gradle index a93d4ae8d0f0410dc355b9fa00466345ce313c3b..90ad297ceaf9a2a9b672a6b152a39f4e55978fd7 100644 --- a/build.gradle +++ b/build.gradle @@ -60,8 +60,9 @@ dependencies { implementation group: 'org.apache.cxf', name: 'cxf-core', version: '3.3.4' implementation 'org.bdware.doip:doip-audit-tool:1.6.0' implementation 'com.github.ben-manes.caffeine:caffeine:2.9.0' + implementation "org.bdware.bdcontract:ypk-deploy-tool:0.7.5" testImplementation "org.bdware.bdcontract:ypk-deploy-tool:0.7.5" - testImplementation "org.bdware.bdcontract:sdk-java:1.0.2" + implementation "org.bdware.bdcontract:sdk-java:1.0.2" } group = "org.bdware.datanet.router" diff --git a/src/main/java/org/bdware/datanet/router/Config.java b/src/main/java/org/bdware/datanet/router/Config.java index 02f1b5d2dc9c7d533c25be31424975ccd6092928..c42bbac314d3cae4814c79ddd88f538a10020088 100644 --- a/src/main/java/org/bdware/datanet/router/Config.java +++ b/src/main/java/org/bdware/datanet/router/Config.java @@ -120,6 +120,19 @@ public class Config { configStorage.put("pubKey", pubKey); } + public String getPrivKey(){ + if (configStorage.get("privKey") == null) { + // test + return "1d4196947f59532db6f8f4055e58474a48db8f30b476ae3edc66406464521b3b"; + } + String privKey = configStorage.get("privKey"); + return privKey; + } + + public void setPrivKey(String privKey) { + configStorage.put("privKey", privKey); + } + public void setScheme(String scheme) { configStorage.put("scheme", scheme); } diff --git a/src/main/java/org/bdware/datanet/router/Global.java b/src/main/java/org/bdware/datanet/router/Global.java index 9fe6435574ab87659241880b3a7218287d929e99..1d672d404219c067d2ea1b770b9ce03a49f5aa3b 100644 --- a/src/main/java/org/bdware/datanet/router/Global.java +++ b/src/main/java/org/bdware/datanet/router/Global.java @@ -1,8 +1,6 @@ package org.bdware.datanet.router; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.*; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; @@ -56,6 +54,12 @@ public class Global { // key = suffix of doid; value = doid; public static RocksDBUtil doStorage = RocksDBUtil.loadDB("./doStorage", false); + // key = suffix of doid; value = datadoidcnt 、 prefixcnt; + public static RocksDBUtil lrsStatisticsStorage = RocksDBUtil.loadDB("./lrsStatisticsStorage", false); + + // key = pubkey, value = permission; 'ac' stands for ABAC (Attribute-Based Access Control), which prefers permissions from lower nodes + public static RocksDBUtil acStorage = RocksDBUtil.loadDB("./acTable", false); + // key = timestamp; value = doid public static RocksDBUtil doidIndex = RocksDBUtil.loadDB("./doIndex", "doIndex"); public static RocksDBUtil indexStorage = RocksDBUtil.loadDB("./indexStorage", false); @@ -159,6 +163,7 @@ public class Global { config.setUpperIP(jsonObject.get("routerIP").getAsString()); config.setUpperPort(jsonObject.get("routerPort").getAsString()); config.setPubKey(publicKey); + config.setPrivKey(privateKey); config.setName(name); SM2KeyPair keyPair = SM2KeyPair.fromJson(jsonObject.toString()); String signature = null; @@ -182,6 +187,7 @@ public class Global { auditRepo.addProperty("address", jsonObject.get(key).getAsString()); auditRepo.addProperty("status", "online"); auditRepo.addProperty("protocol", "DOIP"); + auditRepo.addProperty("type","system/unknown"); auditRepo.add("pubKey", jsonObject.get("publicKey")); auditRepo.addProperty("version", "2.1"); Router.createInternal(auditRepo, Global.repoStorage); @@ -197,8 +203,10 @@ public class Global { isVerified = false; verifyUpperRouter(); isVerified = true; + InfoSendTicker.init(); StatusChecker.init(); } catch (Exception e) { + InfoSendTicker.init(); StatusChecker.init(); isVerified = false; e.printStackTrace(); @@ -291,6 +299,46 @@ public class Global { } + // permission 的操作: 为 子节点 添加可以调用某些方法的权限,比如 storage|update|delete LRSStatistics + public static List getPermissions(String pubkey) { + String jsonStr = Global.acStorage.get(pubkey); + if (jsonStr == null || jsonStr.isEmpty()) { + return new ArrayList<>(); + } + try { + JsonArray jsonArray = JsonParser.parseString(jsonStr).getAsJsonArray(); + List permissions = new ArrayList<>(jsonArray.size()); + for (JsonElement elem : jsonArray) { + permissions.add(elem.getAsString()); + } + return permissions; + } catch (Exception e) { + LOGGER.error("Failed to parse permissions for user: {}", pubkey, e); + return new ArrayList<>(); + } + } + + // 该方法检测了原本的permission是否存在 + public static void addPermission(String user, String permission) { + List permissions = getPermissions(user); + if (!permissions.contains(permission)) { + permissions.add(permission); + flushPermissions(user, permissions); + } + } + public static void removePermission(String user, String permission) { + List permissions = getPermissions(user); + permissions.remove(permission); + flushPermissions(user, permissions); + } + public static void flushPermissions(String user, List permissions) { + Gson gson = new Gson(); + String json = gson.toJson(permissions); + Global.acStorage.put(user, json); + } + public static void deletePermissions(String user) { + Global.acStorage.delete(user); + } private static String signAndAuth(IrpRouterClientImpl client, String pubkey, String name, String signInfo, SM2Signer signer) throws IrpClientException { IrpMessage req = IrpForRouterRequest.newVerifyRouterAuthRequest(pubkey, name, signInfo); diff --git a/src/main/java/org/bdware/datanet/router/IDFilter.java b/src/main/java/org/bdware/datanet/router/IDFilter.java index d91db690e302c68d658c9370d92b42fd5a1b4746..49d4f7f89f775301b8db96f325f498f752555c9d 100644 --- a/src/main/java/org/bdware/datanet/router/IDFilter.java +++ b/src/main/java/org/bdware/datanet/router/IDFilter.java @@ -35,22 +35,22 @@ public abstract class IDFilter { case dataId: return new IDFilter() { @Override - public boolean accept(String id) { - return countSubstrings(id, "/") > 1; + public boolean accept(String type) { + return !type.startsWith("system") && !type.startsWith("prefix") ; } }; case prefixId: return new IDFilter() { @Override - public boolean accept(String id) { - return countSubstrings(id, "/") == 0; + public boolean accept(String type) { + return type.equalsIgnoreCase("prefix"); } }; case repoId: return new IDFilter() { @Override - public boolean accept(String id) { - return countSubstrings(id, "/") == 1; + public boolean accept(String type) { + return type.startsWith("system"); } }; default: diff --git a/src/main/java/org/bdware/datanet/router/InfoSendTicker.java b/src/main/java/org/bdware/datanet/router/InfoSendTicker.java new file mode 100644 index 0000000000000000000000000000000000000000..61c841700744ece8f352c77aeaa4c116c0d34177 --- /dev/null +++ b/src/main/java/org/bdware/datanet/router/InfoSendTicker.java @@ -0,0 +1,138 @@ +package org.bdware.datanet.router; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bdware.client.ContractRequest; +import org.bdware.ypkdeploy.SmartContractClientExt; +import org.rocksdb.RocksIterator; +import org.zz.gmhelper.SM2KeyPair; + +import java.nio.charset.StandardCharsets; +import java.sql.Clob; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.bdware.datanet.router.RowStore.LOGGER; + +public class InfoSendTicker { + // 单线程守护调度器 + private static final ScheduledExecutorService SCHEDULER = + Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "InfoSendTicker"); + t.setDaemon(true); + return t; + }); + private static volatile ScheduledFuture FUTURE; + + // 线程安全的全局计数器 + private static final AtomicInteger PREFIX_COUNTER = new AtomicInteger(0); + private static final AtomicInteger DATA_DOID_COUNTER = new AtomicInteger(0); + + // 每次 snapshot 的快照数据 + private final int prefixCnt; + private final int dataDoidCnt; + + private InfoSendTicker(int prefix, int dataDoid) { + this.prefixCnt = prefix; + this.dataDoidCnt = dataDoid; + } + + public int getPrefixCnt() { return prefixCnt; } + public int getDataDoidCnt() { return dataDoidCnt; } + + //================================================ + // 1. 启动时只做一次全表扫描,初始化计数器 + //================================================ + public static InfoSendTicker scanAndSnapshot() { + PREFIX_COUNTER.set(0); + DATA_DOID_COUNTER.set(0); + + try (RocksIterator iter = Global.doStorage.newIterator()) { + for (iter.seekToFirst(); iter.isValid(); iter.next()) { + String json = new String(iter.value(), StandardCharsets.UTF_8); + JsonObject jo = JsonParser.parseString(json).getAsJsonObject(); + String type = jo.get("type").getAsString(); + + if ("prefix".equals(type)) { + PREFIX_COUNTER.incrementAndGet(); + } else if (!type.startsWith("system") && !"prefix".equals(type)) { + DATA_DOID_COUNTER.incrementAndGet(); + } + } + } catch (Exception e) { + LOGGER.error("scanAndSnapshot failed", e); + } + + return new InfoSendTicker( + PREFIX_COUNTER.get(), + DATA_DOID_COUNTER.get() + ); + } + + //================================================ + // 2. 业务侧增删计数接口,不再重复 scan + //================================================ + public static void addPrefixCnt() { PREFIX_COUNTER.incrementAndGet(); } + public static void deletePrefixCnt() { PREFIX_COUNTER.decrementAndGet(); } + public static void addDataDoidCnt() { DATA_DOID_COUNTER.incrementAndGet(); } + public static void deleteDataDoidCnt(){ DATA_DOID_COUNTER.decrementAndGet(); } + + //================================================ + // 3. 定时发送:每 10 小时 snapshot 当前计数并发给上级 + //================================================ + public static synchronized void init() { + if (FUTURE != null && !FUTURE.isDone()) { + return; + } + + // 先做一次全表扫描,初始化PREFIX_COUNTER & DATA_DOID_COUNTER + scanAndSnapshot(); + LOGGER.info("Initial counts: prefix={}, dataDoid={}", + PREFIX_COUNTER.get(), DATA_DOID_COUNTER.get()); + + FUTURE = SCHEDULER.scheduleWithFixedDelay(() -> { + InfoSendTicker snap = new InfoSendTicker( + PREFIX_COUNTER.get(), + DATA_DOID_COUNTER.get() + ); + + LOGGER.info("Snapshot collected: prefix={}, dataDoid={}", + snap.getPrefixCnt(), snap.getDataDoidCnt()); + try { + sendToUpperNode(snap); + } catch (Exception e) { + LOGGER.error("Failed to send to upper node", e); + } + }, 100, 7200, TimeUnit.SECONDS); + + LOGGER.info("InfoSendTicker scheduled every 10 hours"); + } + + /** 优雅停止定时任务 */ + public static synchronized void shutdown() { + if (FUTURE != null) { + FUTURE.cancel(false); + } + SCHEDULER.shutdownNow(); + } + + private static void sendToUpperNode(InfoSendTicker snap) throws Exception { + String targetUrl = "ws://" + Global.config.getUpperIP() + ":21030" + "/SCIDE/SCExecutor"; + JsonObject body = new JsonObject(); + body.addProperty("doid", Global.config.getPrefix()); + body.addProperty("prefixcnt", snap.getPrefixCnt()); + body.addProperty("datadoidcnt", snap.getDataDoidCnt()); + + JsonObject keyPair = new JsonObject(); + keyPair.addProperty("publicKey", Global.config.getPubKey()); + keyPair.addProperty("privateKey", Global.config.getPrivKey()); + SmartContractClientExt client = new SmartContractClientExt(targetUrl, SM2KeyPair.fromJson(keyPair.toString())); + client.waitForConnect(); + + ContractRequest cr = new ContractRequest(); + cr.setArg( body); + cr.setAction("storageLRSStatistics"); + cr.setContractID("GlobalRouter"); + client.executeContractSync(cr); + } +} \ No newline at end of file diff --git a/src/main/java/org/bdware/datanet/router/ReadMe.md b/src/main/java/org/bdware/datanet/router/ReadMe.md new file mode 100644 index 0000000000000000000000000000000000000000..280ad8865add5c0fe716d4d1242b8e324bb38677 --- /dev/null +++ b/src/main/java/org/bdware/datanet/router/ReadMe.md @@ -0,0 +1,42 @@ +## 开发文档 + +### permission 分析 +RBAC.yjs 指定了通过方法调用的角色以身份为主体(节点与节点之间通信),这是为了保证子节点可以有权限调用父节点的方法。 + +RBAC模型是一种好的实践方案。但是这个服务无法在一个用户添加前后缀的时候通过合约调用(端口占用)。 + +在代码中,acTable指向的是各个公钥的权限,所以在增删改前缀的时候需要充分考虑公钥可能出现的情况。 + +* 简单来说,添加前缀时直接把用户传递的公钥添加权限。 删除前缀时如果这个公钥还只向了别的前缀时,就保留,否则删除此公钥的权限。 + +* 修改前缀时,由于前端未传递修改之前的公钥,所以需要通过oldDoid去获取oldPubkey,比对二者。 + +* 如果不同,那么就对新公钥添加权限,但是原公钥可能还指向别的公钥,所以需要判断是否可被删除权限。 + +这种设计方案有什么问题呢? +* 如果一位用户的原公钥泄露,恰巧有其他前缀包含了原公钥导致权限无法删除,那么攻击者就可使用原公钥的权限伪造行为。 + +解决方案:不使用公钥指代角色,或者使用公钥+前缀组合细化角色,隔离权限 + +![img.png](../../../../../resources/imgs/权限-主体分析.png) + + + +![img.png](../../../../../resources/imgs/权限-问题.png) + + + +![img.png](../../../../../resources/imgs/权限-攻击.png) + + + +### LRSStatistics 方法分析 +该方法一共有三个,分别是get、store、 delete +* 方法的作用是子节点向父节点发送自己的前缀数量和dataDoid数量,系统启动时执行一次,后续每两小时执行一次。 +* 子节点向父节点调用方法需要有权限,具体可参考上述的`authorizedChildren`。 +* 数量是原子操作修改的,具体参考[InfoSendTicker.java](InfoSendTicker.java),调用的位置分别在: LRS(create、delete),removeDoid、putDO +* 父节点收到请求后会将其写入自身的`statistics`表中。`key`是子节点传递的`doid`,`doid`获取自**子节点**系统启动的`prefix`参数,具体可参考`cmconfig.json`. +* 定时推送的逻辑会在判断非根节点后执行。[Global.java](Global.java) :Global.verifyAndStartServer, 一般来说isRoot表示节点是否为根节点,但节点是否是父节点应该参考配置文件中的`routerAddress`字段,表示上层的通信地址。 +* 具体推送的时候获取的`upperIP`即是父节点的ip地址,但是合约跑在21030端口,并且协议是ws,因此需要手动拼接地址。 +* 配置文件中path需要指向子节点模式的ypk文件,所以需要单独build,`routerAddress` 、 `isRoot` 都需要对应修改。 +* 如果能推送成功,**父节点必须有子节点的前缀信息**,具体是`publicKey`+`doId` \ No newline at end of file diff --git a/src/main/java/org/bdware/datanet/router/Router.java b/src/main/java/org/bdware/datanet/router/Router.java index 77f807dbfac06c86c27d278e56221b5b125ebc29..e3951a89eaf31ccc130fa930608c36c2747cb471 100644 --- a/src/main/java/org/bdware/datanet/router/Router.java +++ b/src/main/java/org/bdware/datanet/router/Router.java @@ -20,8 +20,15 @@ import wrp.jdk.nashorn.api.scripting.ScriptObjectMirror; import wrp.jdk.nashorn.internal.runtime.PropertyMap; import wrp.jdk.nashorn.internal.scripts.JO; +import javax.json.Json; import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + public class Router { @@ -104,33 +111,291 @@ public class Router { return ret; } - public static Object listLRS() { - return listValues(Global.lRSStorage); + public static Object listLRS(ScriptObjectMirror obj) { + try { + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + + int offset = jo.has("offset") ? jo.get("offset").getAsInt() : 0; + int count = jo.has("count") ? jo.get("count").getAsInt() : 10; + String sortBy = jo.has("sortBy") ? jo.get("sortBy").getAsString() : "date"; + boolean ascending = !("desc".equalsIgnoreCase(jo.has("order")? jo.get("order").getAsString(): "")); + + JsonObject ret = IrsHandlerImpl.queryLRSByOffsetByRocksDB(offset, count, sortBy, ascending); + + JsonObject expand = new JsonObject(); + for (String key : ret.keySet()) { + if ("data".equals(key)) { + JsonArray array = ret.getAsJsonArray(key); + // 按 doId/name/type 过滤 + JsonArray filtered = new JsonArray(); + if (jo.has("doId") || jo.has("name") || jo.has("type")) { + for (JsonElement je : array) { + JsonObject item = je.getAsJsonObject(); + boolean match = true; + if (jo.has("doId")) { + match &= jo.get("doId").getAsString().equals(item.get("doId").getAsString()); + } + if (jo.has("name") && item.has("name")) { + match &= jo.get("name").getAsString().equals(item.get("name").getAsString()); + } + if (jo.has("type") && item.has("type")) { + match &= jo.get("type").getAsString().equals(item.get("type").getAsString()); + } + if (match) filtered.add(item); + } + expand.add("data", filtered); + } else { + expand.add("data", array); + } + } else { + expand.add(key, ret.get(key)); + } + } + return expand; + } catch (Exception e) { + JsonObject error = new JsonObject(); + error.addProperty("status", "failed"); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + e.printStackTrace(new PrintStream(bo)); + error.addProperty("errorMessage", bo.toString()); + return error; + } } public static Object createLRS(ScriptObjectMirror obj) { - return create(obj, Global.lRSStorage); + // 不同于 createRepo 这里的type是写死的 + obj.setMember("type", "prefix"); + + updateChildPermission(obj); + + Object ret = create(obj, Global.lRSStorage); + JsonObject jo = (JsonObject) ret; + if (jo.has("result") && jo.get("result").getAsString().equals("success")){ + InfoSendTicker.addPrefixCnt(); + } + return ret; } public static Object updateLRS(ScriptObjectMirror obj) { + updateChildPermission(obj); return update(obj, Global.lRSStorage); } public static Object deleteLRS(ScriptObjectMirror obj) { - return delete(obj, Global.lRSStorage); + deleteChildPermission(obj); + JsonObject jo = (JsonObject) delete(obj, Global.lRSStorage); + if (jo.has("result") && jo.get("result").getAsString().equals("success")) { + InfoSendTicker.deletePrefixCnt(); + } + return jo; } public static Object getLRS(ScriptObjectMirror obj) { return get(obj, Global.lRSStorage); } - public static Object listRepo() { - return listValues(Global.repoStorage); + // 注:添加权限的操作是在 LRS 发生增添、修改、删除之后直接添加 authorizedChildren 权限, + // 保证LRSStatistics的方法能够正常调用(子节点调用主节点) + public static void updateChildPermission(ScriptObjectMirror obj){ + // check params + LOGGER.info("[updateChildPermission] start"); + JsonObject jo = JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + if (!jo.has("pubKey")) { + LOGGER.warn("[updateChildPermission] invalid params, missing: pubKey"); + return; + } + String pubkey = jo.get("pubKey").getAsString(); + List permissions = Arrays.asList("getLRSStatistics","storageLRSStatistics","deleteLRSStatistics"); + Global.flushPermissions(pubkey, permissions); + + // Find the old public key: we only check the oldPubKey when oldDoid is different from the current doid. + // Otherwise, retain the existing permissions associated with the old pubkey of this doid. + if (!jo.has("oldDoId") ){ + return; + } + String oldDoId = jo.get("oldDoId").getAsString(); + if (Objects.equals(oldDoId, jo.get("doId").getAsString())){ + return; + } + String old = Global.lRSStorage.get(oldDoId); + if (old == null) { + LOGGER.warn("[updateChildPermission] no existing LRS data found for doId: {}", old); + return; + } + JsonObject oldJson = JsonParser.parseString(old).getAsJsonObject(); + if (!oldJson.has("pubKey")) { + return; + } + String oldPubKey = oldJson.get("pubKey").getAsString(); + + // oldPubkey != newPubKey : check if the oldOne still points to other LRS, + // if not , delete the oldOne's permission + JsonObject lrslist = (JsonObject) listLRS(obj); + JsonArray list = lrslist.getAsJsonArray("data"); + for (JsonElement je : list) { + JsonObject item = je.getAsJsonObject(); + if (item.get("pubKey").getAsString().equals(oldPubKey)) { + LOGGER.info("[updateChildPermission] found LRS with doId: {}, skipping permission update", oldDoId); + return; + } + } + + LOGGER.debug("[updateChildPermission] no LRS data for oldPubkey: {} ,deleting", oldPubKey); + Global.deletePermissions(oldPubKey); + } + + public static void deleteChildPermission(ScriptObjectMirror obj){ + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + if (!jo.has("pubKey")) { + LOGGER.info("[deleteChildPermission] invalid params, missing: pubKey"); + return; + } + String pubKey = jo.get("pubKey").getAsString(); + // check if the pubKey still points to other LRS, + // if not, delete the permission + JsonObject lrslist = (JsonObject) listLRS(obj); + JsonArray list = lrslist.getAsJsonArray("data"); + for (JsonElement je : list) { + JsonObject item = je.getAsJsonObject(); + if (item.get("pubKey").getAsString().equals(pubKey)) { + LOGGER.info("[deleteChildPermission] found LRS with doId: {}, skipping permission update", jo.get("doId").getAsString()); + return; + } + } + Global.deletePermissions(jo.get("pubKey").getAsString()); + } + + public static Object getLRSStatistics(ScriptObjectMirror obj){ + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + JsonObject ret = new JsonObject(); + + if (!jo.has("doid")){ + ret.addProperty("result", "invalid params"); + return ret; + } + String res = Global.lrsStatisticsStorage.get(jo.get("doid").getAsString()); + LOGGER.info("[getLRSStatistics] res: {}", res); + if (null != res){ + ret.addProperty("result", "success"); + ret.add("data", JsonParser.parseString(res)); + }else{ + ret.addProperty("result", "failed"); + ret.addProperty("message", "no data"); + }; + return ret; + } + + public static Object getLRSStatisticsList(ScriptObjectMirror obj){ + return listValues(Global.lrsStatisticsStorage); + } + + public static Object storageLRSStatistics(ScriptObjectMirror obj){ + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + JsonObject ret = new JsonObject(); + if (!(jo.has("doid") && jo.has("prefixcnt") && jo.has("datadoidcnt"))){ + ret.addProperty("result", "invalid params"); + return ret; + } + String doid = jo.get("doid").getAsString(); + // 判断doid在前缀列表中是否存在. + if (Global.lRSStorage.get(doid) == null){ + ret.addProperty("result", "lrs not exists"); + return ret; + } + Global.lrsStatisticsStorage.put(doid,jo.toString() ); + ret.addProperty("result", "success"); + return ret; + } + + public static void updateLRSStatisticsFromUpdateLRS(String oldDoid, String newDoid){ + String res = Global.lrsStatisticsStorage.get(oldDoid); + if (null != res){ + LOGGER.info("[updateLRSStatisticsFromUpdateLRS Warning !!! ]: cannot find LRSStatistics, oldDoid: {}, newDoid: {}", oldDoid, newDoid); + } + Global.lrsStatisticsStorage.put(newDoid, res); + Global.lrsStatisticsStorage.delete(oldDoid); + } + + public static Object deleteLRSStatistics(ScriptObjectMirror obj) { + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + JsonObject ret = new JsonObject(); + if ("success".equals(Global.lrsStatisticsStorage.delete(jo.get("doid").getAsString()))){ + ret.addProperty("result", "success"); + }else{ + ret.addProperty("result", "failed"); + } + return ret; + } + + public static Object listRepo(ScriptObjectMirror obj) { + JsonObject ret; + try { + JsonObject jo = org.bdware.sc.engine.JSONTool.convertMirrorToJson(obj).getAsJsonObject(); + + int offset = jo.has("offset") ? jo.get("offset").getAsInt() : 0; + int count = jo.has("count") ? jo.get("count").getAsInt() : 10; + + String sortBy = jo.has("sortBy") ? jo.get("sortBy").getAsString() : "date"; + boolean ascending = !("desc".equalsIgnoreCase(jo.has("order") ? jo.get("order").getAsString():"")); + + ret = IrsHandlerImpl.queryRepositoryByOffsetByRocksDB(offset, count, sortBy,ascending); + + + JsonObject expand = new JsonObject(); + JsonArray expandArray = new JsonArray(); + + for (String key : ret.keySet()) { + if ("data".equals(key)) { + JsonArray array = ret.get(key).getAsJsonArray(); + LOGGER.info("[listRepo] repo count: {}", array.size()); + if (!jo.has("repoId") && !jo.has("name") && !jo.has("type")) { + JsonArray filtered = new JsonArray(); + for (JsonElement je : array) { + JsonObject item = je.getAsJsonObject(); + boolean match = true; + + if (jo.has("repoId")) { + match &= jo.get("repoId").getAsString() + .equals(item.get("doId").getAsString()); + } + if (jo.has("name") && item.has("name")) { + match &= jo.get("name").getAsString() + .equals(item.get("name").getAsString()); + } + if (jo.has("type") && item.has("type")) { + match &= jo.get("type").getAsString() + .equals(item.get("type").getAsString()); + } + + if (match) filtered.add(item); + } + expandArray = filtered; + }else { + expandArray = array; + } + expand.add("data", expandArray); + } else { + expand.add(key, ret.get(key)); + } + } + + return expand; + } catch (Exception e) { + JsonObject error = new JsonObject(); + error.addProperty("status", "failed"); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + e.printStackTrace(new PrintStream(bo)); + error.addProperty("errorMessage", bo.toString()); + return error; + } } public static Object createRepo(ScriptObjectMirror obj) { if (!obj.hasMember("status")) obj.setMember("status", "online"); + if (!obj.hasMember("type")){ + obj.setMember("type", "system/unknown"); + } return create(obj, Global.repoStorage); } @@ -297,10 +562,12 @@ public class Router { return createInternal(jo, table); } + // prefixId type default: prefix + // repoId type default: system/unknown public static JsonObject createInternal(JsonObject jo, RocksDBUtil table) { JsonObject ret = new JsonObject(); ret.addProperty("result", "success"); - if (jo.has("doId") && jo.has("name") && jo.has("pubKey")) { + if (jo.has("doId") && jo.has("name") && jo.has("pubKey") && jo.has("type")) { String doId = jo.get("doId").getAsString(); String name = jo.get("name").getAsString(); String pubKey = jo.get("pubKey").getAsString(); @@ -506,6 +773,27 @@ public class Router { } expandArray.add(doReuslt); } + // 在 expandArray 构造完成后执行 + if (jo.has("doid") || jo.has("type") || jo.has("repoId")) { + JsonArray filteredArray = new JsonArray(); + for (JsonElement element : expandArray) { + JsonObject item = element.getAsJsonObject(); + boolean match = true; + if (jo.has("doid")) { + match &= jo.get("doid").getAsString().equals(item.get("doId").getAsString()); + } + if (jo.has("type") && item.has("type")) { + match &= jo.get("type").getAsString().equals(item.get("type").getAsString()); + } + if (jo.has("repoId") && item.has("repoId")) { + match &= jo.get("repoId").getAsString().equals(item.get("repoId").getAsString()); + } + if (match) { + filteredArray.add(item); + } + } + expandArray = filteredArray; + } expand.add(key, expandArray); } else { expand.add(key, ret.get(key)); @@ -688,9 +976,10 @@ public class Router { try { String url = "tcp://" + routerInfoJO.get("address").getAsString() + ":" + routerInfoJO.get("port").getAsInt(); + LOGGER.info("resolve:" + url); IrpRouterClientImpl client = new IrpRouterClientImpl("clientTest", url, null); StateInfoBase stateInfo = client.resolve(doId); - LOGGER.info("resolve:" + JsonUtil.toPrettyJson(stateInfo)); + LOGGER.info("resolve:" + stateInfo.handleValues.toString()); if (stateInfo == null) { LOGGER.info("Resolve failed!"); return "{}"; diff --git a/src/main/java/org/bdware/datanet/router/StatusChecker.java b/src/main/java/org/bdware/datanet/router/StatusChecker.java index 226d19d2d35d40df41ee7d439bdc37388f232db5..d4b60bbb8de5ef92b912a1aa289738c51b17aa46 100644 --- a/src/main/java/org/bdware/datanet/router/StatusChecker.java +++ b/src/main/java/org/bdware/datanet/router/StatusChecker.java @@ -134,7 +134,6 @@ public class StatusChecker { } } // 更改了参数:第一个参数的单位是秒,状态检查机制启动太晚不利于调试 - }, 10, 30, TimeUnit.SECONDS); - + }, 100, 30, TimeUnit.SECONDS); } } diff --git a/src/main/java/org/bdware/datanet/router/irp/IrsHandlerImpl.java b/src/main/java/org/bdware/datanet/router/irp/IrsHandlerImpl.java index efb30aea93172564e20aa940b0975d8467d3d2a8..9ec02a805d62ac414c70afe41158fe5b321c84fa 100644 --- a/src/main/java/org/bdware/datanet/router/irp/IrsHandlerImpl.java +++ b/src/main/java/org/bdware/datanet/router/irp/IrsHandlerImpl.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import org.bdware.datanet.router.Config; import org.bdware.datanet.router.Global; import org.bdware.datanet.router.IDFilter; +import org.bdware.datanet.router.InfoSendTicker; import org.bdware.doip.auditrepo.AutoAuditDO; import org.bdware.irp.irplib.core.*; import org.bdware.irp.irplib.util.EncoderUtils; @@ -94,6 +95,10 @@ public class IrsHandlerImpl implements IrpHandler { } public static void putDO(String doid, JsonObject jo) { + if (!jo.has("type")){ + jo.addProperty("type", "data"); + } + InfoSendTicker.addDataDoidCnt(); String timestamp = System.currentTimeMillis() + "_" + doid.hashCode(); jo.addProperty("_updateTimestamp", timestamp); Global.doidIndex.put(timestamp, doid); @@ -104,7 +109,10 @@ public class IrsHandlerImpl implements IrpHandler { try { JsonObject jsonObject = JsonParser.parseString(Global.doStorage.get(doid)).getAsJsonObject(); - Global.doStorage.delete(doid); + String res = Global.doStorage.delete(doid); + if (res.equals("success")){ + InfoSendTicker.deleteDataDoidCnt(); + } if (jsonObject.has("_updateTimestamp")) { Global.doidIndex.delete(jsonObject.get("_updateTimestamp").getAsString()); } @@ -470,14 +478,21 @@ public class IrsHandlerImpl implements IrpHandler { int ccount = 0; for (; iter.isValid();) { String id = new String(iter.value(), StandardCharsets.UTF_8); + String joStr = Global.doStorage.get(id); + JsonObject jo = JsonParser.parseString(joStr).getAsJsonObject(); if (total >= offset && total < offset + count) { - if (filter.accept(id)) { - ccount++; - array.add(id); + if (jo.has("type")){ + if (filter.accept(jo.get("type").getAsString())){ + ccount++; + array.add(id); + } + } + } + if (jo.has("type")){ + if (filter.accept(jo.get("type").getAsString())){ + total++; } } - if (filter.accept(id)) - total++; if (ascending) iter.next(); else @@ -528,15 +543,21 @@ public class IrsHandlerImpl implements IrpHandler { int ccount = 0; for (; iter.isValid();) { String id = new String(iter.key(), StandardCharsets.UTF_8); + JsonObject jo = JsonParser.parseString(new String(iter.value(), StandardCharsets.UTF_8)).getAsJsonObject(); if (total >= offset && total < offset + count) { - if (filter.accept(id)) { - ccount++; - array.add(id); + if (jo.has("type")){ + if (filter.accept(jo.get("type").getAsString())){ + ccount++; + array.add(id); + } } } - if (filter.accept(id)) - total++; + if (jo.has("type")){ + if (filter.accept(jo.get("type").getAsString())){ + total++; + } + } if (ascending) iter.next(); else @@ -552,6 +573,80 @@ public class IrsHandlerImpl implements IrpHandler { iter.close(); } } + public static JsonObject queryLRSByOffsetByRocksDB(int offset, + int count, + String sortBy, + boolean ascending) { + JsonObject result = new JsonObject(); + int total = 0, ccount = 0; + JsonArray data = new JsonArray(); + + try (RocksIterator iter = Global.lRSStorage.newIterator()) { + iter.seekToFirst(); + while (iter.isValid()) { + if (total >= offset && total < offset + count) { + String json = new String(iter.value(), StandardCharsets.UTF_8); + JsonObject obj = JsonUtil.parseString(json).getAsJsonObject(); + data.add(obj); + ccount++; + } + total++; + iter.next(); + } + + if (sortBy != null && !sortBy.isEmpty()) { + data = JsonArraySorter.sort(data, sortBy, ascending); + } + + result.addProperty("status", "success"); + result.addProperty("total", total); + result.addProperty("count", ccount); + result.add("data", data); + + } catch (Exception e) { + result = new JsonObject(); + result.addProperty("status", "failed"); + result.addProperty("errorMessage", e.toString()); + } + return result; + } + + public static JsonObject queryRepositoryByOffsetByRocksDB(int offset,int count, String sortBy ,boolean ascending) { + JsonObject result = new JsonObject(); + int total = 0; // 走过的总记录数 + int ccount = 0; // 本页返回数量 + JsonArray data = new JsonArray(); + + try (RocksIterator iter = Global.repoStorage.newIterator()) { + iter.seekToFirst(); + while (iter.isValid()) { + if (total >= offset && total < offset + count) { + String json = new String(iter.value(), StandardCharsets.UTF_8); + JsonObject repoObj = JsonUtil.parseString(json).getAsJsonObject(); + data.add(repoObj); + ccount++; + } + total++; + + iter.next(); + } + + if ( sortBy != null && !sortBy.isEmpty()){ + data = JsonArraySorter.sort(data, sortBy,ascending); + } + + result.addProperty("status", "success"); + result.addProperty("total", total); + result.addProperty("count", ccount); + result.add("data", data); + } catch (Exception e) { + result = new JsonObject(); + result.addProperty("status", "failed"); + result.addProperty("errorMessage", e.toString()); + } + + return result; + } private int getPortInAddress(String routerInfo) { try { diff --git a/src/main/java/org/bdware/datanet/router/irp/JsonArraySorter.java b/src/main/java/org/bdware/datanet/router/irp/JsonArraySorter.java new file mode 100644 index 0000000000000000000000000000000000000000..64dbec0f10beebbdc381b48bd427aa7b53b5cc5b --- /dev/null +++ b/src/main/java/org/bdware/datanet/router/irp/JsonArraySorter.java @@ -0,0 +1,65 @@ +package org.bdware.datanet.router.irp; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.ArrayList; +import java.util.List; + +public class JsonArraySorter { + /** + * 对任意 JsonArray 按指定字段升/降序排序。 + * + * @param array 待排序的 JsonArray(内部元素必须是 JsonObject) + * @param sortBy 排序字段名 + * @param ascending true=升序, false=降序 + * @return 一个新的、已排序的 JsonArray + */ + public static JsonArray sort(JsonArray array, + String sortBy, + boolean ascending) { + List list = new ArrayList<>(); + for (JsonElement e : array) { + list.add(e.getAsJsonObject()); + } + + // 排序逻辑 + list.sort((o1, o2) -> { + JsonElement v1 = o1.get(sortBy); + JsonElement v2 = o2.get(sortBy); + + // Null 或者 字段缺失时视为相等 + if (v1 == null || v2 == null) { + return 0; + } + + int cmp; + // 数字 vs 数字 + if (v1.isJsonPrimitive() && v2.isJsonPrimitive()) { + JsonPrimitive p1 = v1.getAsJsonPrimitive(); + JsonPrimitive p2 = v2.getAsJsonPrimitive(); + + if (p1.isNumber() && p2.isNumber()) { + cmp = Long.compare(p1.getAsLong(), p2.getAsLong()); + } else { + // 字符串比较 + cmp = p1.getAsString().compareTo(p2.getAsString()); + } + } else { + // 其他类型转成字符串比 + cmp = v1.toString().compareTo(v2.toString()); + } + + return ascending ? cmp : -cmp; + }); + + // 构造新的 JsonArray 返回 + JsonArray sorted = new JsonArray(); + for (JsonObject obj : list) { + sorted.add(obj); + } + return sorted; + } +} diff --git "a/src/main/resources/imgs/\346\235\203\351\231\220-\344\270\273\344\275\223\345\210\206\346\236\220.png" "b/src/main/resources/imgs/\346\235\203\351\231\220-\344\270\273\344\275\223\345\210\206\346\236\220.png" new file mode 100644 index 0000000000000000000000000000000000000000..ea6fd801cd97fddf84721cb74621c85ad367e127 Binary files /dev/null and "b/src/main/resources/imgs/\346\235\203\351\231\220-\344\270\273\344\275\223\345\210\206\346\236\220.png" differ diff --git "a/src/main/resources/imgs/\346\235\203\351\231\220-\346\224\273\345\207\273.png" "b/src/main/resources/imgs/\346\235\203\351\231\220-\346\224\273\345\207\273.png" new file mode 100644 index 0000000000000000000000000000000000000000..b722fd92c92ec3c717a632ab070b2bbafb987bfd Binary files /dev/null and "b/src/main/resources/imgs/\346\235\203\351\231\220-\346\224\273\345\207\273.png" differ diff --git "a/src/main/resources/imgs/\346\235\203\351\231\220-\351\227\256\351\242\230.png" "b/src/main/resources/imgs/\346\235\203\351\231\220-\351\227\256\351\242\230.png" new file mode 100644 index 0000000000000000000000000000000000000000..7ec3d085ede4fed251fa51eeaf09cac4b08d2858 Binary files /dev/null and "b/src/main/resources/imgs/\346\235\203\351\231\220-\351\227\256\351\242\230.png" differ diff --git a/yjs/GlobalRouter.yjs b/yjs/GlobalRouter.yjs index a5e31aa9be9fdc333d4565d3c1e9ae16dcd84340..7b6e0dd76f9a2c97a981b70d45f7f0f4da7f5727 100644 --- a/yjs/GlobalRouter.yjs +++ b/yjs/GlobalRouter.yjs @@ -1,4 +1,4 @@ -import "actemplate/ABAC.yjs"; +import "actemplate/RBAC.yjs"; import "routerModule.yjs"; import "testfuns.yjs"; @Permission("Async") @@ -10,7 +10,7 @@ oracle GlobalRouter { function onCreate(arg) { Global.isGlobal = true; Global.rootPrefix = ""; - initABAC(requester); + initRBAC(requester,Global.Resources.loadAsString("/role.json")); AsyncUtil.postFunction(initAsync, arg); } diff --git a/yjs/Router.yjs b/yjs/Router.yjs index 8b37ce346202fdd58b5d90294591bc6131c51c86..fc0f0399693e14a20d3c66641f215497a59550bc 100644 --- a/yjs/Router.yjs +++ b/yjs/Router.yjs @@ -1,4 +1,4 @@ -import "actemplate/ABAC.yjs"; +import "actemplate/RBAC.yjs"; import "routerModule.yjs"; @Permission("Async") @Permission("RocksDB") @@ -7,7 +7,7 @@ oracle Router { function onCreate(arg) { Global.isGlobal = false; Global.rootPrefix = ""; - initABAC(requester); + initRBAC(requester,Global.Resources.loadAsString("/role.json")); AsyncUtil.postFunction(initAsync, arg); } function initAsync(arg) { diff --git a/yjs/role.json b/yjs/role.json new file mode 100644 index 0000000000000000000000000000000000000000..e6b56bdfde52e415189df6b2307f785cf36769a9 --- /dev/null +++ b/yjs/role.json @@ -0,0 +1,5 @@ +{ + "userAdmin": ["addRole","removeRole"], + "authorizedUser": ["hello"], + "authorizedChildren": ["getLRSStatistics","storageLRSStatistics","deleteLRSStatistics"] +} \ No newline at end of file diff --git a/yjs/routerModule.yjs b/yjs/routerModule.yjs index 6a21457491381f63a25d372ed6d361af31d1c464..3819dfea7fdf03856518c88668b505b180b18399 100644 --- a/yjs/routerModule.yjs +++ b/yjs/routerModule.yjs @@ -43,9 +43,14 @@ module routerModule { } return Math.abs(hash); } - + @Description("参数为 {\"offset\":\"\" ,\"count\":\"\", \"doId\":\"\", \"name\":\"\", \"type\":\"\",\"sortBy\":\"\",\"order\":\"\"}传递任意或不传") export function listLRS(arg) { - return org.bdware.datanet.router.Router.listLRS(); + if (!arg) { + arg = {} + }else{ + arg = JSON.parse(arg) + } + return org.bdware.datanet.router.Router.listLRS(arg); } @ArgSchema({}) export function createLRS(arg) { @@ -64,8 +69,14 @@ module routerModule { return org.bdware.datanet.router.Router.getLRS(arg); } + @Description("参数为 {\"offset\":\"\" ,\"count\":\"\", \"repoId\":\"\", \"name\":\"\", \"type\":\"\",\"sortBy\":\"\",\"order\":\"\"}传递任意或不传") export function listRepo(arg) { - return org.bdware.datanet.router.Router.listRepo(); + if (!arg) { + arg = {} + }else{ + arg = JSON.parse(arg) + } + return org.bdware.datanet.router.Router.listRepo(arg); } @ArgSchema({}) export function createRepo(arg) { @@ -84,6 +95,18 @@ module routerModule { return org.bdware.datanet.router.Router.getRepo(arg); } @ArgSchema({}) + export function getLRSStatistics(arg){ + return org.bdware.datanet.router.Router.getLRSStatistics(arg); + } + @ArgSchema({}) + export function deleteLRSStatistics(arg){ + return org.bdware.datanet.router.Router.deleteLRSStatistics(arg); + } + @ArgSchema({}) + export function storageLRSStatistics(arg){ + return org.bdware.datanet.router.Router.storageLRSStatistics(arg); + } + @ArgSchema({}) export function listID(arg) { return org.bdware.datanet.router.Router.listID(arg); }