From 91d4477ed99469745f5cab50778183390003eac8 Mon Sep 17 00:00:00 2001 From: Redkale <8730487+redkale@users.noreply.github.com> Date: Fri, 11 Jan 2019 16:41:14 +0800 Subject: [PATCH] =?UTF-8?q?DataSource=E5=A2=9E=E5=8A=A0=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=8A=A0=E8=A7=A3=E5=AF=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=B8=BB?= =?UTF-8?q?=E7=B1=BB:CryptColumn/CryptHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/org/redkale/source/CryptColumn.java | 29 +++++++ src/org/redkale/source/CryptHandler.java | 38 ++++++++++ src/org/redkale/source/DataJdbcSource.java | 12 +-- src/org/redkale/source/DataSqlSource.java | 27 +++---- src/org/redkale/source/EntityInfo.java | 88 +++++++++++++++++++--- src/org/redkale/source/FilterNode.java | 2 +- 6 files changed, 165 insertions(+), 31 deletions(-) create mode 100644 src/org/redkale/source/CryptColumn.java create mode 100644 src/org/redkale/source/CryptHandler.java diff --git a/src/org/redkale/source/CryptColumn.java b/src/org/redkale/source/CryptColumn.java new file mode 100644 index 000000000..f9b2294ad --- /dev/null +++ b/src/org/redkale/source/CryptColumn.java @@ -0,0 +1,29 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.source; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.*; + +/** + * 加密字段标记
+ * 注意: 加密字段不能用于 LIKE 等过滤查询 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.0.0 + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface CryptColumn { + + Class handler(); +} diff --git a/src/org/redkale/source/CryptHandler.java b/src/org/redkale/source/CryptHandler.java new file mode 100644 index 000000000..cf08dc0fd --- /dev/null +++ b/src/org/redkale/source/CryptHandler.java @@ -0,0 +1,38 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.source; + +/** + * 字段加密解密接口 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.0.0 + * @param 加密的字段类型 + * @param 加密后的数据类型 + */ +public interface CryptHandler { + + /** + * 加密 + * + * @param value 加密前的字段值 + * + * @return 加密后的字段值 + */ + public D encrypt(S value); + + /** + * 解密 + * + * @param value 加密的字段值 + * + * @return 解密后的字段值 + */ + public S decrypt(D value); +} diff --git a/src/org/redkale/source/DataJdbcSource.java b/src/org/redkale/source/DataJdbcSource.java index 73edcb297..7325b6ff9 100644 --- a/src/org/redkale/source/DataJdbcSource.java +++ b/src/org/redkale/source/DataJdbcSource.java @@ -137,7 +137,7 @@ public class DataJdbcSource extends DataSqlSource { int i = 0; for (char ch : sqlchars) { if (ch == '?') { - Object obj = attrs[i++].get(value); + Object obj = info.getSQLValue(attrs[i++], value); if (obj != null && obj.getClass().isArray()) { sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'"); } else { @@ -174,10 +174,10 @@ public class DataJdbcSource extends DataSqlSource { return prestmt; } - protected int batchStatementParameters(Connection conn, PreparedStatement prestmt, EntityInfo info, Attribute[] attrs, T value) throws SQLException { + protected int batchStatementParameters(Connection conn, PreparedStatement prestmt, EntityInfo info, Attribute[] attrs, T entity) throws SQLException { int i = 0; for (Attribute attr : attrs) { - Serializable val = attr.get(value); + Serializable val = info.getSQLValue(attr, entity); if (val instanceof byte[]) { Blob blob = conn.createBlob(); blob.setBytes(1, (byte[]) val); @@ -186,7 +186,7 @@ public class DataJdbcSource extends DataSqlSource { prestmt.setObject(++i, ((AtomicInteger) val).get()); } else if (val instanceof AtomicLong) { prestmt.setObject(++i, ((AtomicLong) val).get()); - } else if (val != null && !(val instanceof Number) && !(val instanceof CharSequence) && !(value instanceof java.util.Date) + } else if (val != null && !(val instanceof Number) && !(val instanceof CharSequence) && !(entity instanceof java.util.Date) && !val.getClass().getName().startsWith("java.sql.") && !val.getClass().getName().startsWith("java.time.")) { prestmt.setObject(++i, info.jsonConvert.convertTo(attr.genericType(), val)); } else { @@ -281,7 +281,7 @@ public class DataJdbcSource extends DataSqlSource { StringBuilder sb = new StringBuilder(128); for (char ch : sqlchars) { if (ch == '?') { - Object obj = i == attrs.length ? primary.get(value) : attrs[i++].get(value); + Object obj = i == attrs.length ? info.getSQLValue(primary, value) : info.getSQLValue(attrs[i++], value); if (obj != null && obj.getClass().isArray()) { sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'"); } else { @@ -292,7 +292,7 @@ public class DataJdbcSource extends DataSqlSource { } } String debugsql = sb.toString(); - if (info.isLoggable(logger, Level.FINEST, debugsql)) logger.finest(info.getType().getSimpleName() + " update sql=" + debugsql.replaceAll("(\r|\n)", "\\n")); + if (info.isLoggable(logger, Level.FINEST, debugsql)) logger.finest(info.getType().getSimpleName() + " updates sql=" + debugsql.replaceAll("(\r|\n)", "\\n")); } //打印结束 } int[] pc = prestmt.executeBatch(); diff --git a/src/org/redkale/source/DataSqlSource.java b/src/org/redkale/source/DataSqlSource.java index 2507d0f80..3fe6a4e15 100644 --- a/src/org/redkale/source/DataSqlSource.java +++ b/src/org/redkale/source/DataSqlSource.java @@ -483,13 +483,13 @@ public abstract class DataSqlSource extends AbstractService implement protected CompletableFuture deleteCompose(final EntityInfo info, final Serializable... ids) { if (ids.length == 1) { - String sql = "DELETE FROM " + info.getTable(ids[0]) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(ids[0]); + String sql = "DELETE FROM " + info.getTable(ids[0]) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), ids[0])); return deleteDB(info, null, sql); } String sql = "DELETE FROM " + info.getTable(ids[0]) + " WHERE " + info.getPrimarySQLColumn() + " IN ("; for (int i = 0; i < ids.length; i++) { if (i > 0) sql += ','; - sql += FilterNode.formatToString(ids[i]); + sql += FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), ids[i])); } sql += ")"; if (info.isLoggable(logger, Level.FINEST, sql)) logger.finest(info.getType().getSimpleName() + " delete sql=" + sql); @@ -772,11 +772,11 @@ public abstract class DataSqlSource extends AbstractService implement protected CompletableFuture updateColumnCompose(final EntityInfo info, Serializable id, String column, final Serializable value) { if (value instanceof byte[]) { - String sql = "UPDATE " + info.getTable(id) + " SET " + info.getSQLColumn(null, column) + " = " + prepareParamSign(1) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(id); + String sql = "UPDATE " + info.getTable(id) + " SET " + info.getSQLColumn(null, column) + " = " + prepareParamSign(1) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), id)); return updateDB(info, null, sql, true, value); } else { String sql = "UPDATE " + info.getTable(id) + " SET " + info.getSQLColumn(null, column) + " = " - + info.formatToString(value) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(id); + + info.formatToString(info.getSQLValue(column, value)) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), id)); return updateDB(info, null, sql, false); } } @@ -917,11 +917,11 @@ public abstract class DataSqlSource extends AbstractService implement blobs.add((byte[]) col.getValue()); setsql.append(c).append(" = ").append(prepareParamSign(++index)); } else { - setsql.append(c).append(" = ").append(info.formatSQLValue(c, col)); + setsql.append(c).append(" = ").append(info.formatSQLValue(c, attr, col)); } } if (setsql.length() < 1) return CompletableFuture.completedFuture(0); - String sql = "UPDATE " + info.getTable(id) + " SET " + setsql + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(id); + String sql = "UPDATE " + info.getTable(id) + " SET " + setsql + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), id)); if (blobs == null) return updateDB(info, null, sql, false); return updateDB(info, null, sql, true, blobs.toArray()); } @@ -999,7 +999,7 @@ public abstract class DataSqlSource extends AbstractService implement blobs.add((byte[]) col.getValue()); setsql.append(c).append(" = ").append(prepareParamSign(++index)); } else { - setsql.append(c).append(" = ").append(info.formatSQLValue(c, col)); + setsql.append(c).append(" = ").append(info.formatSQLValue(c, attr, col)); } } if (setsql.length() < 1) return CompletableFuture.completedFuture(0); @@ -1128,7 +1128,7 @@ public abstract class DataSqlSource extends AbstractService implement if (!selects.test(attr.field())) continue; if (setsql.length() > 0) setsql.append(", "); setsql.append(info.getSQLColumn(alias, attr.field())); - Serializable val = attr.get(bean); + Serializable val = info.getFieldValue(attr, bean); if (val instanceof byte[]) { if (blobs == null) blobs = new ArrayList<>(); blobs.add((byte[]) val); @@ -1154,8 +1154,8 @@ public abstract class DataSqlSource extends AbstractService implement if (blobs == null) return updateDB(info, null, sql, false); return updateDB(info, null, sql, true, blobs.toArray()); } else { - final Serializable id = info.getPrimary().get(bean); - String sql = "UPDATE " + info.getTable(id) + " a SET " + setsql + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(id); + final Serializable id = info.getSQLValue(info.getPrimary(), bean); + String sql = "UPDATE " + info.getTable(id) + " a SET " + setsql + " WHERE " + info.getPrimarySQLColumn() + " = " + info.getSQLValue(info.getPrimarySQLColumn(), id); if (blobs == null) return updateDB(info, null, sql, false); return updateDB(info, null, sql, true, blobs.toArray()); } @@ -1533,7 +1533,8 @@ public abstract class DataSqlSource extends AbstractService implement } protected CompletableFuture findCompose(final EntityInfo info, final SelectColumn selects, Serializable pk) { - final String sql = "SELECT " + info.getQueryColumns(null, selects) + " FROM " + info.getTable(pk) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(pk); + String column = info.getPrimarySQLColumn(); + final String sql = "SELECT " + info.getQueryColumns(null, selects) + " FROM " + info.getTable(pk) + " WHERE " + column + " = " + FilterNode.formatToString(info.getSQLValue(column, pk)); if (info.isLoggable(logger, Level.FINEST, sql)) logger.finest(info.getType().getSimpleName() + " find sql=" + sql); return findDB(info, sql, true, selects); } @@ -1670,7 +1671,7 @@ public abstract class DataSqlSource extends AbstractService implement } protected CompletableFuture findColumnCompose(final EntityInfo info, String column, final Serializable defValue, final Serializable pk) { - final String sql = "SELECT " + info.getSQLColumn(null, column) + " FROM " + info.getTable(pk) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(pk); + final String sql = "SELECT " + info.getSQLColumn(null, column) + " FROM " + info.getTable(pk) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), pk)); if (info.isLoggable(logger, Level.FINEST, sql)) logger.finest(info.getType().getSimpleName() + " find sql=" + sql); return findColumnDB(info, sql, true, column, defValue); } @@ -1732,7 +1733,7 @@ public abstract class DataSqlSource extends AbstractService implement } protected CompletableFuture existsCompose(final EntityInfo info, Serializable pk) { - final String sql = "SELECT COUNT(*) FROM " + info.getTable(pk) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(pk); + final String sql = "SELECT COUNT(*) FROM " + info.getTable(pk) + " WHERE " + info.getPrimarySQLColumn() + " = " + FilterNode.formatToString(info.getSQLValue(info.getPrimarySQLColumn(), pk)); if (info.isLoggable(logger, Level.FINEST, sql)) logger.finest(info.getType().getSimpleName() + " exists sql=" + sql); return existsDB(info, sql, true); } diff --git a/src/org/redkale/source/EntityInfo.java b/src/org/redkale/source/EntityInfo.java index c7111539d..d85ce5986 100644 --- a/src/org/redkale/source/EntityInfo.java +++ b/src/org/redkale/source/EntityInfo.java @@ -75,6 +75,10 @@ public final class EntityInfo { //只有field.name 与 Column.name不同才存放在aliasmap里. private final Map aliasmap; + //key是field的name, value是CryptHandler + //字段都不存在CryptHandler时值因为为null,减少判断 + private final Map cryptmap; + //所有可更新字段,即排除了主键字段和标记为@Column(updatable=false)的字段 private final Map> updateAttributeMap = new HashMap<>(); @@ -274,6 +278,7 @@ public final class EntityInfo { } this.constructorParameters = (cp == null || cp.value().length < 1) ? null : cp.value(); Attribute idAttr0 = null; + Map cryptmap0 = null; Map aliasmap0 = null; Class cltmp = type; Set fields = new HashSet<>(); @@ -284,7 +289,7 @@ public final class EntityInfo { List> updateattrs = new ArrayList<>(); boolean auto = false; boolean uuid = false; - + Map> cryptCreatorMap = new HashMap<>(); do { for (Field field : cltmp.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) continue; @@ -298,9 +303,16 @@ public final class EntityInfo { if (aliasmap0 == null) aliasmap0 = new HashMap<>(); aliasmap0.put(fieldname, sqlfield); } + final CryptColumn cpt = field.getAnnotation(CryptColumn.class); + CryptHandler cryptHandler = null; + if (cpt != null) { + if (cryptmap0 == null) cryptmap0 = new HashMap<>(); + cryptHandler = cryptCreatorMap.computeIfAbsent(cpt.handler(), c -> (Creator) Creator.create(cpt.handler())).create(); + cryptmap0.put(fieldname, cryptHandler); + } Attribute attr; try { - attr = Attribute.create(cltmp, field); + attr = Attribute.create(cltmp, field, cryptHandler); } catch (RuntimeException e) { continue; } @@ -357,6 +369,7 @@ public final class EntityInfo { this.primary = idAttr0; this.aliasmap = aliasmap0; + this.cryptmap = cryptmap0; this.attributes = attributeMap.values().toArray(new Attribute[attributeMap.size()]); this.queryAttributes = queryattrs.toArray(new Attribute[queryattrs.size()]); this.insertAttributes = insertattrs.toArray(new Attribute[insertattrs.size()]); @@ -857,6 +870,51 @@ public final class EntityInfo { : (tabalis == null ? aliasmap.getOrDefault(fieldname, fieldname) : (tabalis + '.' + aliasmap.getOrDefault(fieldname, fieldname))); } + /** + * 字段值转换成数据库的值 + * + * @param fieldname 字段名 + * @param fieldvalue 字段值 + * + * @return Object + */ + public Object getSQLValue(String fieldname, Object fieldvalue) { + if (this.cryptmap == null) return fieldvalue; + CryptHandler handler = this.cryptmap.get(fieldname); + if (handler == null) return fieldvalue; + return handler.encrypt(fieldvalue); + } + + /** + * 字段值转换成数据库的值 + * + * @param attr Attribute + * @param entity 记录对象 + * + * @return Object + */ + public Serializable getSQLValue(Attribute attr, T entity) { + Serializable val = attr.get(entity); + CryptHandler cryptHandler = attr.attach(); + if (cryptHandler != null) val = (Serializable) cryptHandler.encrypt(val); + return val; + } + + /** + * 数据库的值转换成数字段值 + * + * @param attr Attribute + * @param entity 记录对象 + * + * @return Object + */ + public Serializable getFieldValue(Attribute attr, T entity) { + Serializable val = attr.get(entity); + CryptHandler cryptHandler = attr.attach(); + if (cryptHandler != null) val = (Serializable) cryptHandler.decrypt(val); + return val; + } + /** * 获取主键字段的表字段名 * @@ -880,26 +938,30 @@ public final class EntityInfo { /** * 拼接UPDATE给字段赋值的SQL片段 * - * @param col 表字段名 - * @param cv ColumnValue + * @param col 表字段名 + * @param attr Attribute + * @param cv ColumnValue * * @return CharSequence */ - protected CharSequence formatSQLValue(String col, final ColumnValue cv) { + protected CharSequence formatSQLValue(String col, Attribute attr, final ColumnValue cv) { if (cv == null) return null; + Object val = cv.getValue(); + CryptHandler handler = attr.attach(); + if (handler != null) val = handler.encrypt(val); switch (cv.getExpress()) { case INC: - return new StringBuilder().append(col).append(" + ").append(cv.getValue()); + return new StringBuilder().append(col).append(" + ").append(val); case MUL: - return new StringBuilder().append(col).append(" * ").append(cv.getValue()); + return new StringBuilder().append(col).append(" * ").append(val); case AND: - return new StringBuilder().append(col).append(" & ").append(cv.getValue()); + return new StringBuilder().append(col).append(" & ").append(val); case ORR: - return new StringBuilder().append(col).append(" | ").append(cv.getValue()); + return new StringBuilder().append(col).append(" | ").append(val); case MOV: - return formatToString(cv.getValue()); + return formatToString(val); } - return formatToString(cv.getValue()); + return formatToString(val); } /** @@ -1003,9 +1065,13 @@ public final class EntityInfo { o = null; } else { //不支持超过2G的数据 o = blob.getBytes(1, (int) blob.length()); + CryptHandler cryptHandler = attr.attach(); + if (cryptHandler != null) o = (Serializable) cryptHandler.decrypt(o); } } else { o = (Serializable) set.getObject(this.getSQLColumn(null, attr.field())); + CryptHandler cryptHandler = attr.attach(); + if (cryptHandler != null) o = (Serializable) cryptHandler.decrypt(o); if (t.isPrimitive()) { if (o != null) { if (t == int.class) { diff --git a/src/org/redkale/source/FilterNode.java b/src/org/redkale/source/FilterNode.java index 33c231c7a..6dfd961ed 100644 --- a/src/org/redkale/source/FilterNode.java +++ b/src/org/redkale/source/FilterNode.java @@ -389,7 +389,7 @@ public class FilterNode { //FilterNode 不能实现Serializable接口, 否则 .append(' ').append(fv.getExpress().value()).append(' ').append(fv.getDestvalue()); } final boolean fk = (val0 instanceof FilterKey); - CharSequence val = fk ? info.getSQLColumn(talis, ((FilterKey) val0).getColumn()) : formatToString(express, val0); + CharSequence val = fk ? info.getSQLColumn(talis, ((FilterKey) val0).getColumn()) : formatToString(express, info.getSQLValue(column, val0)); if (val == null) return null; StringBuilder sb = new StringBuilder(32); if (express == CONTAIN) return info.containSQL.replace("${column}", info.getSQLColumn(talis, column)).replace("${keystr}", val);