diff --git a/shine-server-blockchain/src/main/java/blockchain/body/BodyHasTarget.java b/shine-server-blockchain/src/main/java/blockchain/body/BodyHasTarget.java new file mode 100644 index 0000000..2f54481 --- /dev/null +++ b/shine-server-blockchain/src/main/java/blockchain/body/BodyHasTarget.java @@ -0,0 +1,32 @@ +package blockchain.body; + +/** + * BodyToFields — дополнительный интерфейс для body, которые "ссылаются" на цель (to-поля). + * + * Идея: + * - Не все body имеют "to". + * - Но для индексации и удобства запросов в БД мы хотим единообразно доставать: + * toLogin, toBchName, toBlockGlobalNumber, toBlockHashe + * + * Важно: + * - Все методы могут возвращать null. + * - toLogin может отсутствовать в самом формате body (например, ReactionBody, TextBody reply/repost), + * но в БД мы пишем toLogin "про запас". + * Поэтому writer может: + * - взять toLogin из body (если есть), + * - либо попытаться вычислить из toBchName. + */ +public interface BodyHasTarget { + + /** login цели (nullable). */ + String toLogin(); + + /** blockchainName цели (nullable). */ + String toBchName(); + + /** globalNumber цели (nullable). */ + Integer toBlockGlobalNumber(); + + /** hash цели в HEX(64) (nullable). */ + String toBlockHashe(); +} \ No newline at end of file diff --git a/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java b/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java index e1aca1b..b0d3c9e 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java @@ -42,7 +42,7 @@ import java.util.Objects; * ЛИНИЯ: * - строго lineIndex=3 (выделяем отдельную линию под связи). */ -public final class ConnectionBody implements BodyRecord { +public final class ConnectionBody implements BodyRecord, BodyHasTarget { public static final short TYPE = 3; public static final short VER = 1; @@ -279,4 +279,16 @@ public final class ConnectionBody implements BodyRecord { } return new String(out); } + + /* ===================================================================== */ + /* ====================== BodyToFields контракт ========================= */ + /* ===================================================================== */ + + @Override public String toLogin() { return toLogin; } + + @Override public String toBchName() { return toBlockchainName; } + + @Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; } + + @Override public String toBlockHashe() { return toBlockHashHex(); } } \ No newline at end of file diff --git a/shine-server-blockchain/src/main/java/blockchain/body/ReactionBody.java b/shine-server-blockchain/src/main/java/blockchain/body/ReactionBody.java index de0160d..450737a 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/ReactionBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/ReactionBody.java @@ -31,7 +31,7 @@ import java.util.Objects; * - Здесь мы НЕ проверяем, существует ли цель реакции. * - Мы проверяем только корректность формата и целостность полей. */ -public final class ReactionBody implements BodyRecord { +public final class ReactionBody implements BodyRecord, BodyHasTarget { public static final short TYPE = 2; public static final short VER = 1; @@ -172,11 +172,11 @@ public final class ReactionBody implements BodyRecord { hash цели (hex) : %s } """.formatted( - st, - toBlockchainName, - toBlockGlobalNumber, - toBlockHashHex() - ); + st, + toBlockchainName, + toBlockGlobalNumber, + toBlockHashHex() + ); } public String toBlockHashHex() { @@ -189,4 +189,17 @@ public final class ReactionBody implements BodyRecord { } return new String(out); } + + /* ===================================================================== */ + /* ====================== BodyToFields контракт ========================= */ + /* ===================================================================== */ + + /** В самом формате ReactionBody login цели не хранится => null. */ + @Override public String toLogin() { return null; } + + @Override public String toBchName() { return toBlockchainName; } + + @Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; } + + @Override public String toBlockHashe() { return toBlockHashHex(); } } \ No newline at end of file diff --git a/shine-server-blockchain/src/main/java/blockchain/body/TextBody.java b/shine-server-blockchain/src/main/java/blockchain/body/TextBody.java index 2fd739e..c0e0aeb 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/TextBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/TextBody.java @@ -41,7 +41,7 @@ import java.util.Objects; * - для subType=NEW запрещены поля ссылки и запрещены любые “лишние байты” в хвосте * - для subType=REPLY/REPOST хвост обязан быть ровно по формату и без мусора в конце */ -public final class TextBody implements BodyRecord { +public final class TextBody implements BodyRecord, BodyHasTarget { public static final short TYPE = 1; public static final short VER = 1; @@ -219,8 +219,6 @@ public final class TextBody implements BodyRecord { @Override public short type() { return TYPE; } @Override public short version() { return VER; } - - /** ✅ ВАЖНО: теперь BodyRecord требует subType() */ @Override public short subType() { return subType; } @Override @@ -363,4 +361,26 @@ public final class TextBody implements BodyRecord { } return new String(out); } + + /* ===================================================================== */ + /* ====================== BodyToFields контракт ========================= */ + /* ===================================================================== */ + + /** В формате TextBody login цели не хранится => null. */ + @Override public String toLogin() { return null; } + + @Override + public String toBchName() { + return (subType == SUB_REPLY || subType == SUB_REPOST) ? toBlockchainName : null; + } + + @Override + public Integer toBlockGlobalNumber() { + return (subType == SUB_REPLY || subType == SUB_REPOST) ? toBlockGlobalNumber : null; + } + + @Override + public String toBlockHashe() { + return (subType == SUB_REPLY || subType == SUB_REPOST) ? toBlockHashHex() : null; + } } \ No newline at end of file diff --git a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java index 981579b..2a6ee72 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -21,6 +21,10 @@ import java.sql.Statement; * - ip_geo_cache * - blockchain_state (MVP) * - blocks (login TEXT, bchName TEXT, PK убран) + * + * ОБНОВЛЕНО: + * - blocks: добавлено поле msgSubType (сразу после msgType) + * - blocks: поля to* остаются nullable */ public class DatabaseInitializer { @@ -195,7 +199,7 @@ public class DatabaseInitializer { ON blockchain_state (updated_at_ms); """); - // 6. blocks — PK удалён полностью, to* теперь nullable + // 6. blocks — PK удалён полностью, to* теперь nullable, добавлен msgSubType st.executeUpdate(""" CREATE TABLE IF NOT EXISTS blocks ( login TEXT NOT NULL, @@ -208,6 +212,7 @@ public class DatabaseInitializer { blockLinePreHashe TEXT NOT NULL, msgType INTEGER NOT NULL, + msgSubType INTEGER NOT NULL, blockByte BLOB, diff --git a/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java b/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java index 4d54b55..b9a7c7f 100644 --- a/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java @@ -45,12 +45,13 @@ public final class BlocksDAO { blockLineNumber, blockLinePreHashe, msgType, + msgSubType, blockByte, to_login, toBchName, toBlockGlobalNumber, toBlockHashe - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -104,6 +105,7 @@ public final class BlocksDAO { blockLineNumber, blockLinePreHashe, msgType, + msgSubType, blockByte, to_login, toBchName, @@ -157,6 +159,7 @@ public final class BlocksDAO { blockGlobalPreHashe = ?, blockLinePreHashe = ?, msgType = ?, + msgSubType = ?, blockByte = ?, to_login = ?, toBchName = ?, @@ -176,6 +179,7 @@ public final class BlocksDAO { ps.setString(i++, nn(e.getBlockGlobalPreHashe())); ps.setString(i++, nn(e.getBlockLinePreHashe())); ps.setInt(i++, e.getMsgType()); + ps.setInt(i++, e.getMsgSubType()); byte[] bytes = e.getBlockByte(); if (bytes != null) ps.setBytes(i++, bytes); @@ -270,6 +274,7 @@ public final class BlocksDAO { ps.setString(i++, nn(e.getBlockLinePreHashe())); ps.setInt(i++, e.getMsgType()); + ps.setInt(i++, e.getMsgSubType()); byte[] bytes = e.getBlockByte(); if (bytes != null) ps.setBytes(i++, bytes); @@ -301,6 +306,7 @@ public final class BlocksDAO { e.setBlockLinePreHashe(rs.getString("blockLinePreHashe")); e.setMsgType(rs.getInt("msgType")); + e.setMsgSubType(rs.getInt("msgSubType")); e.setBlockByte(rs.getBytes("blockByte")); diff --git a/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java b/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java index 5fc27c2..e727a5f 100644 --- a/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java @@ -11,6 +11,9 @@ package shine.db.entities; * - toBlockGlobalNumber INTEGER nullable * - toBlockHashe TEXT nullable * + * ДОБАВЛЕНО: + * - msgSubType INTEGER (uint16 по смыслу, храним как int) + * * PRIMARY KEY пока убран вообще. */ public class BlockEntry { @@ -26,6 +29,7 @@ public class BlockEntry { private String blockLinePreHashe; // TEXT private int msgType; // int16 (храним как int) + private int msgSubType; // int16 (храним как int) private byte[] blockByte; // BLOB @@ -44,6 +48,7 @@ public class BlockEntry { int blockLineNumber, String blockLinePreHashe, int msgType, + int msgSubType, byte[] blockByte, String toLogin, String toBchName, @@ -57,6 +62,7 @@ public class BlockEntry { this.blockLineNumber = blockLineNumber; this.blockLinePreHashe = blockLinePreHashe; this.msgType = msgType; + this.msgSubType = msgSubType; this.blockByte = blockByte; this.toLogin = toLogin; this.toBchName = toBchName; @@ -88,6 +94,9 @@ public class BlockEntry { public int getMsgType() { return msgType; } public void setMsgType(int msgType) { this.msgType = msgType; } + public int getMsgSubType() { return msgSubType; } + public void setMsgSubType(int msgSubType) { this.msgSubType = msgSubType; } + public byte[] getBlockByte() { return blockByte; } public void setBlockByte(byte[] blockByte) { this.blockByte = blockByte; } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler_utils/BlockchainWriter.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler_utils/BlockchainWriter.java index 74110ec..2385eff 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler_utils/BlockchainWriter.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler_utils/BlockchainWriter.java @@ -1,7 +1,7 @@ package server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils; import blockchain.BchBlockEntry; -import blockchain.body.ReactionBody; +import blockchain.body.BodyHasTarget; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import shine.db.SqliteDbController; @@ -281,7 +281,12 @@ public final class BlockchainWriter { * Важно: * - blockLinePreHashe = prevLineHashHex (а НЕ prevGlobalHashHex) * - msgType = body.type() - * - Для ReactionBody заполняем toBchName/toBlockGlobalNumber/toBlockHashe (+ to_login если можем). + * - msgSubType = body.subType() + * - to* поля берём через BodyToFields (если body его поддерживает) + * + * Про toLogin: + * - если body сам даёт toLogin — пишем его + * - иначе, если есть toBchName — пробуем вычислить login из имени блокчейна (про запас) */ private void insertBlockRow( Connection c, @@ -311,6 +316,7 @@ public final class BlockchainWriter { e.setBlockLinePreHashe(linePre); e.setMsgType(block.body.type()); + e.setMsgSubType(block.body.subType()); e.setBlockByte(block.toBytes()); @@ -320,16 +326,20 @@ public final class BlockchainWriter { e.setToBlockGlobalNumber(null); e.setToBlockHashe(null); - // ReactionBody -> target fields - if (block.body instanceof ReactionBody rb) { - e.setToBchName(rb.toBlockchainName); - e.setToBlockGlobalNumber(rb.toBlockGlobalNumber); - e.setToBlockHashe(rb.toBlockHashHex()); + // ✅ Универсально: если body поддерживает to-поля — пишем их + if (block.body instanceof BodyHasTarget tf) { + + e.setToLogin(tf.toLogin()); + e.setToBchName(tf.toBchName()); + e.setToBlockGlobalNumber(tf.toBlockGlobalNumber()); + e.setToBlockHashe(tf.toBlockHashe()); // optional: try compute to_login from target chain name (для индекса idx_blocks_to_target) - String toLogin = BlockchainNameUtil.loginFromBlockchainName(rb.toBlockchainName); - if (toLogin != null && !toLogin.isBlank()) { - e.setToLogin(toLogin); + if (e.getToLogin() == null && e.getToBchName() != null) { + String toLogin = BlockchainNameUtil.loginFromBlockchainName(e.getToBchName()); + if (toLogin != null && !toLogin.isBlank()) { + e.setToLogin(toLogin); + } } }