初始化
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package tech.easyflow.datacenter.adapter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.common.util.SpringContextUtil;
|
||||
|
||||
@Component
|
||||
public class DbHandleManager {
|
||||
|
||||
@Value("${easyflow.datacenter.handler:defaultDbHandleService}")
|
||||
private String dbHandler;
|
||||
|
||||
public DbHandleService getDbHandler() {
|
||||
return SpringContextUtil.getBean(dbHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package tech.easyflow.datacenter.adapter;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import tech.easyflow.common.constant.enums.EnumFieldType;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public abstract class DbHandleService {
|
||||
|
||||
public abstract void createTable(DatacenterTable table);
|
||||
|
||||
public abstract void updateTable(DatacenterTable table, DatacenterTable record);
|
||||
|
||||
public abstract void deleteTable(DatacenterTable table);
|
||||
|
||||
/**
|
||||
* @see tech.easyflow.common.constant.enums.EnumFieldType
|
||||
*/
|
||||
public abstract String convertFieldType(Integer fieldType);
|
||||
|
||||
public abstract void addField(DatacenterTable entity, DatacenterTableField field);
|
||||
|
||||
public abstract void deleteField(DatacenterTable entity, DatacenterTableField field);
|
||||
|
||||
public abstract void updateField(DatacenterTable entity, DatacenterTableField fieldRecord, DatacenterTableField field);
|
||||
|
||||
public abstract void saveValue(DatacenterTable entity, JSONObject object, LoginAccount account);
|
||||
|
||||
public abstract void updateValue(DatacenterTable entity, JSONObject object, LoginAccount account);
|
||||
|
||||
public abstract void removeValue(DatacenterTable entity, BigInteger id, LoginAccount account);
|
||||
public Object convertFieldValue(Integer fieldType, String fieldValue) {
|
||||
if (fieldType.equals(EnumFieldType.INTEGER.getCode()) || fieldType.equals(EnumFieldType.BOOLEAN.getCode())) {
|
||||
return Integer.parseInt(fieldValue);
|
||||
}
|
||||
if (fieldType.equals(EnumFieldType.TIME.getCode())) {
|
||||
return DateUtil.parse(fieldValue);
|
||||
}
|
||||
if (fieldType.equals(EnumFieldType.NUMBER.getCode())) {
|
||||
return new BigDecimal(fieldValue);
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package tech.easyflow.datacenter.adapter;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.mybatisflex.core.row.Db;
|
||||
import com.mybatisflex.core.row.Row;
|
||||
import com.mybatisflex.core.row.RowKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.common.constant.enums.EnumFieldType;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.utils.SqlInjectionUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component("defaultDbHandleService")
|
||||
public class DefaultDbHandleService extends DbHandleService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DefaultDbHandleService.class);
|
||||
|
||||
@Override
|
||||
public void createTable(DatacenterTable table) {
|
||||
// 设置为 [tb_dynamic_表名_tableId] 的格式
|
||||
String actualTable = table.getActualTable();
|
||||
SqlInjectionUtils.checkIdentifier(actualTable);
|
||||
// 表注释
|
||||
String tableDesc = table.getTableDesc();
|
||||
SqlInjectionUtils.checkComment(tableDesc);
|
||||
|
||||
List<DatacenterTableField> fields = table.getFields();
|
||||
StringBuilder sql = new StringBuilder("CREATE TABLE " + actualTable + " (");
|
||||
sql.append("`id` bigint unsigned NOT NULL COMMENT '主键',");
|
||||
sql.append("`dept_id` bigint unsigned NOT NULL COMMENT '部门ID',");
|
||||
sql.append("`tenant_id` bigint unsigned NOT NULL COMMENT '租户ID',");
|
||||
sql.append("`created` datetime NOT NULL COMMENT '创建时间',");
|
||||
sql.append("`created_by` bigint unsigned NOT NULL COMMENT '创建者',");
|
||||
sql.append("`modified` datetime NOT NULL COMMENT '修改时间',");
|
||||
sql.append("`modified_by` bigint unsigned NOT NULL COMMENT '修改者',");
|
||||
sql.append("`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '备注',");
|
||||
for (DatacenterTableField field : fields) {
|
||||
Integer required = field.getRequired();
|
||||
String fieldName = SqlInjectionUtils.checkIdentifier(field.getFieldName());
|
||||
String fieldDesc = SqlInjectionUtils.checkComment(field.getFieldDesc());
|
||||
sql.append("`").append(fieldName).append("` ")
|
||||
.append(convertFieldType(field.getFieldType())).append(" ")
|
||||
.append(required == 1 ? "NOT NULL" : "NULL").append(" ")
|
||||
.append("COMMENT '").append(fieldDesc).append("',");
|
||||
}
|
||||
sql.append("PRIMARY KEY (id) USING BTREE");
|
||||
sql.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='").append(tableDesc).append("';");
|
||||
log.info("建表语句 >>> {}", sql);
|
||||
Db.selectObject(sql.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTable(DatacenterTable table, DatacenterTable record) {
|
||||
String tableDesc = table.getTableDesc();
|
||||
SqlInjectionUtils.checkComment(tableDesc);
|
||||
String actualTable = record.getActualTable();
|
||||
// 只允许改表备注
|
||||
if (!tableDesc.equals(record.getTableDesc())) {
|
||||
String sql = "ALTER TABLE `" + actualTable + "` "
|
||||
+ "COMMENT '" + tableDesc + "';";
|
||||
log.info("修改表备注语句 >>> {}", sql);
|
||||
Db.selectObject(sql);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTable(DatacenterTable table) {
|
||||
String actualTable = table.getActualTable();
|
||||
String sql = "DROP TABLE IF EXISTS `" + actualTable + "`;";
|
||||
log.info("删除表语句 >>> {}", sql);
|
||||
Db.selectObject(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertFieldType(Integer fieldType) {
|
||||
if (EnumFieldType.INTEGER.getCode().equals(fieldType)) {
|
||||
return "int";
|
||||
}
|
||||
if (EnumFieldType.BOOLEAN.getCode().equals(fieldType)) {
|
||||
return "int";
|
||||
}
|
||||
if (EnumFieldType.TIME.getCode().equals(fieldType)) {
|
||||
return "datetime";
|
||||
}
|
||||
if (EnumFieldType.NUMBER.getCode().equals(fieldType)) {
|
||||
return "decimal(20,6)";
|
||||
}
|
||||
return "text";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addField(DatacenterTable entity, DatacenterTableField field) {
|
||||
String fieldName = field.getFieldName();
|
||||
SqlInjectionUtils.checkIdentifier(fieldName);
|
||||
|
||||
String fieldDesc = field.getFieldDesc();
|
||||
SqlInjectionUtils.checkComment(fieldDesc);
|
||||
|
||||
Integer fieldType = field.getFieldType();
|
||||
Integer required = field.getRequired();
|
||||
String sql = "ALTER TABLE `" + entity.getActualTable() + "`"
|
||||
+ " ADD COLUMN `" + fieldName + "` " + convertFieldType(fieldType) + " "
|
||||
+ (required == 1 ? "NOT NULL" : "NULL") + " "
|
||||
+ "COMMENT '" + fieldDesc + "';";
|
||||
log.info("添加字段语句 >>> {}", sql);
|
||||
Db.selectObject(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteField(DatacenterTable entity, DatacenterTableField field) {
|
||||
|
||||
String fieldName = field.getFieldName();
|
||||
SqlInjectionUtils.checkIdentifier(fieldName);
|
||||
|
||||
String sql = "ALTER TABLE `" + entity.getActualTable() + "`"
|
||||
+ " DROP COLUMN `" + fieldName + "`;";
|
||||
log.info("删除字段语句 >>> {}", sql);
|
||||
Db.selectObject(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateField(DatacenterTable entity, DatacenterTableField fieldRecord, DatacenterTableField field) {
|
||||
String actualTable = entity.getActualTable();
|
||||
// 是否必填
|
||||
Integer required = field.getRequired();
|
||||
// 字段名称
|
||||
String fieldName = field.getFieldName();
|
||||
SqlInjectionUtils.checkIdentifier(fieldName);
|
||||
// 字段描述
|
||||
String fieldDesc = field.getFieldDesc();
|
||||
SqlInjectionUtils.checkComment(fieldDesc);
|
||||
|
||||
String nullable = required == 1 ? "NOT NULL " : "NULL ";
|
||||
String desc = "COMMENT '" + fieldDesc + "';";
|
||||
|
||||
boolean isUpdate = false;
|
||||
String handleType = "MODIFY COLUMN `" + fieldRecord.getFieldName() + "` ";
|
||||
|
||||
if (!required.equals(fieldRecord.getRequired())) {
|
||||
isUpdate = true;
|
||||
}
|
||||
|
||||
if (!fieldDesc.equals(fieldRecord.getFieldDesc())) {
|
||||
isUpdate = true;
|
||||
}
|
||||
|
||||
if (!fieldName.equals(fieldRecord.getFieldName())) {
|
||||
isUpdate = true;
|
||||
handleType = "CHANGE COLUMN `" + fieldRecord.getFieldName() + "` `" + fieldName + "` ";
|
||||
}
|
||||
|
||||
if (isUpdate) {
|
||||
String sql = "ALTER TABLE `" + actualTable + "` "
|
||||
+ handleType
|
||||
+ convertFieldType(field.getFieldType()) + " "
|
||||
+ nullable
|
||||
+ desc;
|
||||
log.info("更新字段语句 >>> {}", sql);
|
||||
Db.selectObject(sql);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveValue(DatacenterTable entity, JSONObject object, LoginAccount account) {
|
||||
String actualTable = entity.getActualTable();
|
||||
List<DatacenterTableField> fields = entity.getFields();
|
||||
|
||||
Row row = Row.ofKey(RowKey.SNOW_FLAKE_ID);
|
||||
row.put("dept_id", account.getDeptId());
|
||||
row.put("tenant_id", account.getTenantId());
|
||||
row.put("created", new Date());
|
||||
row.put("created_by", account.getId());
|
||||
row.put("modified", new Date());
|
||||
row.put("modified_by", account.getId());
|
||||
row.put("remark", object.get("remark"));
|
||||
for (DatacenterTableField field : fields) {
|
||||
String fieldName = field.getFieldName();
|
||||
row.put(fieldName, object.get(fieldName));
|
||||
}
|
||||
|
||||
Db.insert(actualTable, row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateValue(DatacenterTable entity, JSONObject object, LoginAccount account) {
|
||||
String actualTable = entity.getActualTable();
|
||||
List<DatacenterTableField> fields = entity.getFields();
|
||||
|
||||
Row row = Row.ofKey("id", object.get("id"));
|
||||
row.put("modified", new Date());
|
||||
row.put("modified_by", account.getId());
|
||||
for (DatacenterTableField field : fields) {
|
||||
String fieldName = field.getFieldName();
|
||||
row.put(fieldName, object.get(fieldName));
|
||||
}
|
||||
|
||||
Db.updateById(actualTable, row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(DatacenterTable entity, BigInteger id, LoginAccount account) {
|
||||
String actualTable = entity.getActualTable();
|
||||
Row row = Row.ofKey("id", id);
|
||||
Db.deleteById(actualTable, row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package tech.easyflow.datacenter.config;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@MapperScan("tech.easyflow.datacenter.mapper")
|
||||
public class DatacenterModuleConfig {
|
||||
|
||||
public DatacenterModuleConfig() {
|
||||
System.out.println("启用模块 >>>>>>>>>> module-datacenter");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package tech.easyflow.datacenter.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import tech.easyflow.datacenter.entity.base.DatacenterTableBase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据中枢表 实体类。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
@Table(value = "tb_datacenter_table", comment = "数据中枢表")
|
||||
public class DatacenterTable extends DatacenterTableBase {
|
||||
|
||||
@Column(ignore = true)
|
||||
private List<DatacenterTableField> fields = new ArrayList<>();
|
||||
|
||||
public List<DatacenterTableField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<DatacenterTableField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package tech.easyflow.datacenter.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import tech.easyflow.datacenter.entity.base.DatacenterTableFieldBase;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
|
||||
/**
|
||||
* 实体类。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
@Table(value = "tb_datacenter_table_field", comment = "数据中枢字段表")
|
||||
public class DatacenterTableField extends DatacenterTableFieldBase {
|
||||
|
||||
/**
|
||||
* 是否删除该字段
|
||||
* 前端传入 true 则删除该字段
|
||||
*/
|
||||
@Column(ignore = true)
|
||||
private Boolean handleDelete = false;
|
||||
|
||||
public Boolean isHandleDelete() {
|
||||
return handleDelete;
|
||||
}
|
||||
|
||||
public void setHandleDelete(Boolean handleDelete) {
|
||||
this.handleDelete = handleDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端区分 rowKey
|
||||
*/
|
||||
public BigInteger getRowKey() {
|
||||
return this.getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package tech.easyflow.datacenter.entity.base;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.KeyType;
|
||||
import com.mybatisflex.core.handler.FastjsonTypeHandler;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import tech.easyflow.common.entity.DateEntity;
|
||||
|
||||
|
||||
public class DatacenterTableBase extends DateEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||
private BigInteger id;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@Column(comment = "部门ID")
|
||||
private BigInteger deptId;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@Column(tenantId = true, comment = "租户ID")
|
||||
private BigInteger tenantId;
|
||||
|
||||
/**
|
||||
* 数据表名
|
||||
*/
|
||||
@Column(comment = "数据表名")
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* 数据表描述
|
||||
*/
|
||||
@Column(comment = "数据表描述")
|
||||
private String tableDesc;
|
||||
|
||||
/**
|
||||
* 物理表名
|
||||
*/
|
||||
@Column(comment = "物理表名")
|
||||
private String actualTable;
|
||||
|
||||
/**
|
||||
* 数据状态
|
||||
*/
|
||||
@Column(comment = "数据状态")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(comment = "创建时间")
|
||||
private Date created;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@Column(comment = "创建者")
|
||||
private BigInteger createdBy;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@Column(comment = "修改时间")
|
||||
private Date modified;
|
||||
|
||||
/**
|
||||
* 修改者
|
||||
*/
|
||||
@Column(comment = "修改者")
|
||||
private BigInteger modifiedBy;
|
||||
|
||||
/**
|
||||
* 扩展项
|
||||
*/
|
||||
@Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项")
|
||||
private Map<String, Object> options;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigInteger getDeptId() {
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setDeptId(BigInteger deptId) {
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public BigInteger getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(BigInteger tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public String getTableDesc() {
|
||||
return tableDesc;
|
||||
}
|
||||
|
||||
public void setTableDesc(String tableDesc) {
|
||||
this.tableDesc = tableDesc;
|
||||
}
|
||||
|
||||
public String getActualTable() {
|
||||
return actualTable;
|
||||
}
|
||||
|
||||
public void setActualTable(String actualTable) {
|
||||
this.actualTable = actualTable;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public BigInteger getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(BigInteger createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Date getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public void setModified(Date modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public BigInteger getModifiedBy() {
|
||||
return modifiedBy;
|
||||
}
|
||||
|
||||
public void setModifiedBy(BigInteger modifiedBy) {
|
||||
this.modifiedBy = modifiedBy;
|
||||
}
|
||||
|
||||
public Map<String, Object> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Map<String, Object> options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package tech.easyflow.datacenter.entity.base;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.KeyType;
|
||||
import com.mybatisflex.core.handler.FastjsonTypeHandler;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import tech.easyflow.common.entity.DateEntity;
|
||||
|
||||
|
||||
public class DatacenterTableFieldBase extends DateEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||
private BigInteger id;
|
||||
|
||||
/**
|
||||
* 数据表ID
|
||||
*/
|
||||
@Column(comment = "数据表ID")
|
||||
private BigInteger tableId;
|
||||
|
||||
/**
|
||||
* 字段名称
|
||||
*/
|
||||
@Column(comment = "字段名称")
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 字段描述
|
||||
*/
|
||||
@Column(comment = "字段描述")
|
||||
private String fieldDesc;
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
*/
|
||||
@Column(comment = "字段类型")
|
||||
private Integer fieldType;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
@Column(comment = "是否必填")
|
||||
private Integer required;
|
||||
|
||||
/**
|
||||
* 扩展项
|
||||
*/
|
||||
@Column(typeHandler = FastjsonTypeHandler.class, comment = "扩展项")
|
||||
private Map<String, Object> options;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(comment = "创建时间")
|
||||
private Date created;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@Column(comment = "创建者")
|
||||
private BigInteger createdBy;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@Column(comment = "修改时间")
|
||||
private Date modified;
|
||||
|
||||
/**
|
||||
* 修改者
|
||||
*/
|
||||
@Column(comment = "修改者")
|
||||
private BigInteger modifiedBy;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigInteger getTableId() {
|
||||
return tableId;
|
||||
}
|
||||
|
||||
public void setTableId(BigInteger tableId) {
|
||||
this.tableId = tableId;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public void setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getFieldDesc() {
|
||||
return fieldDesc;
|
||||
}
|
||||
|
||||
public void setFieldDesc(String fieldDesc) {
|
||||
this.fieldDesc = fieldDesc;
|
||||
}
|
||||
|
||||
public Integer getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public void setFieldType(Integer fieldType) {
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
public Integer getRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(Integer required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public Map<String, Object> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Map<String, Object> options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public BigInteger getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(BigInteger createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Date getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public void setModified(Date modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public BigInteger getModifiedBy() {
|
||||
return modifiedBy;
|
||||
}
|
||||
|
||||
public void setModifiedBy(BigInteger modifiedBy) {
|
||||
this.modifiedBy = modifiedBy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package tech.easyflow.datacenter.entity.vo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class HeaderVo {
|
||||
|
||||
private String key;
|
||||
private String dataIndex;
|
||||
private String title;
|
||||
private Integer fieldType;
|
||||
private Integer required;
|
||||
private BigInteger fieldId;
|
||||
private BigInteger tableId;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getDataIndex() {
|
||||
return dataIndex;
|
||||
}
|
||||
|
||||
public void setDataIndex(String dataIndex) {
|
||||
this.dataIndex = dataIndex;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public Integer getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public void setFieldType(Integer fieldType) {
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
public Integer getRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(Integer required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public BigInteger getFieldId() {
|
||||
return fieldId;
|
||||
}
|
||||
|
||||
public void setFieldId(BigInteger fieldId) {
|
||||
this.fieldId = fieldId;
|
||||
}
|
||||
|
||||
public BigInteger getTableId() {
|
||||
return tableId;
|
||||
}
|
||||
|
||||
public void setTableId(BigInteger tableId) {
|
||||
this.tableId = tableId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package tech.easyflow.datacenter.excel;
|
||||
|
||||
import cn.idev.excel.context.AnalysisContext;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.read.listener.ReadListener;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.util.SpringContextUtil;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.service.DatacenterTableService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
public class ReadDataListener implements ReadListener<LinkedHashMap<Integer, Object>> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ReadDataListener.class);
|
||||
|
||||
private BigInteger tableId;
|
||||
|
||||
private List<DatacenterTableField> fields;
|
||||
|
||||
private LoginAccount loginAccount;
|
||||
|
||||
private final Map<String, Integer> headFieldIndex = new HashMap<>();
|
||||
|
||||
private int successCount = 0;
|
||||
private int errorCount = 0;
|
||||
private int totalCount = 0;
|
||||
|
||||
private final List<JSONObject> errorRows = new ArrayList<>();
|
||||
|
||||
public ReadDataListener() {
|
||||
}
|
||||
|
||||
public ReadDataListener(BigInteger tableId, List<DatacenterTableField> fields, LoginAccount loginAccount) {
|
||||
this.tableId = tableId;
|
||||
this.fields = fields;
|
||||
this.loginAccount = loginAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(LinkedHashMap<Integer, Object> o, AnalysisContext analysisContext) {
|
||||
DatacenterTableService service = SpringContextUtil.getBean(DatacenterTableService.class);
|
||||
JSONObject obj = new JSONObject();
|
||||
for (DatacenterTableField field : fields) {
|
||||
String fieldName = field.getFieldName();
|
||||
Integer i = headFieldIndex.get(fieldName);
|
||||
if (i != null) {
|
||||
obj.put(fieldName, o.get(i));
|
||||
}
|
||||
}
|
||||
try {
|
||||
service.saveValue(tableId, obj, loginAccount);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
errorCount++;
|
||||
log.error("导入数据到数据中枢失败,具体值:{}", obj, e);
|
||||
errorRows.add(obj);
|
||||
}
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
|
||||
Set<Map.Entry<Integer, ReadCellData<?>>> entries = headMap.entrySet();
|
||||
for (Map.Entry<Integer, ReadCellData<?>> entry : entries) {
|
||||
Integer key = entry.getKey();
|
||||
String field = entry.getValue().getStringValue();
|
||||
headFieldIndex.put(field, key);
|
||||
}
|
||||
if (headFieldIndex.size() != fields.size()) {
|
||||
throw new RuntimeException("表头字段数量与表结构对应不上!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
|
||||
|
||||
}
|
||||
|
||||
public List<DatacenterTableField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<DatacenterTableField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public List<JSONObject> getErrorRows() {
|
||||
return errorRows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package tech.easyflow.datacenter.excel;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ReadResVo {
|
||||
|
||||
/**
|
||||
* 成功数
|
||||
*/
|
||||
private int successCount = 0;
|
||||
/**
|
||||
* 失败数
|
||||
*/
|
||||
private int errorCount = 0;
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private int totalCount = 0;
|
||||
|
||||
/**
|
||||
* 错误行
|
||||
*/
|
||||
private List<JSONObject> errorRows;
|
||||
|
||||
public ReadResVo() {
|
||||
}
|
||||
|
||||
public ReadResVo(int successCount, int errorCount, int totalCount, List<JSONObject> errorRows) {
|
||||
this.successCount = successCount;
|
||||
this.errorCount = errorCount;
|
||||
this.totalCount = totalCount;
|
||||
this.errorRows = errorRows;
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public void setSuccessCount(int successCount) {
|
||||
this.successCount = successCount;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public void setErrorCount(int errorCount) {
|
||||
this.errorCount = errorCount;
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public void setTotalCount(int totalCount) {
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
|
||||
public List<JSONObject> getErrorRows() {
|
||||
return errorRows;
|
||||
}
|
||||
|
||||
public void setErrorRows(List<JSONObject> errorRows) {
|
||||
this.errorRows = errorRows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tech.easyflow.datacenter.mapper;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
|
||||
/**
|
||||
* 映射层。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
public interface DatacenterTableFieldMapper extends BaseMapper<DatacenterTableField> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tech.easyflow.datacenter.mapper;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
|
||||
/**
|
||||
* 数据中枢表 映射层。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
public interface DatacenterTableMapper extends BaseMapper<DatacenterTable> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tech.easyflow.datacenter.service;
|
||||
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
|
||||
/**
|
||||
* 服务层。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
public interface DatacenterTableFieldService extends IService<DatacenterTableField> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package tech.easyflow.datacenter.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.row.Row;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import tech.easyflow.common.entity.DatacenterQuery;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.entity.vo.HeaderVo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
public interface DatacenterTableService extends IService<DatacenterTable> {
|
||||
|
||||
void saveTable(DatacenterTable entity, LoginAccount loginUser);
|
||||
|
||||
void removeTable(BigInteger tableId);
|
||||
|
||||
Long getCount(DatacenterQuery where);
|
||||
|
||||
List<Row> getListData(DatacenterQuery where);
|
||||
|
||||
Page<Row> getPageData(DatacenterQuery where);
|
||||
|
||||
List<HeaderVo> getHeaders(BigInteger tableId);
|
||||
|
||||
void saveValue(BigInteger tableId, JSONObject object, LoginAccount account);
|
||||
|
||||
void removeValue(BigInteger tableId, BigInteger id, LoginAccount account);
|
||||
|
||||
List<DatacenterTableField> getFields(BigInteger tableId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package tech.easyflow.datacenter.service.impl;
|
||||
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper;
|
||||
import tech.easyflow.datacenter.service.DatacenterTableFieldService;
|
||||
|
||||
/**
|
||||
* 服务层实现。
|
||||
*
|
||||
* @author ArkLight
|
||||
* @since 2025-07-10
|
||||
*/
|
||||
@Service
|
||||
public class DatacenterTableFieldServiceImpl extends ServiceImpl<DatacenterTableFieldMapper, DatacenterTableField> implements DatacenterTableFieldService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package tech.easyflow.datacenter.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.row.Db;
|
||||
import com.mybatisflex.core.row.Row;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tech.easyflow.common.entity.DatacenterQuery;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.datacenter.adapter.DbHandleManager;
|
||||
import tech.easyflow.datacenter.adapter.DbHandleService;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTable;
|
||||
import tech.easyflow.datacenter.entity.DatacenterTableField;
|
||||
import tech.easyflow.datacenter.entity.vo.HeaderVo;
|
||||
import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper;
|
||||
import tech.easyflow.datacenter.mapper.DatacenterTableMapper;
|
||||
import tech.easyflow.datacenter.service.DatacenterTableService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMapper, DatacenterTable> implements DatacenterTableService {
|
||||
|
||||
@Resource
|
||||
private DbHandleManager dbHandleManager;
|
||||
@Resource
|
||||
private DatacenterTableFieldMapper fieldsMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveTable(DatacenterTable entity, LoginAccount loginUser) {
|
||||
|
||||
DbHandleService dbHandler = dbHandleManager.getDbHandler();
|
||||
|
||||
List<DatacenterTableField> fields = entity.getFields();
|
||||
|
||||
BigInteger tableId = entity.getId();
|
||||
|
||||
if (tableId == null) {
|
||||
long snowId = new SnowFlakeIDKeyGenerator().nextId();
|
||||
entity.setId(new BigInteger(String.valueOf(snowId)));
|
||||
|
||||
String actualTable = getActualTableName(entity);
|
||||
entity.setActualTable(actualTable);
|
||||
// 先 DDL 操作,DDL会默认提交事务,不然报错了事务不会回滚。
|
||||
dbHandler.createTable(entity);
|
||||
// 保存主表和字段表
|
||||
save(entity);
|
||||
for (DatacenterTableField field : fields) {
|
||||
// 插入
|
||||
field.setCreated(new Date());
|
||||
field.setCreatedBy(loginUser.getId());
|
||||
field.setModified(new Date());
|
||||
field.setModifiedBy(loginUser.getId());
|
||||
field.setTableId(entity.getId());
|
||||
fieldsMapper.insert(field);
|
||||
}
|
||||
} else {
|
||||
// actualTable 前端不可见,所以要设置
|
||||
DatacenterTable tableRecord = getById(tableId);
|
||||
entity.setActualTable(tableRecord.getActualTable());
|
||||
dbHandler.updateTable(entity, tableRecord);
|
||||
updateById(entity);
|
||||
// 查询所有字段
|
||||
QueryWrapper w = QueryWrapper.create();
|
||||
w.eq(DatacenterTableField::getTableId, entity.getId());
|
||||
List<DatacenterTableField> fieldRecords = fieldsMapper.selectListByQuery(w);
|
||||
|
||||
Map<BigInteger, DatacenterTableField> fieldsMap = fieldRecords.stream()
|
||||
.collect(Collectors.toMap(DatacenterTableField::getId, field -> field));
|
||||
|
||||
for (DatacenterTableField field : fields) {
|
||||
BigInteger id = field.getId();
|
||||
if (id == null) {
|
||||
// 新增字段到物理表
|
||||
dbHandler.addField(entity, field);
|
||||
// 插入
|
||||
field.setCreated(new Date());
|
||||
field.setCreatedBy(loginUser.getId());
|
||||
field.setModified(new Date());
|
||||
field.setModifiedBy(loginUser.getId());
|
||||
field.setTableId(entity.getId());
|
||||
fieldsMapper.insert(field);
|
||||
} else {
|
||||
// 删除的字段
|
||||
if (field.isHandleDelete()) {
|
||||
// 删除物理表中的字段
|
||||
dbHandler.deleteField(entity, field);
|
||||
// 删除字段
|
||||
fieldsMapper.deleteById(id);
|
||||
} else {
|
||||
// 修改物理表中的字段
|
||||
DatacenterTableField fieldRecord = fieldsMap.get(id);
|
||||
dbHandler.updateField(entity, fieldRecord, field);
|
||||
// 更新字段,字段类型不允许修改
|
||||
field.setFieldType(field.getFieldType());
|
||||
field.setModified(new Date());
|
||||
field.setModifiedBy(loginUser.getId());
|
||||
fieldsMapper.update(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeTable(BigInteger tableId) {
|
||||
DatacenterTable record = getById(tableId);
|
||||
dbHandleManager.getDbHandler().deleteTable(record);
|
||||
removeById(tableId);
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||
fieldsMapper.deleteByQuery(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCount(DatacenterQuery where) {
|
||||
String actualTable = getActualTable(where.getTableId());
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
buildCondition(wrapper, where);
|
||||
return Db.selectCountByQuery(actualTable, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Row> getListData(DatacenterQuery where) {
|
||||
String actualTable = getActualTable(where.getTableId());
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
buildCondition(wrapper, where);
|
||||
List<Row> rows = Db.selectListByQuery(actualTable, wrapper);
|
||||
handleBigNumber(rows);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Row> getPageData(DatacenterQuery where) {
|
||||
Long pageNumber = where.getPageNumber();
|
||||
Long pageSize = where.getPageSize();
|
||||
|
||||
Long count = getCount(where);
|
||||
if (count == 0) {
|
||||
return new Page<>(new ArrayList<>(), pageNumber, pageSize, count);
|
||||
}
|
||||
|
||||
String actualTable = getActualTable(where.getTableId());
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
buildCondition(wrapper, where);
|
||||
|
||||
Page<Row> page = new Page<>(pageNumber, pageSize, count);
|
||||
Page<Row> paginate = Db.paginate(actualTable, page, wrapper);
|
||||
handleBigNumber(paginate.getRecords());
|
||||
return paginate;
|
||||
}
|
||||
|
||||
private void handleBigNumber(List<Row> records) {
|
||||
for (Row record : records) {
|
||||
Map<String, Object> newMap = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Object> entry : record.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if ((value instanceof BigInteger ||
|
||||
value instanceof BigDecimal ||
|
||||
value instanceof Long)) {
|
||||
newMap.put(entry.getKey(), value.toString());
|
||||
} else {
|
||||
newMap.put(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
record.clear();
|
||||
record.putAll(newMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeaderVo> getHeaders(BigInteger tableId) {
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||
wrapper.orderBy("id");
|
||||
List<DatacenterTableField> fields = fieldsMapper.selectListByQuery(wrapper);
|
||||
List<HeaderVo> headers = new ArrayList<>();
|
||||
for (DatacenterTableField field : fields) {
|
||||
HeaderVo header = new HeaderVo();
|
||||
header.setKey(field.getFieldName());
|
||||
header.setDataIndex(field.getFieldName());
|
||||
header.setTitle(field.getFieldDesc());
|
||||
header.setFieldType(field.getFieldType());
|
||||
header.setRequired(field.getRequired());
|
||||
header.setFieldId(field.getId());
|
||||
header.setTableId(field.getTableId());
|
||||
headers.add(header);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveValue(BigInteger tableId, JSONObject object, LoginAccount account) {
|
||||
|
||||
DatacenterTable table = getById(tableId);
|
||||
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||
List<DatacenterTableField> fields = fieldsMapper.selectListByQuery(wrapper);
|
||||
|
||||
if (CollectionUtil.isEmpty(fields)) {
|
||||
throw new BusinessException("请先添加字段");
|
||||
}
|
||||
table.setFields(fields);
|
||||
Object valueId = object.get("id");
|
||||
if (valueId == null) {
|
||||
dbHandleManager.getDbHandler().saveValue(table, object, account);
|
||||
} else {
|
||||
dbHandleManager.getDbHandler().updateValue(table, object, account);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(BigInteger tableId, BigInteger id, LoginAccount account) {
|
||||
DatacenterTable record = getById(tableId);
|
||||
dbHandleManager.getDbHandler().removeValue(record, id, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DatacenterTableField> getFields(BigInteger tableId) {
|
||||
QueryWrapper wrapper = QueryWrapper.create();
|
||||
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||
return fieldsMapper.selectListByQuery(wrapper);
|
||||
}
|
||||
|
||||
private String getActualTable(BigInteger tableId) {
|
||||
DatacenterTable record = getById(tableId);
|
||||
return record.getActualTable();
|
||||
}
|
||||
|
||||
private String getActualTableName(DatacenterTable table) {
|
||||
String tableName = table.getTableName();
|
||||
BigInteger id = table.getId();
|
||||
return "tb_dynamic_" + tableName + "_" + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private void buildCondition(QueryWrapper wrapper, DatacenterQuery where) {
|
||||
// 构建查询条件
|
||||
String condition = where.getWhere();
|
||||
if (StrUtil.isNotEmpty(condition)) {
|
||||
wrapper.where(condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package tech.easyflow.datacenter.utils;
|
||||
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SqlInjectionUtils {
|
||||
|
||||
private static final Set<String> SQL_KEYWORDS = new HashSet<>(Arrays.asList(
|
||||
"select", "insert", "update", "delete", "drop", "alter", "create",
|
||||
"table", "where", "from", "join", "union", "truncate", "execute",
|
||||
"grant", "revoke", "commit", "rollback"
|
||||
));
|
||||
|
||||
/**
|
||||
* 校验字段或表名
|
||||
*/
|
||||
public static String checkIdentifier(String identifier) {
|
||||
if (identifier == null || identifier.isEmpty()) {
|
||||
throw new BusinessException("标识符不能为空");
|
||||
}
|
||||
if (identifier.length() > 64) {
|
||||
throw new BusinessException("标识符过长");
|
||||
}
|
||||
// 检查是否只包含字母、数字和下划线
|
||||
if (!identifier.matches("^[a-zA-Z0-9_]+$")) {
|
||||
throw new BusinessException("只允许字母、数字和下划线");
|
||||
}
|
||||
if (isSqlKeyword(identifier)) {
|
||||
throw new BusinessException("非法字符");
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验注释
|
||||
* 允许的字符包括以下 Unicode 类别或符号:
|
||||
* \p{L}:任何语言的字母(包括中文、英文、日文等)。
|
||||
* \p{N}:任何数字(包括阿拉伯数字 0-9 或其他语言的数字符号)。
|
||||
* \p{Zs}:空白分隔符(如空格,但不包括换行符、制表符等)。
|
||||
* 标点符号:. , - : ? !(基础标点)。
|
||||
*/
|
||||
public static String checkComment(String comment) {
|
||||
if (comment == null) {
|
||||
return "";
|
||||
}
|
||||
if (comment.length() > 255) {
|
||||
throw new BusinessException("注释过长");
|
||||
}
|
||||
if (!comment.matches("^[\\p{L}\\p{N}\\p{Zs}\\.\\,\\-\\:\\?\\!]+$")) {
|
||||
throw new BusinessException("包含非法字符");
|
||||
}
|
||||
if (comment.contains("--")) {
|
||||
throw new BusinessException("包含非法字符!");
|
||||
}
|
||||
if (comment.chars().anyMatch(c -> c <= 31 || c == 127)) {
|
||||
throw new BusinessException("存在非法字符");
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
// 检查是否是数据库关键字
|
||||
public static boolean isSqlKeyword(String word) {
|
||||
return SQL_KEYWORDS.contains(word.toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package tech.easyflow.datacenter.utils;
|
||||
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class WhereConditionSecurityChecker {
|
||||
|
||||
// 允许的运算符白名单
|
||||
private static final Set<String> ALLOWED_OPERATORS = new HashSet<>(Arrays.asList(
|
||||
"=", "!=", "<>", "<", ">", "<=", ">=", "LIKE", "IN", "IS NULL", "IS NOT NULL",
|
||||
"NOT IN", "NOT LIKE", "BETWEEN", "AND", "OR"
|
||||
));
|
||||
|
||||
// 最大条件嵌套深度
|
||||
private static final int MAX_CONDITION_DEPTH = 3;
|
||||
|
||||
// 最大表达式节点数
|
||||
private static final int MAX_EXPRESSION_NODES = 20;
|
||||
|
||||
private int currentDepth = 0;
|
||||
private int nodeCount = 0;
|
||||
|
||||
public void checkConditionSafety(Expression expr, Set<String> allowColumns) {
|
||||
try {
|
||||
// 重置计数器
|
||||
currentDepth = 0;
|
||||
nodeCount = 0;
|
||||
|
||||
// 开始安全检查
|
||||
expr.accept(new ExpressionVisitorAdapter() {
|
||||
@Override
|
||||
protected void visitBinaryExpression(BinaryExpression expr) {
|
||||
checkNodeCount();
|
||||
checkOperator(expr.getStringExpression());
|
||||
super.visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Column column) {
|
||||
checkNodeCount();
|
||||
String colName = column.getColumnName();
|
||||
if (!allowColumns.contains(colName)) {
|
||||
throw new SecurityException("非法查询列: " + colName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Function function) {
|
||||
throw new SecurityException("where 条件不允许使用函数");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AndExpression expr) {
|
||||
enterNestedCondition();
|
||||
super.visit(expr);
|
||||
exitNestedCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OrExpression expr) {
|
||||
enterNestedCondition();
|
||||
super.visit(expr);
|
||||
exitNestedCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NotExpression expr) {
|
||||
enterNestedCondition();
|
||||
super.visit(expr);
|
||||
exitNestedCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LikeExpression expr) {
|
||||
// 检查LIKE模式是否包含通配符攻击
|
||||
String pattern = expr.getRightExpression().toString();
|
||||
if (pattern.matches(".*%[^%]{50,}.*")) {
|
||||
throw new SecurityException("非法通配符");
|
||||
}
|
||||
super.visit(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JdbcParameter parameter) {
|
||||
// 允许参数化查询参数
|
||||
checkNodeCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LongValue value) {
|
||||
checkNodeCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(StringValue value) {
|
||||
checkNodeCount();
|
||||
// 检查字符串值是否包含潜在危险内容
|
||||
String str = value.getValue();
|
||||
if (str.length() > 100) {
|
||||
throw new SecurityException("字符串过长");
|
||||
}
|
||||
if (str.matches(".*[\\x00-\\x1F].*")) {
|
||||
throw new SecurityException("非法字符串");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOperator(String operator) {
|
||||
if (!ALLOWED_OPERATORS.contains(operator.toUpperCase())) {
|
||||
throw new SecurityException("非法操作: " + operator);
|
||||
}
|
||||
}
|
||||
|
||||
private void enterNestedCondition() {
|
||||
currentDepth++;
|
||||
if (currentDepth > MAX_CONDITION_DEPTH) {
|
||||
throw new SecurityException("条件嵌套深度过深");
|
||||
}
|
||||
}
|
||||
|
||||
private void exitNestedCondition() {
|
||||
currentDepth--;
|
||||
}
|
||||
|
||||
private void checkNodeCount() {
|
||||
nodeCount++;
|
||||
if (nodeCount > MAX_EXPRESSION_NODES) {
|
||||
throw new SecurityException("条件表达式节点数过多");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException("条件语句校验失败:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user