初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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 {
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}