4 Commits 255cb18840 ... 1a61eb11b5

Autor SHA1 Mensaje Fecha
  xiaoguohua 1a61eb11b5 提交缺失文件 hace 2 meses
  xiaoguohua ee56253d64 Merge branch 'master' of http://36.141.74.62:4888/yeguanglin/forecast hace 2 meses
  xiaoguohua b64cb0dd04 修改协议关联机制 hace 2 meses
  xiaoguohua 91e88c1eb0 初版本通讯协议 hace 2 meses
Se han modificado 25 ficheros con 3155 adiciones y 943 borrados
  1. 1 2
      forecast-backend-server/.gitignore
  2. 258 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/controller/GenController.java
  3. 385 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/domain/GenTable.java
  4. 373 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/domain/GenTableColumn.java
  5. 60 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/mapper/GenTableColumnMapper.java
  6. 91 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/mapper/GenTableMapper.java
  7. 68 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/GenTableColumnServiceImpl.java
  8. 554 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/GenTableServiceImpl.java
  9. 44 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/IGenTableColumnService.java
  10. 130 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/IGenTableService.java
  11. 257 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/GenUtils.java
  12. 34 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/VelocityInitializer.java
  13. 496 0
      forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/VelocityUtils.java
  14. 340 0
      gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/MyRtuMaster.java
  15. 0 69
      gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ConfigValidator.java
  16. 0 514
      gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusDataReader.java
  17. 0 339
      gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusMasterManager.java
  18. 1 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/ITransmit.java
  19. 0 3
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/channel/AbstractChannel.java
  20. 7 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/ConnectEventArgs.java
  21. 7 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/DisconnectEventArgs.java
  22. 4 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventCenter.java
  23. 44 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventCenterTopic.java
  24. 1 0
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventType.java
  25. 0 16
      gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/ProtocolMessageEventArgs.java

+ 1 - 2
forecast-backend-server/.gitignore

@@ -14,8 +14,7 @@
 # IntelliJ project files
 .idea
 *.iml
-out
-gen
+target/
 
 #
 *.class

+ 258 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/controller/GenController.java

@@ -0,0 +1,258 @@
+package com.ufo.module.tool.gen.controller;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.alibaba.druid.DbType;
+import com.alibaba.druid.sql.SQLUtils;
+import com.alibaba.druid.sql.ast.SQLStatement;
+import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
+import com.ufo.base.common.core.text.Convert;
+import com.ufo.base.common.utils.SecurityUtils;
+import com.ufo.base.common.utils.sql.SqlUtil;
+import com.ufo.base.framework.aspectj.lang.annotation.Log;
+import com.ufo.base.framework.aspectj.lang.enums.BusinessType;
+import com.ufo.base.framework.web.controller.BaseController;
+import com.ufo.base.framework.web.domain.AjaxResult;
+import com.ufo.base.framework.web.page.TableDataInfo;
+import com.ufo.module.tool.gen.domain.GenTable;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+import com.ufo.module.tool.gen.service.IGenTableColumnService;
+import com.ufo.module.tool.gen.service.IGenTableService;
+
+/**
+ * 代码生成 操作处理
+ * 
+ * @author ufo
+ */
+@RestController
+@RequestMapping("/tool/gen")
+public class GenController extends BaseController
+{
+    @Autowired
+    private IGenTableService genTableService;
+
+    @Autowired
+    private IGenTableColumnService genTableColumnService;
+
+    /**
+     * 查询代码生成列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping("/list")
+    public TableDataInfo genList(GenTable genTable)
+    {
+        startPage();
+        List<GenTable> list = genTableService.selectGenTableList(genTable);
+        return getDataTable(list);
+    }
+
+    /**
+     * 修改代码生成业务
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:query')")
+    @GetMapping(value = "/{talbleId}")
+    public AjaxResult getInfo(@PathVariable Long talbleId)
+    {
+        GenTable table = genTableService.selectGenTableById(talbleId);
+        List<GenTable> tables = genTableService.selectGenTableAll();
+        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(talbleId);
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("info", table);
+        map.put("rows", list);
+        map.put("tables", tables);
+        return success(map);
+    }
+
+    /**
+     * 查询数据库列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping("/db/list")
+    public TableDataInfo dataList(GenTable genTable)
+    {
+        startPage();
+        List<GenTable> list = genTableService.selectDbTableList(genTable);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询数据表字段列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping(value = "/column/{tableId}")
+    public TableDataInfo columnList(Long tableId)
+    {
+        TableDataInfo dataInfo = new TableDataInfo();
+        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
+        dataInfo.setRows(list);
+        dataInfo.setTotal(list.size());
+        return dataInfo;
+    }
+
+    /**
+     * 导入表结构(保存)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:import')")
+    @Log(title = "代码生成", businessType = BusinessType.IMPORT)
+    @PostMapping("/importTable")
+    public AjaxResult importTableSave(String tables)
+    {
+        String[] tableNames = Convert.toStrArray(tables);
+        // 查询表信息
+        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
+        genTableService.importGenTable(tableList, SecurityUtils.getUsername());
+        return success();
+    }
+
+    /**
+     * 创建表结构(保存)
+     */
+    @PreAuthorize("@ss.hasRole('admin')")
+    @Log(title = "创建表", businessType = BusinessType.OTHER)
+    @PostMapping("/createTable")
+    public AjaxResult createTableSave(String sql)
+    {
+        try
+        {
+            SqlUtil.filterDdlKeyword(sql);
+            List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
+            List<String> tableNames = new ArrayList<>();
+            for (SQLStatement sqlStatement : sqlStatements)
+            {
+                if (sqlStatement instanceof MySqlCreateTableStatement)
+                {
+                    MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
+                    if (genTableService.createTable(createTableStatement.toString()))
+                    {
+                        String tableName = createTableStatement.getTableName().replaceAll("`", "");
+                        tableNames.add(tableName);
+                    }
+                }
+            }
+            List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
+            String operName = SecurityUtils.getUsername();
+            genTableService.importGenTable(tableList, operName);
+            return AjaxResult.success();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+            return AjaxResult.error("创建表结构异常");
+        }
+    }
+
+    /**
+     * 修改保存代码生成业务
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:edit')")
+    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult editSave(@Validated @RequestBody GenTable genTable)
+    {
+        genTableService.validateEdit(genTable);
+        genTableService.updateGenTable(genTable);
+        return success();
+    }
+
+    /**
+     * 删除代码生成
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:remove')")
+    @Log(title = "代码生成", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{tableIds}")
+    public AjaxResult remove(@PathVariable Long[] tableIds)
+    {
+        genTableService.deleteGenTableByIds(tableIds);
+        return success();
+    }
+
+    /**
+     * 预览代码
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:preview')")
+    @GetMapping("/preview/{tableId}")
+    public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException
+    {
+        Map<String, String> dataMap = genTableService.previewCode(tableId);
+        return success(dataMap);
+    }
+
+    /**
+     * 生成代码(下载方式)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/download/{tableName}")
+    public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException
+    {
+        byte[] data = genTableService.downloadCode(tableName);
+        genCode(response, data);
+    }
+
+    /**
+     * 生成代码(自定义路径)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/genCode/{tableName}")
+    public AjaxResult genCode(@PathVariable("tableName") String tableName)
+    {
+        genTableService.generatorCode(tableName);
+        return success();
+    }
+
+    /**
+     * 同步数据库
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:edit')")
+    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
+    @GetMapping("/synchDb/{tableName}")
+    public AjaxResult synchDb(@PathVariable("tableName") String tableName)
+    {
+        genTableService.synchDb(tableName);
+        return success();
+    }
+
+    /**
+     * 批量生成代码
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/batchGenCode")
+    public void batchGenCode(HttpServletResponse response, String tables) throws IOException
+    {
+        String[] tableNames = Convert.toStrArray(tables);
+        byte[] data = genTableService.downloadCode(tableNames);
+        genCode(response, data);
+    }
+
+    /**
+     * 生成zip文件
+     */
+    private void genCode(HttpServletResponse response, byte[] data) throws IOException
+    {
+        response.reset();
+        response.addHeader("Access-Control-Allow-Origin", "*");
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
+        response.setHeader("Content-Disposition", "attachment; filename=\"ufo.zip\"");
+        response.addHeader("Content-Length", "" + data.length);
+        response.setContentType("application/octet-stream; charset=UTF-8");
+        IOUtils.write(data, response.getOutputStream());
+    }
+}

+ 385 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/domain/GenTable.java

@@ -0,0 +1,385 @@
+package com.ufo.module.tool.gen.domain;
+
+import java.util.List;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import org.apache.commons.lang3.ArrayUtils;
+import com.ufo.base.common.constant.GenConstants;
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.base.framework.web.domain.BaseEntity;
+
+/**
+ * 业务表 gen_table
+ * 
+ * @author ufo
+ */
+public class GenTable extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 编号 */
+    private Long tableId;
+
+    /** 表名称 */
+    @NotBlank(message = "表名称不能为空")
+    private String tableName;
+
+    /** 表描述 */
+    @NotBlank(message = "表描述不能为空")
+    private String tableComment;
+
+    /** 关联父表的表名 */
+    private String subTableName;
+
+    /** 本表关联父表的外键名 */
+    private String subTableFkName;
+
+    /** 实体类名称(首字母大写) */
+    @NotBlank(message = "实体类名称不能为空")
+    private String className;
+
+    /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
+    private String tplCategory;
+
+    /** 前端类型(bearjia模版 ant-dv模版) */
+    private String tplWebType;
+
+    /** 生成包路径 */
+    @NotBlank(message = "生成包路径不能为空")
+    private String packageName;
+
+    /** 生成模块名 */
+    @NotBlank(message = "生成模块名不能为空")
+    private String moduleName;
+
+    /** 生成业务名 */
+    @NotBlank(message = "生成业务名不能为空")
+    private String businessName;
+
+    /** 生成功能名 */
+    @NotBlank(message = "生成功能名不能为空")
+    private String functionName;
+
+    /** 生成作者 */
+    @NotBlank(message = "作者不能为空")
+    private String functionAuthor;
+
+    /** 生成代码方式(0zip压缩包 1自定义路径) */
+    private String genType;
+
+    /** 生成路径(不填默认项目路径) */
+    private String genPath;
+
+    /** 主键信息 */
+    private GenTableColumn pkColumn;
+
+    /** 子表信息 */
+    private GenTable subTable;
+
+    /** 表列信息 */
+    @Valid
+    private List<GenTableColumn> columns;
+
+    /** 其它生成选项 */
+    private String options;
+
+    /** 树编码字段 */
+    private String treeCode;
+
+    /** 树父编码字段 */
+    private String treeParentCode;
+
+    /** 树名称字段 */
+    private String treeName;
+
+    /** 上级菜单ID字段 */
+    private String parentMenuId;
+
+    /** 上级菜单名称字段 */
+    private String parentMenuName;
+
+    public Long getTableId()
+    {
+        return tableId;
+    }
+
+    public void setTableId(Long tableId)
+    {
+        this.tableId = tableId;
+    }
+
+    public String getTableName()
+    {
+        return tableName;
+    }
+
+    public void setTableName(String tableName)
+    {
+        this.tableName = tableName;
+    }
+
+    public String getTableComment()
+    {
+        return tableComment;
+    }
+
+    public void setTableComment(String tableComment)
+    {
+        this.tableComment = tableComment;
+    }
+
+    public String getSubTableName()
+    {
+        return subTableName;
+    }
+
+    public void setSubTableName(String subTableName)
+    {
+        this.subTableName = subTableName;
+    }
+
+    public String getSubTableFkName()
+    {
+        return subTableFkName;
+    }
+
+    public void setSubTableFkName(String subTableFkName)
+    {
+        this.subTableFkName = subTableFkName;
+    }
+
+    public String getClassName()
+    {
+        return className;
+    }
+
+    public void setClassName(String className)
+    {
+        this.className = className;
+    }
+
+    public String getTplCategory()
+    {
+        return tplCategory;
+    }
+
+    public void setTplCategory(String tplCategory)
+    {
+        this.tplCategory = tplCategory;
+    }
+
+    public String getTplWebType()
+    {
+        return tplWebType;
+    }
+
+    public void setTplWebType(String tplWebType)
+    {
+        this.tplWebType = tplWebType;
+    }
+
+    public String getPackageName()
+    {
+        return packageName;
+    }
+
+    public void setPackageName(String packageName)
+    {
+        this.packageName = packageName;
+    }
+
+    public String getModuleName()
+    {
+        return moduleName;
+    }
+
+    public void setModuleName(String moduleName)
+    {
+        this.moduleName = moduleName;
+    }
+
+    public String getBusinessName()
+    {
+        return businessName;
+    }
+
+    public void setBusinessName(String businessName)
+    {
+        this.businessName = businessName;
+    }
+
+    public String getFunctionName()
+    {
+        return functionName;
+    }
+
+    public void setFunctionName(String functionName)
+    {
+        this.functionName = functionName;
+    }
+
+    public String getFunctionAuthor()
+    {
+        return functionAuthor;
+    }
+
+    public void setFunctionAuthor(String functionAuthor)
+    {
+        this.functionAuthor = functionAuthor;
+    }
+
+    public String getGenType()
+    {
+        return genType;
+    }
+
+    public void setGenType(String genType)
+    {
+        this.genType = genType;
+    }
+
+    public String getGenPath()
+    {
+        return genPath;
+    }
+
+    public void setGenPath(String genPath)
+    {
+        this.genPath = genPath;
+    }
+
+    public GenTableColumn getPkColumn()
+    {
+        return pkColumn;
+    }
+
+    public void setPkColumn(GenTableColumn pkColumn)
+    {
+        this.pkColumn = pkColumn;
+    }
+
+    public GenTable getSubTable()
+    {
+        return subTable;
+    }
+
+    public void setSubTable(GenTable subTable)
+    {
+        this.subTable = subTable;
+    }
+
+    public List<GenTableColumn> getColumns()
+    {
+        return columns;
+    }
+
+    public void setColumns(List<GenTableColumn> columns)
+    {
+        this.columns = columns;
+    }
+
+    public String getOptions()
+    {
+        return options;
+    }
+
+    public void setOptions(String options)
+    {
+        this.options = options;
+    }
+
+    public String getTreeCode()
+    {
+        return treeCode;
+    }
+
+    public void setTreeCode(String treeCode)
+    {
+        this.treeCode = treeCode;
+    }
+
+    public String getTreeParentCode()
+    {
+        return treeParentCode;
+    }
+
+    public void setTreeParentCode(String treeParentCode)
+    {
+        this.treeParentCode = treeParentCode;
+    }
+
+    public String getTreeName()
+    {
+        return treeName;
+    }
+
+    public void setTreeName(String treeName)
+    {
+        this.treeName = treeName;
+    }
+
+    public String getParentMenuId()
+    {
+        return parentMenuId;
+    }
+
+    public void setParentMenuId(String parentMenuId)
+    {
+        this.parentMenuId = parentMenuId;
+    }
+
+    public String getParentMenuName()
+    {
+        return parentMenuName;
+    }
+
+    public void setParentMenuName(String parentMenuName)
+    {
+        this.parentMenuName = parentMenuName;
+    }
+
+    public boolean isSub()
+    {
+        return isSub(this.tplCategory);
+    }
+
+    public static boolean isSub(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
+    }
+
+    public boolean isTree()
+    {
+        return isTree(this.tplCategory);
+    }
+
+    public static boolean isTree(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
+    }
+
+    public boolean isCrud()
+    {
+        return isCrud(this.tplCategory);
+    }
+
+    public static boolean isCrud(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
+    }
+
+    public boolean isSuperColumn(String javaField)
+    {
+        return isSuperColumn(this.tplCategory, javaField);
+    }
+
+    public static boolean isSuperColumn(String tplCategory, String javaField)
+    {
+        if (isTree(tplCategory))
+        {
+            return StringUtils.equalsAnyIgnoreCase(javaField,
+                    ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
+        }
+        return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
+    }
+}

+ 373 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/domain/GenTableColumn.java

@@ -0,0 +1,373 @@
+package com.ufo.module.tool.gen.domain;
+
+import javax.validation.constraints.NotBlank;
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.base.framework.web.domain.BaseEntity;
+
+/**
+ * 代码生成业务字段表 gen_table_column
+ * 
+ * @author ufo
+ */
+public class GenTableColumn extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 编号 */
+    private Long columnId;
+
+    /** 归属表编号 */
+    private Long tableId;
+
+    /** 列名称 */
+    private String columnName;
+
+    /** 列描述 */
+    private String columnComment;
+
+    /** 列类型 */
+    private String columnType;
+
+    /** JAVA类型 */
+    private String javaType;
+
+    /** JAVA字段名 */
+    @NotBlank(message = "Java属性不能为空")
+    private String javaField;
+
+    /** 是否主键(1是) */
+    private String isPk;
+
+    /** 是否自增(1是) */
+    private String isIncrement;
+
+    /** 是否必填(1是) */
+    private String isRequired;
+
+    /** 是否为插入字段(1是) */
+    private String isInsert;
+
+    /** 是否编辑字段(1是) */
+    private String isEdit;
+
+    /** 是否列表字段(1是) */
+    private String isList;
+
+    /** 是否查询字段(1是) */
+    private String isQuery;
+
+    /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */
+    private String queryType;
+
+    /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */
+    private String htmlType;
+
+    /** 字典类型 */
+    private String dictType;
+
+    /** 排序 */
+    private Integer sort;
+
+    public void setColumnId(Long columnId)
+    {
+        this.columnId = columnId;
+    }
+
+    public Long getColumnId()
+    {
+        return columnId;
+    }
+
+    public void setTableId(Long tableId)
+    {
+        this.tableId = tableId;
+    }
+
+    public Long getTableId()
+    {
+        return tableId;
+    }
+
+    public void setColumnName(String columnName)
+    {
+        this.columnName = columnName;
+    }
+
+    public String getColumnName()
+    {
+        return columnName;
+    }
+
+    public void setColumnComment(String columnComment)
+    {
+        this.columnComment = columnComment;
+    }
+
+    public String getColumnComment()
+    {
+        return columnComment;
+    }
+
+    public void setColumnType(String columnType)
+    {
+        this.columnType = columnType;
+    }
+
+    public String getColumnType()
+    {
+        return columnType;
+    }
+
+    public void setJavaType(String javaType)
+    {
+        this.javaType = javaType;
+    }
+
+    public String getJavaType()
+    {
+        return javaType;
+    }
+
+    public void setJavaField(String javaField)
+    {
+        this.javaField = javaField;
+    }
+
+    public String getJavaField()
+    {
+        return javaField;
+    }
+
+    public String getCapJavaField()
+    {
+        return StringUtils.capitalize(javaField);
+    }
+
+    public void setIsPk(String isPk)
+    {
+        this.isPk = isPk;
+    }
+
+    public String getIsPk()
+    {
+        return isPk;
+    }
+
+    public boolean isPk()
+    {
+        return isPk(this.isPk);
+    }
+
+    public boolean isPk(String isPk)
+    {
+        return isPk != null && StringUtils.equals("1", isPk);
+    }
+
+    public String getIsIncrement()
+    {
+        return isIncrement;
+    }
+
+    public void setIsIncrement(String isIncrement)
+    {
+        this.isIncrement = isIncrement;
+    }
+
+    public boolean isIncrement()
+    {
+        return isIncrement(this.isIncrement);
+    }
+
+    public boolean isIncrement(String isIncrement)
+    {
+        return isIncrement != null && StringUtils.equals("1", isIncrement);
+    }
+
+    public void setIsRequired(String isRequired)
+    {
+        this.isRequired = isRequired;
+    }
+
+    public String getIsRequired()
+    {
+        return isRequired;
+    }
+
+    public boolean isRequired()
+    {
+        return isRequired(this.isRequired);
+    }
+
+    public boolean isRequired(String isRequired)
+    {
+        return isRequired != null && StringUtils.equals("1", isRequired);
+    }
+
+    public void setIsInsert(String isInsert)
+    {
+        this.isInsert = isInsert;
+    }
+
+    public String getIsInsert()
+    {
+        return isInsert;
+    }
+
+    public boolean isInsert()
+    {
+        return isInsert(this.isInsert);
+    }
+
+    public boolean isInsert(String isInsert)
+    {
+        return isInsert != null && StringUtils.equals("1", isInsert);
+    }
+
+    public void setIsEdit(String isEdit)
+    {
+        this.isEdit = isEdit;
+    }
+
+    public String getIsEdit()
+    {
+        return isEdit;
+    }
+
+    public boolean isEdit()
+    {
+        return isInsert(this.isEdit);
+    }
+
+    public boolean isEdit(String isEdit)
+    {
+        return isEdit != null && StringUtils.equals("1", isEdit);
+    }
+
+    public void setIsList(String isList)
+    {
+        this.isList = isList;
+    }
+
+    public String getIsList()
+    {
+        return isList;
+    }
+
+    public boolean isList()
+    {
+        return isList(this.isList);
+    }
+
+    public boolean isList(String isList)
+    {
+        return isList != null && StringUtils.equals("1", isList);
+    }
+
+    public void setIsQuery(String isQuery)
+    {
+        this.isQuery = isQuery;
+    }
+
+    public String getIsQuery()
+    {
+        return isQuery;
+    }
+
+    public boolean isQuery()
+    {
+        return isQuery(this.isQuery);
+    }
+
+    public boolean isQuery(String isQuery)
+    {
+        return isQuery != null && StringUtils.equals("1", isQuery);
+    }
+
+    public void setQueryType(String queryType)
+    {
+        this.queryType = queryType;
+    }
+
+    public String getQueryType()
+    {
+        return queryType;
+    }
+
+    public String getHtmlType()
+    {
+        return htmlType;
+    }
+
+    public void setHtmlType(String htmlType)
+    {
+        this.htmlType = htmlType;
+    }
+
+    public void setDictType(String dictType)
+    {
+        this.dictType = dictType;
+    }
+
+    public String getDictType()
+    {
+        return dictType;
+    }
+
+    public void setSort(Integer sort)
+    {
+        this.sort = sort;
+    }
+
+    public Integer getSort()
+    {
+        return sort;
+    }
+
+    public boolean isSuperColumn()
+    {
+        return isSuperColumn(this.javaField);
+    }
+
+    public static boolean isSuperColumn(String javaField)
+    {
+        return StringUtils.equalsAnyIgnoreCase(javaField,
+                // BaseEntity
+                "createBy", "createTime", "updateBy", "updateTime", "remark",
+                // TreeEntity
+                "parentName", "parentId", "orderNum", "ancestors");
+    }
+
+    public boolean isUsableColumn()
+    {
+        return isUsableColumn(javaField);
+    }
+
+    public static boolean isUsableColumn(String javaField)
+    {
+        // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单
+        return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
+    }
+
+    public String readConverterExp()
+    {
+        String remarks = StringUtils.substringBetween(this.columnComment, "(", ")");
+        StringBuffer sb = new StringBuffer();
+        if (StringUtils.isNotEmpty(remarks))
+        {
+            for (String value : remarks.split(" "))
+            {
+                if (StringUtils.isNotEmpty(value))
+                {
+                    Object startStr = value.subSequence(0, 1);
+                    String endStr = value.substring(1);
+                    sb.append("").append(startStr).append("=").append(endStr).append(",");
+                }
+            }
+            return sb.deleteCharAt(sb.length() - 1).toString();
+        }
+        else
+        {
+            return this.columnComment;
+        }
+    }
+}

+ 60 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/mapper/GenTableColumnMapper.java

@@ -0,0 +1,60 @@
+package com.ufo.module.tool.gen.mapper;
+
+import java.util.List;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+
+/**
+ * 业务字段 数据层
+ * 
+ * @author ufo
+ */
+public interface GenTableColumnMapper
+{
+    /**
+     * 根据表名称查询列信息
+     * 
+     * @param tableName 表名称
+     * @return 列信息
+     */
+    public List<GenTableColumn> selectDbTableColumnsByName(String tableName);
+
+    /**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
+
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 删除业务字段
+     * 
+     * @param genTableColumns 列数据
+     * @return 结果
+     */
+    public int deleteGenTableColumns(List<GenTableColumn> genTableColumns);
+
+    /**
+     * 批量删除业务字段
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableColumnByIds(Long[] ids);
+}

+ 91 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/mapper/GenTableMapper.java

@@ -0,0 +1,91 @@
+package com.ufo.module.tool.gen.mapper;
+
+import java.util.List;
+import com.ufo.module.tool.gen.domain.GenTable;
+
+/**
+ * 业务 数据层
+ * 
+ * @author ufo
+ */
+public interface GenTableMapper
+{
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    public List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    public List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询表ID业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    public GenTable selectGenTableById(Long id);
+
+    /**
+     * 查询表名称业务信息
+     * 
+     * @param tableName 表名称
+     * @return 业务信息
+     */
+    public GenTable selectGenTableByName(String tableName);
+
+    /**
+     * 新增业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public int insertGenTable(GenTable genTable);
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public int updateGenTable(GenTable genTable);
+
+    /**
+     * 批量删除业务
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableByIds(Long[] ids);
+
+    /**
+     * 创建表
+     *
+     * @param sql 表结构
+     * @return 结果
+     */
+    public int createTable(String sql);
+}

+ 68 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/GenTableColumnServiceImpl.java

@@ -0,0 +1,68 @@
+package com.ufo.module.tool.gen.service;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ufo.base.common.core.text.Convert;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+import com.ufo.module.tool.gen.mapper.GenTableColumnMapper;
+
+/**
+ * 业务字段 服务层实现
+ * 
+ * @author ufo
+ */
+@Service
+public class GenTableColumnServiceImpl implements IGenTableColumnService 
+{
+	@Autowired
+	private GenTableColumnMapper genTableColumnMapper;
+
+	/**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+	@Override
+	public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId)
+	{
+	    return genTableColumnMapper.selectGenTableColumnListByTableId(tableId);
+	}
+	
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int insertGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.insertGenTableColumn(genTableColumn);
+	}
+	
+	/**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int updateGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.updateGenTableColumn(genTableColumn);
+	}
+
+	/**
+     * 删除业务字段对象
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+	@Override
+	public int deleteGenTableColumnByIds(String ids)
+	{
+		return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
+	}
+}

+ 554 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/GenTableServiceImpl.java

@@ -0,0 +1,554 @@
+package com.ufo.module.tool.gen.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ufo.base.common.constant.Constants;
+import com.ufo.base.common.constant.GenConstants;
+import com.ufo.base.common.core.text.CharsetKit;
+import com.ufo.base.common.exception.ServiceException;
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.module.tool.gen.domain.GenTable;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+import com.ufo.module.tool.gen.mapper.GenTableColumnMapper;
+import com.ufo.module.tool.gen.mapper.GenTableMapper;
+import com.ufo.module.tool.gen.util.GenUtils;
+import com.ufo.module.tool.gen.util.VelocityInitializer;
+import com.ufo.module.tool.gen.util.VelocityUtils;
+
+/**
+ * 业务 服务层实现
+ * 
+ * @author ufo
+ */
+@Service
+public class GenTableServiceImpl implements IGenTableService
+{
+    private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
+
+    @Autowired
+    private GenTableMapper genTableMapper;
+
+    @Autowired
+    private GenTableColumnMapper genTableColumnMapper;
+
+    /**
+     * 查询业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    @Override
+    public GenTable selectGenTableById(Long id)
+    {
+        GenTable genTable = genTableMapper.selectGenTableById(id);
+        setTableFromOptions(genTable);
+        return genTable;
+    }
+
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    @Override
+    public List<GenTable> selectGenTableList(GenTable genTable)
+    {
+        return genTableMapper.selectGenTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableList(GenTable genTable)
+    {
+        return genTableMapper.selectDbTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableListByNames(String[] tableNames)
+    {
+        return genTableMapper.selectDbTableListByNames(tableNames);
+    }
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    @Override
+    public List<GenTable> selectGenTableAll()
+    {
+        return genTableMapper.selectGenTableAll();
+    }
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void updateGenTable(GenTable genTable)
+    {
+        String options = JSON.toJSONString(genTable.getParams());
+        genTable.setOptions(options);
+        int row = genTableMapper.updateGenTable(genTable);
+        if (row > 0)
+        {
+            for (GenTableColumn genTableColumn : genTable.getColumns())
+            {
+                genTableColumnMapper.updateGenTableColumn(genTableColumn);
+            }
+        }
+    }
+
+    /**
+     * 删除业务对象
+     * 
+     * @param tableIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void deleteGenTableByIds(Long[] tableIds)
+    {
+        genTableMapper.deleteGenTableByIds(tableIds);
+        genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
+    }
+
+    /**
+     * 创建表
+     *
+     * @param sql 创建表语句
+     * @return 结果
+     */
+    @Override
+    public boolean createTable(String sql)
+    {
+        return genTableMapper.createTable(sql) == 0;
+    }
+
+    /**
+     * 导入表结构
+     * 
+     * @param tableList 导入表列表
+     */
+    @Override
+    @Transactional
+    public void importGenTable(List<GenTable> tableList, String operName)
+    {
+        try
+        {
+            for (GenTable table : tableList)
+            {
+                String tableName = table.getTableName();
+                GenUtils.initTable(table, operName);
+                int row = genTableMapper.insertGenTable(table);
+                if (row > 0)
+                {
+                    // 保存列信息
+                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+                    for (GenTableColumn column : genTableColumns)
+                    {
+                        GenUtils.initColumnField(column, table);
+                        genTableColumnMapper.insertGenTableColumn(column);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 预览代码
+     * 
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    @Override
+    public Map<String, String> previewCode(Long tableId)
+    {
+        Map<String, String> dataMap = new LinkedHashMap<>();
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableById(tableId);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            dataMap.put(template, sw.toString());
+        }
+        return dataMap;
+    }
+
+    /**
+     * 生成代码(下载方式)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String tableName)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        generatorCode(tableName, zip);
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 生成代码(自定义路径)
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    public void generatorCode(String tableName)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+        for (String template : templates)
+        {
+            if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm"))
+            {
+                // 渲染模板
+                StringWriter sw = new StringWriter();
+                Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+                tpl.merge(context, sw);
+                try
+                {
+                    String path = getGenPath(table, template);
+                    FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
+                }
+                catch (IOException e)
+                {
+                    throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
+                }
+            }
+        }
+    }
+
+    /**
+     * 同步数据库
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    @Transactional
+    public void synchDb(String tableName)
+    {
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        List<GenTableColumn> tableColumns = table.getColumns();
+        Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));
+
+        List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+        if (StringUtils.isEmpty(dbTableColumns))
+        {
+            throw new ServiceException("同步数据失败,原表结构不存在");
+        }
+        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
+
+        dbTableColumns.forEach(column -> {
+            GenUtils.initColumnField(column, table);
+            if (tableColumnMap.containsKey(column.getColumnName()))
+            {
+                GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
+                column.setColumnId(prevColumn.getColumnId());
+                if (column.isList())
+                {
+                    // 如果是列表,继续保留查询方式/字典类型选项
+                    column.setDictType(prevColumn.getDictType());
+                    column.setQueryType(prevColumn.getQueryType());
+                }
+                if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
+                        && (column.isInsert() || column.isEdit())
+                        && ((column.isUsableColumn()) || (!column.isSuperColumn())))
+                {
+                    // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
+                    column.setIsRequired(prevColumn.getIsRequired());
+                    column.setHtmlType(prevColumn.getHtmlType());
+                }
+                genTableColumnMapper.updateGenTableColumn(column);
+            }
+            else
+            {
+                genTableColumnMapper.insertGenTableColumn(column);
+            }
+        });
+
+        List<GenTableColumn> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
+        if (StringUtils.isNotEmpty(delColumns))
+        {
+            genTableColumnMapper.deleteGenTableColumns(delColumns);
+        }
+    }
+
+    /**
+     * 批量生成代码(下载方式)
+     * 
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String[] tableNames)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        for (String tableName : tableNames)
+        {
+            generatorCode(tableName, zip);
+        }
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 查询表信息并生成代码
+     */
+    private void generatorCode(String tableName, ZipOutputStream zip)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+
+        // 用于检测重复文件名的集合
+        Set<String> usedFileNames = new HashSet<>();
+
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            try
+            {
+                String fileName = VelocityUtils.getFileName(template, table);
+
+                // 检查文件名是否重复
+                if (usedFileNames.contains(fileName))
+                {
+                    // 如果重复,添加模板路径作为后缀
+                    String templateSuffix = template.replace("/", "-").replace(".vm", "");
+                    int dotIndex = fileName.lastIndexOf(".");
+                    if (dotIndex > 0)
+                    {
+                        fileName = fileName.substring(0, dotIndex) + "-" + templateSuffix + fileName.substring(dotIndex);
+                    }
+                    else
+                    {
+                        fileName = fileName + "-" + templateSuffix;
+                    }
+                    log.warn("检测到重复文件名,已自动重命名:{} -> {}", VelocityUtils.getFileName(template, table), fileName);
+                }
+
+                usedFileNames.add(fileName);
+
+                // 添加到zip
+                zip.putNextEntry(new ZipEntry(fileName));
+                IOUtils.write(sw.toString(), zip, Constants.UTF8);
+                IOUtils.closeQuietly(sw);
+                zip.flush();
+                zip.closeEntry();
+            }
+            catch (IOException e)
+            {
+                log.error("渲染模板失败,表名:" + table.getTableName(), e);
+            }
+        }
+    }
+
+    /**
+     * 修改保存参数校验
+     * 
+     * @param genTable 业务信息
+     */
+    @Override
+    public void validateEdit(GenTable genTable)
+    {
+        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory()))
+        {
+            String options = JSON.toJSONString(genTable.getParams());
+            JSONObject paramsObj = JSON.parseObject(options);
+            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE)))
+            {
+                throw new ServiceException("树编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE)))
+            {
+                throw new ServiceException("树父编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME)))
+            {
+                throw new ServiceException("树名称字段不能为空");
+            }
+        }
+        else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory()))
+        {
+            if (StringUtils.isEmpty(genTable.getSubTableName()))
+            {
+                throw new ServiceException("关联子表的表名不能为空");
+            }
+            else if (StringUtils.isEmpty(genTable.getSubTableFkName()))
+            {
+                throw new ServiceException("子表关联的外键名不能为空");
+            }
+        }
+    }
+
+    /**
+     * 设置主键列信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setPkColumn(GenTable table)
+    {
+        for (GenTableColumn column : table.getColumns())
+        {
+            if (column.isPk())
+            {
+                table.setPkColumn(column);
+                break;
+            }
+        }
+        if (StringUtils.isNull(table.getPkColumn()))
+        {
+            table.setPkColumn(table.getColumns().get(0));
+        }
+        if (GenConstants.TPL_SUB.equals(table.getTplCategory()))
+        {
+            for (GenTableColumn column : table.getSubTable().getColumns())
+            {
+                if (column.isPk())
+                {
+                    table.getSubTable().setPkColumn(column);
+                    break;
+                }
+            }
+            if (StringUtils.isNull(table.getSubTable().getPkColumn()))
+            {
+                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
+            }
+        }
+    }
+
+    /**
+     * 设置主子表信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setSubTable(GenTable table)
+    {
+        String subTableName = table.getSubTableName();
+        if (StringUtils.isNotEmpty(subTableName))
+        {
+            table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
+        }
+    }
+
+    /**
+     * 设置代码生成其他选项值
+     * 
+     * @param genTable 设置后的生成对象
+     */
+    public void setTableFromOptions(GenTable genTable)
+    {
+        JSONObject paramsObj = JSON.parseObject(genTable.getOptions());
+        if (StringUtils.isNotNull(paramsObj))
+        {
+            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
+            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
+            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+            String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
+            String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
+
+            genTable.setTreeCode(treeCode);
+            genTable.setTreeParentCode(treeParentCode);
+            genTable.setTreeName(treeName);
+            genTable.setParentMenuId(parentMenuId);
+            genTable.setParentMenuName(parentMenuName);
+        }
+    }
+
+    /**
+     * 获取代码生成地址
+     * 
+     * @param table 业务表信息
+     * @param template 模板文件路径
+     * @return 生成地址
+     */
+    public static String getGenPath(GenTable table, String template)
+    {
+        String genPath = table.getGenPath();
+        if (StringUtils.equals(genPath, "/"))
+        {
+            return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
+        }
+        return genPath + File.separator + VelocityUtils.getFileName(template, table);
+    }
+}

+ 44 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/IGenTableColumnService.java

@@ -0,0 +1,44 @@
+package com.ufo.module.tool.gen.service;
+
+import java.util.List;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+
+/**
+ * 业务字段 服务层
+ * 
+ * @author ufo
+ */
+public interface IGenTableColumnService
+{
+    /**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
+
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 删除业务字段信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableColumnByIds(String ids);
+}

+ 130 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/service/IGenTableService.java

@@ -0,0 +1,130 @@
+package com.ufo.module.tool.gen.service;
+
+import java.util.List;
+import java.util.Map;
+import com.ufo.module.tool.gen.domain.GenTable;
+
+/**
+ * 业务 服务层
+ * 
+ * @author ufo
+ */
+public interface IGenTableService
+{
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    public List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    public List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    public GenTable selectGenTableById(Long id);
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public void updateGenTable(GenTable genTable);
+
+    /**
+     * 删除业务信息
+     * 
+     * @param tableIds 需要删除的表数据ID
+     * @return 结果
+     */
+    public void deleteGenTableByIds(Long[] tableIds);
+
+    /**
+     * 创建表
+     *
+     * @param sql 创建表语句
+     * @return 结果
+     */
+    public boolean createTable(String sql);
+
+    /**
+     * 导入表结构
+     *
+     * @param tableList 导入表列表
+     * @param operName 操作人员
+     */
+    public void importGenTable(List<GenTable> tableList, String operName);
+
+    /**
+     * 预览代码
+     * 
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    public Map<String, String> previewCode(Long tableId);
+
+    /**
+     * 生成代码(下载方式)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    public byte[] downloadCode(String tableName);
+
+    /**
+     * 生成代码(自定义路径)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    public void generatorCode(String tableName);
+
+    /**
+     * 同步数据库
+     * 
+     * @param tableName 表名称
+     */
+    public void synchDb(String tableName);
+
+    /**
+     * 批量生成代码(下载方式)
+     * 
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    public byte[] downloadCode(String[] tableNames);
+
+    /**
+     * 修改保存参数校验
+     * 
+     * @param genTable 业务信息
+     */
+    public void validateEdit(GenTable genTable);
+}

+ 257 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/GenUtils.java

@@ -0,0 +1,257 @@
+package com.ufo.module.tool.gen.util;
+
+import java.util.Arrays;
+import org.apache.commons.lang3.RegExUtils;
+import com.ufo.base.common.constant.GenConstants;
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.base.framework.config.GenConfig;
+import com.ufo.module.tool.gen.domain.GenTable;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+
+/**
+ * 代码生成器 工具类
+ * 
+ * @author ufo
+ */
+public class GenUtils
+{
+    /**
+     * 初始化表信息
+     */
+    public static void initTable(GenTable genTable, String operName)
+    {
+        genTable.setClassName(convertClassName(genTable.getTableName()));
+        genTable.setPackageName(GenConfig.getPackageName());
+        genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
+        genTable.setBusinessName(getBusinessName(genTable.getTableName()));
+        genTable.setFunctionName(replaceText(genTable.getTableComment()));
+        genTable.setFunctionAuthor(GenConfig.getAuthor());
+        genTable.setCreateBy(operName);
+    }
+
+    /**
+     * 初始化列属性字段
+     */
+    public static void initColumnField(GenTableColumn column, GenTable table)
+    {
+        String dataType = getDbType(column.getColumnType());
+        String columnName = column.getColumnName();
+        column.setTableId(table.getTableId());
+        column.setCreateBy(table.getCreateBy());
+        // 设置java字段名
+        column.setJavaField(StringUtils.toCamelCase(columnName));
+        // 设置默认类型
+        column.setJavaType(GenConstants.TYPE_STRING);
+        column.setQueryType(GenConstants.QUERY_EQ);
+
+        if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType))
+        {
+            // 字符串长度超过500设置为文本域
+            Integer columnLength = getColumnLength(column.getColumnType());
+            String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
+            column.setHtmlType(htmlType);
+        }
+        else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
+        {
+            column.setJavaType(GenConstants.TYPE_DATE);
+            column.setHtmlType(GenConstants.HTML_DATETIME);
+        }
+        else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
+        {
+            column.setHtmlType(GenConstants.HTML_INPUT);
+
+            // 如果是浮点型 统一用BigDecimal
+            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
+            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0)
+            {
+                column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
+            }
+            // 如果是整形
+            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10)
+            {
+                column.setJavaType(GenConstants.TYPE_INTEGER);
+            }
+            // 长整形
+            else
+            {
+                column.setJavaType(GenConstants.TYPE_LONG);
+            }
+        }
+
+        // 插入字段(默认所有字段都需要插入)
+        column.setIsInsert(GenConstants.REQUIRE);
+
+        // 编辑字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk())
+        {
+            column.setIsEdit(GenConstants.REQUIRE);
+        }
+        // 列表字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
+        {
+            column.setIsList(GenConstants.REQUIRE);
+        }
+        // 查询字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk())
+        {
+            column.setIsQuery(GenConstants.REQUIRE);
+        }
+
+        // 查询字段类型
+        if (StringUtils.endsWithIgnoreCase(columnName, "name"))
+        {
+            column.setQueryType(GenConstants.QUERY_LIKE);
+        }
+        // 状态字段设置单选框
+        if (StringUtils.endsWithIgnoreCase(columnName, "status"))
+        {
+            column.setHtmlType(GenConstants.HTML_RADIO);
+        }
+        // 类型&性别字段设置下拉框
+        else if (StringUtils.endsWithIgnoreCase(columnName, "type")
+                || StringUtils.endsWithIgnoreCase(columnName, "sex"))
+        {
+            column.setHtmlType(GenConstants.HTML_SELECT);
+        }
+        // 图片字段设置图片上传控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "image"))
+        {
+            column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
+        }
+        // 文件字段设置文件上传控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "file"))
+        {
+            column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
+        }
+        // 内容字段设置富文本控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "content"))
+        {
+            column.setHtmlType(GenConstants.HTML_EDITOR);
+        }
+    }
+
+    /**
+     * 校验数组是否包含指定值
+     * 
+     * @param arr 数组
+     * @param targetValue 值
+     * @return 是否包含
+     */
+    public static boolean arraysContains(String[] arr, String targetValue)
+    {
+        return Arrays.asList(arr).contains(targetValue);
+    }
+
+    /**
+     * 获取模块名
+     * 
+     * @param packageName 包名
+     * @return 模块名
+     */
+    public static String getModuleName(String packageName)
+    {
+        int lastIndex = packageName.lastIndexOf(".");
+        int nameLength = packageName.length();
+        return StringUtils.substring(packageName, lastIndex + 1, nameLength);
+    }
+
+    /**
+     * 获取业务名
+     * 
+     * @param tableName 表名
+     * @return 业务名
+     */
+    public static String getBusinessName(String tableName)
+    {
+        int lastIndex = tableName.lastIndexOf("_");
+        int nameLength = tableName.length();
+        return StringUtils.substring(tableName, lastIndex + 1, nameLength);
+    }
+
+    /**
+     * 表名转换成Java类名
+     * 
+     * @param tableName 表名称
+     * @return 类名
+     */
+    public static String convertClassName(String tableName)
+    {
+        boolean autoRemovePre = GenConfig.getAutoRemovePre();
+        String tablePrefix = GenConfig.getTablePrefix();
+        if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix))
+        {
+            String[] searchList = StringUtils.split(tablePrefix, ",");
+            tableName = replaceFirst(tableName, searchList);
+        }
+        return StringUtils.convertToCamelCase(tableName);
+    }
+
+    /**
+     * 批量替换前缀
+     * 
+     * @param replacementm 替换值
+     * @param searchList 替换列表
+     * @return
+     */
+    public static String replaceFirst(String replacementm, String[] searchList)
+    {
+        String text = replacementm;
+        for (String searchString : searchList)
+        {
+            if (replacementm.startsWith(searchString))
+            {
+                text = replacementm.replaceFirst(searchString, "");
+                break;
+            }
+        }
+        return text;
+    }
+
+    /**
+     * 关键字替换
+     * 
+     * @param text 需要被替换的名字
+     * @return 替换后的名字
+     */
+    public static String replaceText(String text)
+    {
+        return RegExUtils.replaceAll(text, "(?:表|小熊学 Java)", "");
+    }
+
+    /**
+     * 获取数据库类型字段
+     * 
+     * @param columnType 列类型
+     * @return 截取后的列类型
+     */
+    public static String getDbType(String columnType)
+    {
+        if (StringUtils.indexOf(columnType, "(") > 0)
+        {
+            return StringUtils.substringBefore(columnType, "(");
+        }
+        else
+        {
+            return columnType;
+        }
+    }
+
+    /**
+     * 获取字段长度
+     * 
+     * @param columnType 列类型
+     * @return 截取后的列类型
+     */
+    public static Integer getColumnLength(String columnType)
+    {
+        if (StringUtils.indexOf(columnType, "(") > 0)
+        {
+            String length = StringUtils.substringBetween(columnType, "(", ")");
+            return Integer.valueOf(length);
+        }
+        else
+        {
+            return 0;
+        }
+    }
+}

+ 34 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/VelocityInitializer.java

@@ -0,0 +1,34 @@
+package com.ufo.module.tool.gen.util;
+
+import java.util.Properties;
+import org.apache.velocity.app.Velocity;
+import com.ufo.base.common.constant.Constants;
+
+/**
+ * VelocityEngine工厂
+ * 
+ * @author ufo
+ */
+public class VelocityInitializer
+{
+    /**
+     * 初始化vm方法
+     */
+    public static void initVelocity()
+    {
+        Properties p = new Properties();
+        try
+        {
+            // 加载classpath目录下的vm文件
+            p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+            // 定义字符集
+            p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
+            // 初始化Velocity引擎,指定配置Properties
+            Velocity.init(p);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 496 - 0
forecast-backend-server/src/main/java/com/ufo/module/tool/gen/util/VelocityUtils.java

@@ -0,0 +1,496 @@
+package com.ufo.module.tool.gen.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ufo.base.common.constant.GenConstants;
+import com.ufo.base.common.utils.DateUtils;
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.module.tool.gen.domain.GenTable;
+import com.ufo.module.tool.gen.domain.GenTableColumn;
+import org.apache.velocity.VelocityContext;
+
+/**
+ * 模板处理工具类
+ * 
+ * @author ufo
+ */
+public class VelocityUtils
+{
+    /** 项目空间路径 */
+    private static final String PROJECT_PATH = "main/java";
+
+    /** mybatis空间路径 */
+    private static final String MYBATIS_PATH = "main/resources/mapper";
+
+    /** 默认上级菜单,系统工具 */
+    private static final String DEFAULT_PARENT_MENU_ID = "3";
+
+    /**
+     * 设置模板变量信息
+     *
+     * @return 模板列表
+     */
+    public static VelocityContext prepareContext(GenTable genTable)
+    {
+        String moduleName = genTable.getModuleName();
+        String businessName = genTable.getBusinessName();
+        String packageName = genTable.getPackageName();
+        String tplCategory = genTable.getTplCategory();
+        String functionName = genTable.getFunctionName();
+
+        VelocityContext velocityContext = new VelocityContext();
+        velocityContext.put("tplCategory", genTable.getTplCategory());
+        velocityContext.put("tableName", genTable.getTableName());
+        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
+        velocityContext.put("ClassName", genTable.getClassName());
+        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+        velocityContext.put("moduleName", genTable.getModuleName());
+        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
+        velocityContext.put("businessName", genTable.getBusinessName());
+        velocityContext.put("basePackage", getPackagePrefix(packageName));
+        velocityContext.put("packageName", packageName);
+        velocityContext.put("author", genTable.getFunctionAuthor());
+        velocityContext.put("datetime", DateUtils.getDate());
+        velocityContext.put("pkColumn", genTable.getPkColumn());
+        velocityContext.put("importList", getImportList(genTable));
+        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+        velocityContext.put("columns", genTable.getColumns());
+        velocityContext.put("table", genTable);
+        velocityContext.put("dicts", getDicts(genTable));
+        setMenuVelocityContext(velocityContext, genTable);
+        if (GenConstants.TPL_TREE.equals(tplCategory))
+        {
+            setTreeVelocityContext(velocityContext, genTable);
+        }
+        if (GenConstants.TPL_SUB.equals(tplCategory))
+        {
+            setSubVelocityContext(velocityContext, genTable);
+        }
+        return velocityContext;
+    }
+
+    public static void setMenuVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSONObject.parseObject(options);
+        String parentMenuId = getParentMenuId(paramsObj);
+        context.put("parentMenuId", parentMenuId);
+    }
+
+    public static void setTreeVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSONObject.parseObject(options);
+        String treeCode = getTreecode(paramsObj);
+        String treeParentCode = getTreeParentCode(paramsObj);
+        String treeName = getTreeName(paramsObj);
+
+        context.put("treeCode", treeCode);
+        context.put("treeParentCode", treeParentCode);
+        context.put("treeName", treeName);
+        context.put("expandColumn", getExpandColumn(genTable));
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE))
+        {
+            context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        if (paramsObj.containsKey(GenConstants.TREE_NAME))
+        {
+            context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
+        }
+    }
+
+    public static void setSubVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        GenTable subTable = genTable.getSubTable();
+        String subTableName = genTable.getSubTableName();
+        String subTableFkName = genTable.getSubTableFkName();
+        String subClassName = genTable.getSubTable().getClassName();
+        String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName);
+
+        context.put("subTable", subTable);
+        context.put("subTableName", subTableName);
+        context.put("subTableFkName", subTableFkName);
+        context.put("subTableFkClassName", subTableFkClassName);
+        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
+        context.put("subClassName", subClassName);
+        context.put("subclassName", StringUtils.uncapitalize(subClassName));
+        context.put("subImportList", getImportList(genTable.getSubTable()));
+    }
+
+    /**
+     * 获取模板信息
+     *
+     * @param tplCategory 模板分类
+     * @param tplWebType 前端模板类型
+     * @return 模板列表
+     */
+    public static List<String> getTemplateList(String tplCategory, String tplWebType)
+    {
+        List<String> templates = new ArrayList<String>();
+        templates.add("vm/java/domain.java.vm");
+        templates.add("vm/java/mapper.java.vm");
+        templates.add("vm/java/service.java.vm");
+        templates.add("vm/java/serviceImpl.java.vm");
+        templates.add("vm/java/controller.java.vm");
+        templates.add("vm/xml/mapper.xml.vm");
+        templates.add("vm/sql/sql.vm");
+
+        if (GenConstants.TPL_CRUD.equals(tplCategory) || GenConstants.TPL_TREE.equals(tplCategory))
+        {
+            // 根据前端模板类型选择不同的模板
+            if (" 2".equals(tplWebType)) // antdv-vue3模板
+            {
+                templates.add("vm/antdv/index.vue.vm");
+                templates.add("vm/antdv/modules/CreateFormVue3.vue.vm");
+                templates.add("vm/antdv/api.js.vm");
+            }
+            else // 默认使用 bearjia模板 (值为 ' 1' 或其他)
+            {
+                templates.add("vm/bearjia/index.vue.vm");
+                templates.add("vm/bearjia/detailModal.vue.vm");
+                templates.add("vm/bearjia/addUpdateModal.vue.vm");
+                templates.add("vm/bearjia/api.js.vm");
+            }
+        }
+        else if (GenConstants.TPL_SUB.equals(tplCategory))
+        {
+            // 子表模板暂时只支持 antdv 模板
+            templates.add("vm/antdv/index.vue.vm");
+            templates.add("vm/antdv/modules/CreateForm.vue.vm");
+            templates.add("vm/antdv/modules/SubTable.vue.vm");
+            templates.add("vm/antdv/modules/CreateSubForm.vue.vm");
+            templates.add("vm/java/sub-domain.java.vm");
+        }
+        return templates;
+    }
+
+    /**
+     * 获取模板信息(兼容旧版本)
+     *
+     * @return 模板列表
+     */
+    public static List<String> getTemplateList(String tplCategory)
+    {
+        return getTemplateList(tplCategory, " 1"); // 默认使用 bearjia 模板
+    }
+
+    /**
+     * 获取文件名
+     */
+    public static String getFileName(String template, GenTable genTable)
+    {
+        // 文件名称
+        String fileName = "";
+        // 包路径
+        String packageName = genTable.getPackageName();
+        // 模块名
+        String moduleName = genTable.getModuleName();
+        // 大写类名
+        String className = genTable.getClassName();
+        // 业务名称
+        String businessName = genTable.getBusinessName();
+
+        String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+        String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+        String vuePath = "vue";
+
+        if (template.contains("domain.java.vm"))
+        {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+        }
+        else if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory()))
+        {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName());
+        }
+        else if (template.contains("mapper.java.vm"))
+        {
+            fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+        }
+        else if (template.contains("service.java.vm"))
+        {
+            fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+        }
+        else if (template.contains("serviceImpl.java.vm"))
+        {
+            fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+        }
+        else if (template.contains("controller.java.vm"))
+        {
+            fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+        }
+        else if (template.contains("mapper.xml.vm"))
+        {
+            fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+        }
+        else if (template.contains("sql.vm"))
+        {
+            fileName = businessName + "Menu.sql";
+        }
+        else if (template.contains("api.js.vm"))
+        {
+            // 根据不同的模板路径生成不同的文件名,避免重复条目
+            if (template.contains("antdv/js/api.js.vm"))
+            {
+                fileName = StringUtils.format("{}/api/{}/antdv-js-{}.js", vuePath, moduleName, businessName);
+            }
+            else if (template.contains("antdv/api.js.vm"))
+            {
+                fileName = StringUtils.format("{}/api/{}/antdv-{}.js", vuePath, moduleName, businessName);
+            }
+            else if (template.contains("js/api.js.vm"))
+            {
+                fileName = StringUtils.format("{}/api/{}/js-{}.js", vuePath, moduleName, businessName);
+            }
+            else if (template.contains("bearjia/api.js.vm"))
+            {
+                fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
+            }
+            else
+            {
+                // 默认情况,添加模板路径标识避免重复
+                String templatePath = template.replace("/", "-").replace(".vm", "");
+                fileName = StringUtils.format("{}/api/{}/{}-{}.js", vuePath, moduleName, templatePath, businessName);
+            }
+        }
+        else if (template.contains("index.vue.vm"))
+        {
+            // 根据不同的模板路径生成不同的文件名,避免重复条目
+            if (template.contains("antdv/index.vue.vm"))
+            {
+                fileName = StringUtils.format("{}/views/{}/{}/antdv-index.vue", vuePath, moduleName, businessName);
+            }
+            else if (template.contains("bearjia/index.vue.vm"))
+            {
+                fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
+            }
+            else
+            {
+                // 默认情况,添加模板路径标识避免重复
+                String templatePath = template.replace("/", "-").replace(".vm", "");
+                fileName = StringUtils.format("{}/views/{}/{}/{}-index.vue", vuePath, moduleName, businessName, templatePath);
+            }
+        }
+        else if (template.contains("CreateForm.vue.vm"))
+        {
+            // 区分不同的 CreateForm 模板
+            if (template.contains("CreateFormVue3.vue.vm"))
+            {
+                fileName = StringUtils.format("{}/views/{}/{}/modules/CreateFormVue3.vue", vuePath, moduleName, businessName);
+            }
+            else
+            {
+                fileName = StringUtils.format("{}/views/{}/{}/modules/CreateForm.vue", vuePath, moduleName, businessName);
+            }
+        }
+        else if (template.contains("index-tree.vue.vm"))
+        {
+            fileName = StringUtils.format("{}/views/{}/{}/index-tree.vue", vuePath, moduleName, businessName);
+        }
+        else if (template.contains("SubTable.vue.vm"))
+        {
+            fileName = StringUtils.format("{}/views/{}/{}/modules/SubTable.vue", vuePath, moduleName, businessName);
+        }
+        else if (template.contains("CreateSubForm.vue.vm"))
+        {
+            fileName = StringUtils.format("{}/views/{}/{}/modules/CreateSubForm.vue", vuePath, moduleName, businessName);
+        }
+        else
+        {
+            // 通用处理:为未明确处理的模板添加唯一标识符,防止重复条目
+            String templateName = template.substring(template.lastIndexOf("/") + 1);
+            String templatePath = template.replace("/", "-").replace(".vm", "");
+
+            if (templateName.endsWith(".java.vm"))
+            {
+                String simpleTemplateName = templateName.replace(".java.vm", "");
+                fileName = StringUtils.format("{}/unknown/{}-{}.java", javaPath, simpleTemplateName, className);
+            }
+            else if (templateName.endsWith(".vue.vm"))
+            {
+                String simpleTemplateName = templateName.replace(".vue.vm", "");
+                fileName = StringUtils.format("{}/views/{}/{}/{}-{}.vue", vuePath, moduleName, businessName, templatePath, simpleTemplateName);
+            }
+            else if (templateName.endsWith(".js.vm"))
+            {
+                String simpleTemplateName = templateName.replace(".js.vm", "");
+                fileName = StringUtils.format("{}/api/{}/{}-{}.js", vuePath, moduleName, templatePath, simpleTemplateName);
+            }
+            else
+            {
+                // 其他类型的模板
+                fileName = StringUtils.format("{}/unknown/{}-{}", vuePath, templatePath, businessName);
+            }
+        }
+
+        System.out.println("Generated fileName: " + fileName + " for template: " + template);
+        return fileName;
+    }
+
+    /**
+     * 获取包前缀
+     *
+     * @param packageName 包名称
+     * @return 包前缀名称
+     */
+    public static String getPackagePrefix(String packageName)
+    {
+        int lastIndex = packageName.lastIndexOf(".");
+        String basePackage = StringUtils.substring(packageName, 0, lastIndex);
+        return basePackage;
+    }
+
+    /**
+     * 根据列类型获取导入包
+     * 
+     * @param genTable 业务表对象
+     * @return 返回需要导入的包列表
+     */
+    public static HashSet<String> getImportList(GenTable genTable)
+    {
+        List<GenTableColumn> columns = genTable.getColumns();
+        GenTable subGenTable = genTable.getSubTable();
+        HashSet<String> importList = new HashSet<String>();
+        if (StringUtils.isNotNull(subGenTable))
+        {
+            importList.add("java.util.List");
+        }
+        for (GenTableColumn column : columns)
+        {
+            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType()))
+            {
+                importList.add("java.util.Date");
+                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
+            }
+            else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType()))
+            {
+                importList.add("java.math.BigDecimal");
+            }
+        }
+        return importList;
+    }
+
+    /**
+     * 根据列类型获取字典组
+     * 
+     * @param genTable 业务表对象
+     * @return 返回字典组
+     */
+    public static String getDicts(GenTable genTable)
+    {
+        List<GenTableColumn> columns = genTable.getColumns();
+        List<String> dicts = new ArrayList<String>();
+        for (GenTableColumn column : columns)
+        {
+            if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+                    column.getHtmlType(),
+                    new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX }))
+            {
+                dicts.add("'" + column.getDictType() + "'");
+            }
+        }
+        return StringUtils.join(dicts, ", ");
+    }
+
+    /**
+     * 获取权限前缀
+     *
+     * @param moduleName 模块名称
+     * @param businessName 业务名称
+     * @return 返回权限前缀
+     */
+    public static String getPermissionPrefix(String moduleName, String businessName)
+    {
+        return StringUtils.format("{}:{}", moduleName, businessName);
+    }
+
+    /**
+     * 获取上级菜单ID字段
+     *
+     * @param paramsObj 生成其他选项
+     * @return 上级菜单ID字段
+     */
+    public static String getParentMenuId(JSONObject paramsObj)
+    {
+        if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
+                && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID)))
+        {
+            return paramsObj.getString(GenConstants.PARENT_MENU_ID);
+        }
+        return DEFAULT_PARENT_MENU_ID;
+    }
+
+    /**
+     * 获取树编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树编码
+     */
+    public static String getTreecode(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_CODE))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取树父编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树父编码
+     */
+    public static String getTreeParentCode(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取树名称
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树名称
+     */
+    public static String getTreeName(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_NAME))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取需要在哪一列上面显示展开按钮
+     *
+     * @param genTable 业务表对象
+     * @return 展开按钮列序号
+     */
+    public static int getExpandColumn(GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSONObject.parseObject(options);
+        String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+        int num = 0;
+        for (GenTableColumn column : genTable.getColumns())
+        {
+            if (column.isList())
+            {
+                num++;
+                String columnName = column.getColumnName();
+                if (columnName.equals(treeName))
+                {
+                    break;
+                }
+            }
+        }
+        return num;
+    }
+}

+ 340 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/MyRtuMaster.java

@@ -0,0 +1,340 @@
+package cn.oleauto.modbus.rtu;
+
+import cn.oleauto.common.core.text.Convert;
+import cn.oleauto.common.utils.StringUtils;
+import cn.oleauto.protocol.common.IChannel;
+import cn.oleauto.protocol.common.ITransmit;
+import cn.oleauto.protocol.common.event.DefaultEventArgs;
+import cn.oleauto.protocol.common.event.EventCenter;
+import cn.oleauto.protocol.common.event.EventType;
+import cn.oleauto.protocol.common.event.TransmitMessageEventArgs;
+import cn.oleauto.protocol.common.transmit.TransmitState;
+import com.serotonin.modbus4j.exception.ModbusInitException;
+import com.serotonin.modbus4j.exception.ModbusTransportException;
+import com.serotonin.modbus4j.msg.ModbusRequest;
+import com.serotonin.modbus4j.msg.ModbusResponse;
+import com.serotonin.modbus4j.serial.SerialMaster;
+import com.serotonin.modbus4j.serial.SerialPortWrapper;
+import com.serotonin.modbus4j.serial.SerialWaitingRoomKeyFactory;
+import com.serotonin.modbus4j.serial.rtu.RtuMessageParser;
+import com.serotonin.modbus4j.serial.rtu.RtuMessageRequest;
+import com.serotonin.modbus4j.serial.rtu.RtuMessageResponse;
+import com.serotonin.modbus4j.sero.NotImplementedException;
+import com.serotonin.modbus4j.sero.ShouldNeverHappenException;
+import com.serotonin.modbus4j.sero.messaging.MessageControl;
+import com.serotonin.modbus4j.sero.messaging.StreamTransport;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.Assert;
+
+import java.io.StringReader;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+@Slf4j
+public class MyRtuMaster extends SerialMaster implements ITransmit {
+
+    // Runtime fields.
+    private final MessageControl conn;
+
+    public MyRtuMaster(IChannel channel){
+        super(null);
+        this.channel = channel;
+        setTimeout(10000);
+        this.conn = getMessageControl();
+    }
+
+    @Override
+    synchronized public void init() throws ModbusInitException {
+        try {
+            openConnection();
+        }
+        catch (Exception e) {
+            throw new ModbusInitException(e);
+        }
+        initialized = true;
+    }
+
+    private void openConnection() throws Exception {
+        super.openConnection(null);
+
+        RtuMessageParser rtuMessageParser = new RtuMessageParser(true);
+        this.conn.start(transport, rtuMessageParser, null, new SerialWaitingRoomKeyFactory());
+        if (getePoll() == null) {
+            ((StreamTransport) transport).start("Modbus RTU master");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    synchronized public void destroy() {
+        closeMessageControl(conn);
+        super.close();
+        initialized = false;
+        setState(TransmitState.DISCONNECTED);
+    }
+
+    @Override
+    synchronized public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException {
+        // Wrap the modbus request in an rtu request.
+        RtuMessageRequest rtuRequest = new RtuMessageRequest(request);
+
+        // Send the request to get the response.
+        RtuMessageResponse rtuResponse;
+        try {
+            notifySent(rtuRequest.getMessageData());
+            rtuResponse = (RtuMessageResponse) conn.send(rtuRequest);
+            if (rtuResponse == null)
+                return null;
+            notifyReceived(rtuResponse.getMessageData());
+            return rtuResponse.getModbusResponse();
+        }
+        catch (Exception e) {
+            try {
+                log.debug("Connection may have been reset. Attempting to re-open.", e);
+                openConnection();
+                rtuResponse = (RtuMessageResponse) conn.send(rtuRequest);
+                if (rtuResponse == null)
+                    return null;
+                return rtuResponse.getModbusResponse();
+            }catch(Exception e2) {
+                closeConnection(conn);
+                log.debug("Failed to re-connect", e2);
+                throw new ModbusTransportException(e2, request.getSlaveId());
+            }
+        }
+    }
+
+    /**
+     * RTU Spec:
+     * For baud greater than 19200
+     * Message Spacing: 1.750uS
+     *
+     * For baud less than 19200
+     * Message Spacing: 3.5 * char time
+     *
+     * @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
+     * @return a long.
+     */
+    public static long computeMessageFrameSpacing(SerialPortWrapper wrapper){
+        //For Modbus Serial Spec, Message Framing rates at 19200 Baud are fixed
+        if (wrapper.getBaudRate() > 19200) {
+            return 1750000l; //Nanoseconds
+        }
+        else {
+            float charTime = computeCharacterTime(wrapper);
+            return (long) (charTime * 3.5f);
+        }
+    }
+
+    /**
+     * RTU Spec:
+     * For baud greater than 19200
+     * Char Spacing: 750uS
+     *
+     * For baud less than 19200
+     * Char Spacing: 1.5 * char time
+     *
+     * @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
+     * @return a long.
+     */
+    public static long computeCharacterSpacing(SerialPortWrapper wrapper){
+        //For Modbus Serial Spec, Message Framing rates at 19200 Baud are fixed
+        if (wrapper.getBaudRate() > 19200) {
+            return 750000l; //Nanoseconds
+        }
+        else {
+            float charTime = computeCharacterTime(wrapper);
+            return (long) (charTime * 1.5f);
+        }
+    }
+
+    /**
+     * Compute the time it takes to transmit 1 character with
+     * the provided Serial Parameters.
+     *
+     * RTU Spec:
+     * For baud greater than 19200
+     * Char Spacing: 750uS
+     * Message Spacing: 1.750uS
+     *
+     * For baud less than 19200
+     * Char Spacing: 1.5 * char time
+     * Message Spacing: 3.5 * char time
+     *
+     * @return time in nanoseconds
+     * @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
+     */
+    public static float computeCharacterTime(SerialPortWrapper wrapper){
+        //Compute the char size
+        float charBits = wrapper.getDataBits();
+        switch (wrapper.getStopBits()) {
+            case 1:
+                //Strangely this results in 0 stop bits.. in JSSC code
+                break;
+            case 2:
+                charBits += 2f;
+                break;
+            case 3:
+                //1.5 stop bits
+                charBits += 1.5f;
+                break;
+            default:
+                throw new ShouldNeverHappenException("Unknown stop bit size: " + wrapper.getStopBits());
+        }
+
+        if (wrapper.getParity() > 0)
+            charBits += 1; //Add another if using parity
+
+        //Compute ns it takes to send one char
+        // ((charSize/symbols per second) ) * ns per second
+        return (charBits / wrapper.getBaudRate()) * 1000000000f;
+    }
+
+    // 可以合并成一个吗?
+    private final EventCenter<DefaultEventArgs> centerConnected = new EventCenter<>();
+    private final EventCenter<DefaultEventArgs> centerDisonnected = new EventCenter<>();
+    private EventCenter<TransmitMessageEventArgs> centerReceived = new EventCenter<>();
+    private EventCenter<TransmitMessageEventArgs> centerSent = new EventCenter<>();
+    private final IChannel channel;
+    protected TransmitState state = TransmitState.STOPPED;
+    private volatile boolean isStop = false;
+
+
+    void setState(TransmitState state){
+        this.state = state;
+        if(state == TransmitState.CONNECTED){
+            notifyConnected();
+        } else {
+            notifyDisconnected();
+        }
+    }
+
+    @Override
+    public int start(String config){
+        Properties properties = new Properties();
+        try {
+            config = config.replace("\\n", "\n");
+            properties.load(new StringReader(config));
+            String comPort = properties.getProperty("port");
+            if(StringUtils.isBlank(comPort)){
+                throw new IllegalArgumentException("not exist port");
+            }
+            int baud = Convert.toInt(properties.getProperty("baud"), 9600);
+            if(baud == 0){
+                throw new IllegalArgumentException("invalid baud");
+            }
+            int dataBit = Convert.toInt(properties.getProperty("dataBit"), 8);
+            if(dataBit == 0){
+                throw new IllegalArgumentException("invalid dataBit");
+            }
+            int stopBit = Convert.toInt(properties.getProperty("stopBit"), 1);
+            if(dataBit == 0){
+                throw new IllegalArgumentException("invalid stopBit");
+            }
+            int parity = Convert.toInt(properties.getProperty("parity"), 0);
+            if(dataBit == 0){
+                throw new IllegalArgumentException("invalid parity");
+            }
+            ComSerialPortWrapper comSerialPortWrapper = new ComSerialPortWrapper(this,
+                    comPort, baud,0, 0, dataBit, stopBit, parity);
+            wrapper = comSerialPortWrapper;
+            isStop = false;
+        } catch (Exception ex){
+            log.error("channel={} config is invalid, content={}, ex={}", channel.getName(), config, ex);
+            return -1;
+        }
+        state = TransmitState.STARTED;
+        return 0;
+    }
+
+    @Override
+    public void stop(){
+        isStop = true;
+    }
+
+    @Override
+    public int connect(){
+        if(isStop){
+            return -1;
+        }
+        try {
+            init();
+            return 0;
+        } catch (Exception exception){
+            setState(TransmitState.DISCONNECTED);
+            return -1;
+        }
+    };
+
+    @Override
+    public void disconnect() {
+        closeConnection(conn);
+        setState(TransmitState.DISCONNECTED);
+    };
+    @Override
+    public TransmitState state() {
+        return state;
+    }
+
+    @Override
+    public int send(byte[] data) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public int send(byte[] data, int tmOutMs) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public byte[] receive() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public byte[] receive(int tmOutMs) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void subscribeConnected(Consumer<DefaultEventArgs> consumer){
+        Assert.notNull(consumer, "consumer can not be null.");
+        centerConnected.subscribe(consumer);
+    }
+
+    @Override
+    public void subscribeDisConnected(Consumer<DefaultEventArgs> consumer){
+        Assert.notNull(consumer, "consumer can not be null.");
+        centerDisonnected.subscribe(consumer);
+    }
+
+    @Override
+    public void subscribeSent(Consumer<TransmitMessageEventArgs> consumer){
+        Assert.notNull(consumer, "consumer can not be null.");
+        centerSent.subscribe(consumer);
+    }
+
+    @Override
+    public void subscribeReceived(Consumer<TransmitMessageEventArgs> consumer){
+        Assert.notNull(consumer, "consumer can not be null.");
+        centerReceived.subscribe(consumer);
+    }
+
+    protected void notifyConnected(){
+        state = TransmitState.CONNECTED;
+        centerConnected.publish(new DefaultEventArgs(this, EventType.TRANSMIT_CONNETED));
+    }
+
+    protected void notifyDisconnected(){
+        state = TransmitState.DISCONNECTED;
+        centerDisonnected.publish(new DefaultEventArgs(this, EventType.TRANSMIT_DISCONNECTED));
+    }
+
+    protected void notifySent(byte[] data){
+        centerSent.publish(new TransmitMessageEventArgs(this, EventType.TRANSMIT_SENT, data));
+    }
+
+    protected void notifyReceived(byte[] data){
+        centerReceived.publish(new TransmitMessageEventArgs(this, EventType.TRANSMIT_RECEIVED, data));
+    }
+}

+ 0 - 69
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ConfigValidator.java

@@ -1,69 +0,0 @@
-//package cn.oleauto.modbus.tcp.uitl;
-//
-//import cn.oleauto.modbus.tcp.dto.ModbusConfig;
-//import cn.oleauto.modbus.tcp.dto.RegisterConfig;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//
-//import java.util.*;
-//
-//public class ConfigValidator {
-//    private static final Logger logger = LoggerFactory.getLogger(ConfigValidator.class);
-//
-//    /**
-//     * 验证配置完整性
-//     */
-//    public static List<String> validate(ModbusConfig config) {
-//        List<String> errors = new ArrayList<>();
-//
-//        if (config.getDevice() == null) {
-//            errors.add("设备信息不能为空");
-//        } else {
-//            if (config.getDevice().getSlaveId() < 0 || config.getDevice().getSlaveId() > 255) {
-//                errors.add("从站ID必须在0-255之间");
-//            }
-//            if (config.getDevice().getPort() < 1 || config.getDevice().getPort() > 65535) {
-//                errors.add("端口号必须在1-65535之间");
-//            }
-//        }
-//
-//        if (config.getRegisters() == null || config.getRegisters().isEmpty()) {
-//            errors.add("寄存器配置列表不能为空");
-//        } else {
-//            // 检查地址和名称重复
-//            Set<Integer> addresses = new HashSet<>();
-//            Set<String> names = new HashSet<>();
-//
-//            for (RegisterConfig reg : config.getRegisters()) {
-//                if (reg.getName() == null || reg.getName().trim().isEmpty()) {
-//                    errors.add("寄存器名称不能为空");
-//                } else if (!names.add(reg.getName())) {
-//                    errors.add("寄存器名称重复: " + reg.getName());
-//                }
-//
-//                if (reg.getAddress() < 0) {
-//                    errors.add("寄存器地址不能为负数: " + reg.getName());
-//                } else if (!addresses.add(reg.getAddress())) {
-//                    errors.add("寄存器地址重复: " + reg.getName() + " (地址: " + reg.getAddress() + ")");
-//                }
-//
-//                if (reg.getArea() == null) {
-//                    errors.add("寄存器区域不能为空: " + reg.getName());
-//                }
-//
-//                if (reg.getType() == null) {
-//                    errors.add("数据类型不能为空: " + reg.getName());
-//                }
-//            }
-//        }
-//
-//        if (!errors.isEmpty()) {
-//            logger.warn("配置验证发现{}个问题", errors.size());
-//            errors.forEach(logger::warn);
-//        } else {
-//            logger.info("配置验证通过");
-//        }
-//
-//        return errors;
-//    }
-//}

+ 0 - 514
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusDataReader.java

@@ -1,514 +0,0 @@
-//package cn.oleauto.modbus.tcp.uitl;
-//
-//import cn.oleauto.modbus.tcp.dto.*;
-//import cn.oleauto.modbus.tcp.exception.ModbusDataException;
-//import cn.oleauto.modbus.tcp.service.DataParser;
-//import com.serotonin.modbus4j.ModbusMaster;
-//import com.serotonin.modbus4j.exception.ErrorResponseException;
-//import com.serotonin.modbus4j.exception.ModbusTransportException;
-//import com.serotonin.modbus4j.locator.BaseLocator;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//
-//import java.util.*;
-//import java.util.concurrent.ConcurrentHashMap;
-//import java.util.concurrent.ExecutorService;
-//import java.util.concurrent.Executors;
-//import java.util.stream.Collectors;
-//
-//public class ModbusDataReader {
-//    private static final Logger logger = LoggerFactory.getLogger(ModbusDataReader.class);
-//
-//    private final ModbusMasterManager masterManager;
-//    private final Map<String, ParseResult> cacheMap = new ConcurrentHashMap<>();
-//    private final Map<AreaType, List<RegisterConfig>> areaGroupMap = new ConcurrentHashMap<>();
-//    private final Map<String, RegisterConfig> configMap = new ConcurrentHashMap<>();
-//
-//    // 异步读取线程池
-//    private final ExecutorService readExecutor = Executors.newFixedThreadPool(4);
-//
-//    // 统计数据
-//    private long totalReadCount = 0;
-//    private long successReadCount = 0;
-//    private long failedReadCount = 0;
-//
-//    public ModbusDataReader(ModbusMasterManager masterManager) {
-//        this.masterManager = masterManager;
-//    }
-//
-//    /**
-//     * 构建索引
-//     */
-//    public void buildIndex(List<RegisterConfig> registers) {
-//        areaGroupMap.clear();
-//        configMap.clear();
-//
-//        for (RegisterConfig config : registers) {
-//            if (!config.isActive()) continue;
-//
-//            configMap.put(config.getName(), config);
-//            AreaType area = config.getArea();
-//            areaGroupMap.computeIfAbsent(area, k -> new ArrayList<>()).add(config);
-//        }
-//
-//        // 按地址排序
-//        for (List<RegisterConfig> list : areaGroupMap.values()) {
-//            list.sort(Comparator.comparingInt(RegisterConfig::getAddress));
-//        }
-//
-//        logger.info("索引构建完成,共{}个区域分组,{}个配置项",
-//                areaGroupMap.size(), configMap.size());
-//    }
-//
-//    /**
-//     * 读取所有有效数据(同步)
-//     */
-//    public Map<String, ParseResult> readAll() {
-//        Map<String, ParseResult> results = new LinkedHashMap<>();
-//
-//        for (Map.Entry<AreaType, List<RegisterConfig>> entry : areaGroupMap.entrySet()) {
-//            AreaType area = entry.getKey();
-//            List<RegisterConfig> configs = entry.getValue();
-//
-//            try {
-//                Map<String, ParseResult> areaResults = readByArea(area, configs);
-//                results.putAll(areaResults);
-//            } catch (Exception e) {
-//                logger.error("读取区域失败: {}", area, e);
-//                // 为每个配置创建错误结果
-//                for (RegisterConfig config : configs) {
-//                    ParseResult errorResult = new ParseResult();
-//                    errorResult.setName(config.getName());
-//                    errorResult.setAddress(config.getAddress());
-//                    errorResult.setArea(config.getArea());
-//                    errorResult.setType(config.getType());
-//                    errorResult.setSuccess(false);
-//                    errorResult.setErrorMessage("读取失败: " + e.getMessage());
-//                    results.put(config.getName(), errorResult);
-//                }
-//            }
-//        }
-//
-//        // 更新缓存
-//        cacheMap.putAll(results);
-//        updateStatistics(results);
-//
-//        return results;
-//    }
-//
-//    /**
-//     * 异步读取所有数据
-//     */
-//    public void readAllAsync(DataReadCallback callback) {
-//        readExecutor.submit(() -> {
-//            try {
-//                Map<String, ParseResult> results = readAll();
-//                if (callback != null) {
-//                    callback.onSuccess(results);
-//                }
-//            } catch (Exception e) {
-//                logger.error("异步读取失败", e);
-//                if (callback != null) {
-//                    callback.onError(e);
-//                }
-//            }
-//        });
-//    }
-//
-//    /**
-//     * 按区域读取数据
-//     */
-//    private Map<String, ParseResult> readByArea(AreaType area, List<RegisterConfig> configs)
-//            throws ModbusTransportException, ErrorResponseException {
-//
-//        Map<String, ParseResult> results = new LinkedHashMap<>();
-//
-//        if (configs == null || configs.isEmpty()) {
-//            return results;
-//        }
-//
-//        ModbusMaster master = masterManager.getMaster();
-//        int slaveId = getSlaveId();
-//
-//        // 过滤出可读的配置
-//        List<RegisterConfig> readableConfigs = configs.stream()
-//                .filter(RegisterConfig::isReadable)
-//                .collect(Collectors.toList());
-//
-//        if (readableConfigs.isEmpty()) {
-//            return results;
-//        }
-//
-//        // 按连续地址分组
-//        List<List<RegisterConfig>> groups = groupContinuousAddresses(readableConfigs);
-//
-//        for (List<RegisterConfig> group : groups) {
-//            try {
-//                Map<String, ParseResult> groupResults = readGroup(master, slaveId, area, group);
-//                results.putAll(groupResults);
-//            } catch (Exception e) {
-//                logger.error("批量读取组失败: area={}, startAddress={}",
-//                        area, group.get(0).getAddress(), e);
-//
-//                // 回退到单个读取
-//                for (RegisterConfig config : group) {
-//                    try {
-//                        ParseResult result = readSingle(master, slaveId, config);
-//                        if (result != null) {
-//                            results.put(config.getName(), result);
-//                        }
-//                    } catch (Exception ex) {
-//                        logger.error("单个读取失败: {}", config.getName(), ex);
-//                        ParseResult errorResult = createErrorResult(config, ex.getMessage());
-//                        results.put(config.getName(), errorResult);
-//                    }
-//                }
-//            }
-//        }
-//
-//        return results;
-//    }
-//
-//    /**
-//     * 按连续地址分组
-//     */
-//    private List<List<RegisterConfig>> groupContinuousAddresses(List<RegisterConfig> configs) {
-//        List<List<RegisterConfig>> groups = new ArrayList<>();
-//
-//        if (configs == null || configs.isEmpty()) {
-//            return groups;
-//        }
-//
-//        // 按地址排序
-//        List<RegisterConfig> sorted = new ArrayList<>(configs);
-//        sorted.sort(Comparator.comparingInt(RegisterConfig::getAddress));
-//
-//        List<RegisterConfig> currentGroup = new ArrayList<>();
-//
-//        for (int i = 0; i < sorted.size(); i++) {
-//            RegisterConfig current = sorted.get(i);
-//
-//            if (currentGroup.isEmpty()) {
-//                currentGroup.add(current);
-//                continue;
-//            }
-//
-//            RegisterConfig previous = sorted.get(i - 1);
-//            int previousEnd = previous.getAddress() + previous.getType().getRegisterCount();
-//
-//            // 检查地址是否连续(且类型相同)
-//            if (current.getAddress() == previousEnd &&
-//                    current.getType() == previous.getType() &&
-//                    current.getArea() == previous.getArea()) {
-//                currentGroup.add(current);
-//            } else {
-//                if (!currentGroup.isEmpty()) {
-//                    groups.add(new ArrayList<>(currentGroup));
-//                }
-//                currentGroup.clear();
-//                currentGroup.add(current);
-//            }
-//        }
-//
-//        if (!currentGroup.isEmpty()) {
-//            groups.add(currentGroup);
-//        }
-//
-//        return groups;
-//    }
-//
-//    /**
-//     * 批量读取一组配置
-//     */
-//    private Map<String, ParseResult> readGroup(ModbusMaster master, int slaveId,
-//                                               AreaType area, List<RegisterConfig> group)
-//            throws ModbusTransportException, ErrorResponseException {
-//
-//        Map<String, ParseResult> results = new LinkedHashMap<>();
-//        int startAddress = group.get(0).getAddress();
-//
-//        if (area.isBitType()) {
-//            // 读取位类型(线圈/离散输入)
-//            int count = group.size();
-//            boolean[] bitValues;
-//
-//            if (area == AreaType.COIL) {
-//                bitValues = master.getCoils(slaveId, startAddress, count);
-//            } else {
-//                bitValues = master.getDiscreteInputs(slaveId, startAddress, count);
-//            }
-//
-//            for (int i = 0; i < group.size(); i++) {
-//                RegisterConfig config = group.get(i);
-//                boolean value = (i < bitValues.length) ? bitValues[i] : false;
-//                ParseResult result = DataParser.parseBitValue(config, value);
-//                results.put(config.getName(), result);
-//            }
-//
-//        } else {
-//            // 读取寄存器类型
-//            int count = calculateGroupCount(group);
-//            short[] rawData;
-//
-//            if (area == AreaType.HOLDING) {
-//                rawData = master.getHoldingRegisters(slaveId, startAddress, count);
-//            } else {
-//                rawData = master.getInputRegisters(slaveId, startAddress, count);
-//            }
-//
-//            // 解析每个寄存器
-//            for (RegisterConfig config : group) {
-//                ParseResult result = DataParser.parseRegister(config, rawData, startAddress);
-//                if (result != null) {
-//                    results.put(config.getName(), result);
-//                }
-//            }
-//        }
-//
-//        return results;
-//    }
-//
-//    /**
-//     * 计算组需要读取的寄存器数量
-//     */
-//    private int calculateGroupCount(List<RegisterConfig> group) {
-//        if (group.isEmpty()) return 0;
-//        RegisterConfig last = group.get(group.size() - 1);
-//        RegisterConfig first = group.get(0);
-//        return last.getAddress() + last.getType().getRegisterCount() - first.getAddress();
-//    }
-//
-//    /**
-//     * 读取单个数据点
-//     */
-//    private ParseResult readSingle(ModbusMaster master, int slaveId, RegisterConfig config)
-//            throws ModbusTransportException, ErrorResponseException {
-//
-//        if (!config.isReadable()) {
-//            return createErrorResult(config, "配置为只写,不可读");
-//        }
-//
-//        if (config.isBitType()) {
-//            boolean value;
-//            if (config.getArea() == AreaType.COIL) {
-//                value = master.getCoil(slaveId, config.getAddress());
-//            } else {
-//                value = master.getDiscreteInput(slaveId, config.getAddress());
-//            }
-//            return DataParser.parseBitValue(config, value);
-//        } else {
-//            // 寄存器类型
-//            if (config.getType() == DataTypeEnum.BINARY) {
-//                throw new IllegalArgumentException("寄存器不能使用BINARY类型: " + config.getName());
-//            }
-//
-//            BaseLocator<Number> locator = createLocator(slaveId, config);
-//            Number rawValue = master.getValue(locator);
-//            return DataParser.parseNumberValue(config, rawValue);
-//        }
-//    }
-//
-//    /**
-//     * 创建BaseLocator
-//     */
-//    private BaseLocator<Number> createLocator(int slaveId, RegisterConfig config) {
-//        if (config.getArea() == AreaType.HOLDING) {
-//            return BaseLocator.holdingRegister(slaveId, config.getAddress(),
-//                    config.getType().getModbus4jDataType());
-//        } else if (config.getArea() == AreaType.INPUT) {
-//            return BaseLocator.inputRegister(slaveId, config.getAddress(),
-//                    config.getType().getModbus4jDataType());
-//        } else {
-//            throw new IllegalArgumentException("不支持的寄存器区域: " + config.getArea());
-//        }
-//    }
-//
-//    /**
-//     * 读取单个寄存器(公开方法)
-//     */
-//    public ParseResult readRegister(String name) throws ModbusDataException {
-//        RegisterConfig config = configMap.get(name);
-//        if (config == null) {
-//            throw new ModbusDataException("未找到寄存器配置: " + name);
-//        }
-//
-//        try {
-//            ModbusMaster master = masterManager.getMaster();
-//            int slaveId = getSlaveId();
-//            return readSingle(master, slaveId, config);
-//        } catch (ModbusTransportException | ErrorResponseException e) {
-//            throw new ModbusDataException("读取失败: " + name, e, name, config.getAddress());
-//        } catch (Exception e) {
-//            throw new ModbusDataException("读取异常: " + name, e);
-//        }
-//    }
-//
-//    /**
-//     * 读取单个寄存器(带配置对象)
-//     */
-//    public ParseResult readRegister(RegisterConfig config) throws ModbusDataException {
-//        try {
-//            ModbusMaster master = masterManager.getMaster();
-//            int slaveId = getSlaveId();
-//            return readSingle(master, slaveId, config);
-//        } catch (ModbusTransportException | ErrorResponseException e) {
-//            throw new ModbusDataException("读取失败: " + config.getName(), e,
-//                    config.getName(), config.getAddress());
-//        } catch (Exception e) {
-//            throw new ModbusDataException("读取异常: " + config.getName(), e);
-//        }
-//    }
-//
-//    /**
-//     * 读取指定区域的多个寄存器
-//     */
-//    public Map<String, ParseResult> readRegistersByArea(AreaType area) {
-//        List<RegisterConfig> configs = areaGroupMap.get(area);
-//        if (configs == null || configs.isEmpty()) {
-//            return Collections.emptyMap();
-//        }
-//
-//        try {
-//            return readByArea(area, configs);
-//        } catch (Exception e) {
-//            logger.error("读取区域失败: {}", area, e);
-//            Map<String, ParseResult> errorResults = new LinkedHashMap<>();
-//            for (RegisterConfig config : configs) {
-//                errorResults.put(config.getName(), createErrorResult(config, e.getMessage()));
-//            }
-//            return errorResults;
-//        }
-//    }
-//
-//    /**
-//     * 获取缓存的读取结果
-//     */
-//    public ParseResult getCachedResult(String name) {
-//        return cacheMap.get(name);
-//    }
-//
-//    /**
-//     * 获取所有缓存结果
-//     */
-//    public Map<String, ParseResult> getAllCachedResults() {
-//        return new LinkedHashMap<>(cacheMap);
-//    }
-//
-//    /**
-//     * 清除缓存
-//     */
-//    public void clearCache() {
-//        cacheMap.clear();
-//    }
-//
-//    /**
-//     * 获取从站ID
-//     */
-//    private int getSlaveId() {
-//        ModbusConfig config = masterManager.getCurrentConfig();
-//        return config != null && config.getDevice() != null ?
-//                config.getDevice().getSlaveId() : 1;
-//    }
-//
-//    /**
-//     * 创建错误结果
-//     */
-//    private ParseResult createErrorResult(RegisterConfig config, String errorMessage) {
-//        ParseResult result = new ParseResult();
-//        result.setName(config.getName());
-//        result.setAddress(config.getAddress());
-//        result.setArea(config.getArea());
-//        result.setType(config.getType());
-//        result.setUnit(config.getUnit());
-//        result.setSuccess(false);
-//        result.setErrorMessage(errorMessage);
-//        return result;
-//    }
-//
-//    /**
-//     * 更新统计数据
-//     */
-//    private void updateStatistics(Map<String, ParseResult> results) {
-//        totalReadCount++;
-//        long success = results.values().stream().filter(ParseResult::isSuccess).count();
-//        long failed = results.size() - success;
-//
-//        successReadCount += success;
-//        failedReadCount += failed;
-//    }
-//
-//    /**
-//     * 获取统计信息
-//     */
-//    public Map<String, Object> getStatistics() {
-//        Map<String, Object> stats = new HashMap<>();
-//        stats.put("totalReadCount", totalReadCount);
-//        stats.put("successReadCount", successReadCount);
-//        stats.put("failedReadCount", failedReadCount);
-//        stats.put("successRate", totalReadCount > 0 ?
-//                (double) successReadCount / totalReadCount * 100 : 0);
-//        stats.put("cacheSize", cacheMap.size());
-//        stats.put("configCount", configMap.size());
-//        return stats;
-//    }
-//
-//    /**
-//     * 获取所有配置
-//     */
-//    public List<RegisterConfig> getAllConfigs() {
-//        return new ArrayList<>(configMap.values());
-//    }
-//
-//    /**
-//     * 根据名称获取配置
-//     */
-//    public RegisterConfig getConfig(String name) {
-//        return configMap.get(name);
-//    }
-//
-//    /**
-//     * 根据区域获取配置列表
-//     */
-//    public List<RegisterConfig> getConfigsByArea(AreaType area) {
-//        return areaGroupMap.getOrDefault(area, Collections.emptyList());
-//    }
-//
-//    /**
-//     * 检查是否包含指定名称的配置
-//     */
-//    public boolean hasConfig(String name) {
-//        return configMap.containsKey(name);
-//    }
-//
-//    /**
-//     * 获取配置数量
-//     */
-//    public int getConfigCount() {
-//        return configMap.size();
-//    }
-//
-//    /**
-//     * 关闭资源
-//     */
-//    public void shutdown() {
-//        readExecutor.shutdown();
-//        try {
-//            if (!readExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
-//                readExecutor.shutdownNow();
-//            }
-//        } catch (InterruptedException e) {
-//            readExecutor.shutdownNow();
-//            Thread.currentThread().interrupt();
-//        }
-//        clearCache();
-//        logger.info("ModbusDataReader已关闭");
-//    }
-//
-//    /**
-//     * 数据读取回调接口
-//     */
-//    public interface DataReadCallback {
-//        void onSuccess(Map<String, ParseResult> results);
-//        void onError(Exception e);
-//    }
-//}

+ 0 - 339
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusMasterManager.java

@@ -1,339 +0,0 @@
-//package cn.oleauto.modbus.tcp.uitl;
-//
-//import cn.oleauto.modbus.tcp.dto.ModbusConfig;
-//import cn.oleauto.modbus.tcp.exception.ModbusConfigException;
-//import com.serotonin.modbus4j.ModbusFactory;
-//import com.serotonin.modbus4j.ModbusMaster;
-//import com.serotonin.modbus4j.exception.ModbusInitException;
-//import com.serotonin.modbus4j.ip.IpParameters;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//
-//import java.util.Map;
-//import java.util.concurrent.atomic.AtomicBoolean;
-//import java.util.concurrent.atomic.AtomicReference;
-//
-//public class ModbusMasterManager {
-//    private static final Logger logger = LoggerFactory.getLogger(ModbusMasterManager.class);
-//
-//    private final ModbusFactory modbusFactory = new ModbusFactory();
-//    private final AtomicReference<ModbusMaster> masterRef = new AtomicReference<>();
-//    private final AtomicBoolean connected = new AtomicBoolean(false);
-//
-//    // 当前配置
-//    private ModbusConfig currentConfig;
-//
-//    // 连接参数
-//    private String connectionType = "tcp"; // tcp 或 rtu
-//    private String host;
-//    private int port = 502;
-//    private String serialPort;
-//    private int baudRate = 9600;
-//    private int dataBits = 8;
-//    private int stopBits = 1;
-//    private int parity = 0; // 0:无, 1:奇, 2:偶
-//
-//    // 协议参数
-//    private int timeout = 1000;
-//    private int retries = 3;
-//
-//    /**
-//     * 初始化并连接(从配置加载)
-//     */
-//    public void connect(ModbusConfig config) throws ModbusInitException {
-//        this.currentConfig = config;
-//
-//        // 从配置中提取连接参数
-//        if (config.getDevice() != null) {
-//            this.host = config.getDevice().getIpAddress();
-//            this.port = config.getDevice().getPort();
-//        }
-//
-//        if (config.getProtocol() != null) {
-//            this.timeout = config.getProtocol().getTimeout();
-//            this.retries = config.getProtocol().getRetry();
-//        }
-//
-//        ModbusMaster master = createMaster();
-//        master.init();
-//
-//        // 关闭旧的连接
-//        disconnect();
-//
-//        masterRef.set(master);
-//        connected.set(true);
-//
-//        logger.info("Modbus连接成功: {}:{} (从站ID: {})",
-//                host, port, config.getDevice() != null ? config.getDevice().getSlaveId() : 1);
-//    }
-//
-//    /**
-//     * 连接(TCP/IP)
-//     */
-//    public void connectTcp(String host, int port, int slaveId) throws ModbusInitException {
-//        this.host = host;
-//        this.port = port;
-//        this.connectionType = "tcp";
-//
-//        // 创建临时配置
-//        ModbusConfig config = new ModbusConfig();
-//        ModbusConfig.DeviceInfo device = new ModbusConfig.DeviceInfo();
-//        device.setSlaveId(slaveId);
-//        config.setDevice(device);
-//        this.currentConfig = config;
-//
-//        connect(currentConfig);
-//    }
-//
-//    /**
-//     * 连接(RTU串口)
-//     */
-//    public void connectRtu(String serialPort, int baudRate, int slaveId) throws ModbusInitException {
-//        this.serialPort = serialPort;
-//        this.baudRate = baudRate;
-//        this.connectionType = "rtu";
-//
-//        // 创建临时配置
-//        ModbusConfig config = new ModbusConfig();
-//        ModbusConfig.DeviceInfo device = new ModbusConfig.DeviceInfo();
-//        device.setSlaveId(slaveId);
-//        device.setIpAddress(serialPort); // 复用字段存储串口名
-//        config.setDevice(device);
-//        this.currentConfig = config;
-//
-//        ModbusMaster master = createRtuMaster();
-//        master.init();
-//
-//        disconnect();
-//        masterRef.set(master);
-//        connected.set(true);
-//
-//        logger.info("Modbus RTU连接成功: {} (从站ID: {})", serialPort, slaveId);
-//    }
-//
-//    /**
-//     * 创建ModbusMaster实例(TCP/IP)
-//     */
-//    private ModbusMaster createMaster() {
-//        if (host == null || host.isEmpty()) {
-//            throw new ModbusConfigException("主机地址不能为空");
-//        }
-//
-//        IpParameters ipParams = new IpParameters();
-//        ipParams.setHost(host);
-//        ipParams.setPort(port);
-//
-//        ModbusMaster master = modbusFactory.createTcpMaster(ipParams, true);
-//        master.setTimeout(timeout);
-//        master.setRetries(retries);
-//
-//        logger.debug("创建TCP Master: {}:{}", host, port);
-//        return master;
-//    }
-//
-//    /**
-//     * 创建ModbusMaster实例(RTU串口)
-//     */
-//    private ModbusMaster createRtuMaster() {
-////        if (serialPort == null || serialPort.isEmpty()) {
-////            throw new ModbusConfigException("串口名称不能为空");
-////        }
-////
-////        RtuParameters rtuParams = new RtuParameters();
-////        rtuParams.setPort(serialPort);
-////        rtuParams.setBaudRate(baudRate);
-////        rtuParams.setDataBits(dataBits);
-////        rtuParams.setStopBits(stopBits);
-////        rtuParams.setParity(parity);
-////
-////        ModbusMaster master = modbusFactory.createRtuMaster(rtuParams);
-////        master.setTimeout(timeout);
-////        master.setRetries(retries);
-////
-////        logger.debug("创建RTU Master: {} {}bps", serialPort, baudRate);
-////        return master;
-//        return null;
-//    }
-//
-//    /**
-//     * 重新连接
-//     */
-//    public void reconnect() throws ModbusInitException {
-//        if (currentConfig == null) {
-//            throw new IllegalStateException("配置未初始化,请先调用connect()");
-//        }
-//
-//        logger.info("正在重新连接...");
-//        disconnect();
-//        connect(currentConfig);
-//        logger.info("重新连接成功");
-//    }
-//
-//    /**
-//     * 断开连接
-//     */
-//    public void disconnect() {
-//        ModbusMaster master = masterRef.get();
-//        if (master != null) {
-//            try {
-//                master.destroy();
-//                logger.debug("Modbus连接已断开");
-//            } catch (Exception e) {
-//                logger.warn("断开连接时发生错误", e);
-//            }
-//        }
-//        masterRef.set(null);
-//        connected.set(false);
-//    }
-//
-//    /**
-//     * 获取ModbusMaster实例
-//     */
-//    public ModbusMaster getMaster() {
-//        ModbusMaster master = masterRef.get();
-//        if (master == null || !connected.get()) {
-//            throw new IllegalStateException("ModbusMaster未连接,请先调用connect()");
-//        }
-//        return master;
-//    }
-//
-//    /**
-//     * 检查是否已连接
-//     */
-//    public boolean isConnected() {
-//        return connected.get() && masterRef.get() != null;
-//    }
-//
-//    /**
-//     * 获取当前配置
-//     */
-//    public ModbusConfig getCurrentConfig() {
-//        return currentConfig;
-//    }
-//
-//    /**
-//     * 更新配置
-//     */
-//    public void updateConfig(ModbusConfig newConfig) {
-//        this.currentConfig = newConfig;
-//        if (newConfig.getDevice() != null) {
-//            this.host = newConfig.getDevice().getIpAddress();
-//            this.port = newConfig.getDevice().getPort();
-//        }
-//        if (newConfig.getProtocol() != null) {
-//            this.timeout = newConfig.getProtocol().getTimeout();
-//            this.retries = newConfig.getProtocol().getRetry();
-//        }
-//        logger.info("配置已更新");
-//    }
-//
-//    /**
-//     * 设置超时时间
-//     */
-//    public void setTimeout(int timeout) {
-//        this.timeout = timeout;
-//        ModbusMaster master = masterRef.get();
-//        if (master != null) {
-//            master.setTimeout(timeout);
-//        }
-//    }
-//
-//    /**
-//     * 设置重试次数
-//     */
-//    public void setRetries(int retries) {
-//        this.retries = retries;
-//        ModbusMaster master = masterRef.get();
-//        if (master != null) {
-//            master.setRetries(retries);
-//        }
-//    }
-//
-//    /**
-//     * 获取从站ID
-//     */
-//    public int getSlaveId() {
-//        if (currentConfig != null && currentConfig.getDevice() != null) {
-//            return currentConfig.getDevice().getSlaveId();
-//        }
-//        return 1;
-//    }
-//
-//    /**
-//     * 获取连接信息
-//     */
-//    public Map<String, Object> getConnectionInfo() {
-//        Map<String, Object> info = new java.util.HashMap<>();
-//        info.put("connected", isConnected());
-//        info.put("connectionType", connectionType);
-//        info.put("host", host);
-//        info.put("port", port);
-//        info.put("slaveId", getSlaveId());
-//        info.put("timeout", timeout);
-//        info.put("retries", retries);
-//        if ("rtu".equals(connectionType)) {
-//            info.put("serialPort", serialPort);
-//            info.put("baudRate", baudRate);
-//        }
-//        return info;
-//    }
-//
-//    /**
-//     * 测试连接
-//     */
-//    public boolean testConnection() {
-//        try {
-//            ModbusMaster master = getMaster();
-//            int slaveId = getSlaveId();
-//            // 尝试读取一个寄存器测试连接
-//            // master.getHoldingRegisters(slaveId, 0, 1);
-//            return true;
-//        } catch (Exception e) {
-//            logger.warn("连接测试失败", e);
-//            return false;
-//        }
-//    }
-//
-//    /**
-//     * 设置RTU串口参数
-//     */
-//    public void setRtuParameters(String serialPort, int baudRate, int dataBits,
-//                                 int stopBits, int parity) {
-//        this.serialPort = serialPort;
-//        this.baudRate = baudRate;
-//        this.dataBits = dataBits;
-//        this.stopBits = stopBits;
-//        this.parity = parity;
-//        this.connectionType = "rtu";
-//    }
-//
-//    /**
-//     * 设置TCP参数
-//     */
-//    public void setTcpParameters(String host, int port) {
-//        this.host = host;
-//        this.port = port;
-//        this.connectionType = "tcp";
-//    }
-//
-//    /**
-//     * 关闭资源
-//     */
-//    public void destroy() {
-//        disconnect();
-//        logger.info("ModbusMasterManager已销毁");
-//    }
-//
-//    /**
-//     * 获取ModbusFactory实例
-//     */
-//    public ModbusFactory getModbusFactory() {
-//        return modbusFactory;
-//    }
-//
-//    @Override
-//    public String toString() {
-//        return String.format("ModbusMasterManager{connected=%s, host=%s, port=%d, slaveId=%d}",
-//                isConnected(), host, port, getSlaveId());
-//    }
-//}

+ 1 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/ITransmit.java

@@ -1,5 +1,6 @@
 package cn.oleauto.protocol.common;
 
+import cn.oleauto.protocol.common.event.AbstractEventArgs;
 import cn.oleauto.protocol.common.event.DefaultEventArgs;
 import cn.oleauto.protocol.common.event.TransmitMessageEventArgs;
 import cn.oleauto.protocol.common.transmit.TransmitState;

+ 0 - 3
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/channel/AbstractChannel.java

@@ -9,9 +9,6 @@ import cn.oleauto.config.domain.ConfigChannel;
 import cn.oleauto.protocol.common.IChannel;
 import cn.oleauto.protocol.common.IProtocol;
 import cn.oleauto.protocol.common.ITransmit;
-import cn.oleauto.protocol.common.dto.ReportMessageDto;
-import cn.oleauto.protocol.common.event.ProtocolMessageEventArgs;
-import cn.oleauto.protocol.common.mq.MQCenter;
 import lombok.extern.slf4j.Slf4j;
 
 @Slf4j

+ 7 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/ConnectEventArgs.java

@@ -0,0 +1,7 @@
+package cn.oleauto.protocol.common.event;
+
+public class ConnectEventArgs extends AbstractEventArgs{
+    public ConnectEventArgs(Object sender) {
+        super(sender, EventType.TRANSMIT_CONNETED);
+    }
+}

+ 7 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/DisconnectEventArgs.java

@@ -0,0 +1,7 @@
+package cn.oleauto.protocol.common.event;
+
+public class DisconnectEventArgs extends AbstractEventArgs{
+    public DisconnectEventArgs(Object sender) {
+        super(sender, EventType.TRANSMIT_DISCONNECTED);
+    }
+}

+ 4 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventCenter.java

@@ -4,6 +4,10 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
+/**
+ * 非线程安全类
+ * @param <T>
+ */
 public class EventCenter<T> {
     private final List<Consumer<T> > consumerList = new ArrayList<>();
 

+ 44 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventCenterTopic.java

@@ -0,0 +1,44 @@
+package cn.oleauto.protocol.common.event;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+/**
+ * 线程安全类
+ */
+public class EventCenterTopic {
+    private ConcurrentMap<EventType, List<Consumer<AbstractEventArgs>>> topicMap = new ConcurrentHashMap<>();
+
+    public EventCenterTopic(){
+        for(EventType eventType : EventType.values()){
+            topicMap.put(eventType, new CopyOnWriteArrayList<>());
+        }
+    }
+
+    public void subscribe(Consumer<AbstractEventArgs> consumer, EventType... eventTypes){
+        for(EventType eventType : eventTypes){
+            subscribe(eventType, consumer);
+        }
+    }
+
+    public void subscribe(EventType eventType, Consumer<AbstractEventArgs> consumer) {
+        List<Consumer<AbstractEventArgs>> orDefault = topicMap.getOrDefault(eventType, new CopyOnWriteArrayList<>());
+        orDefault.add(consumer);
+    }
+
+    public void unsubscribe(EventType eventType, Consumer<AbstractEventArgs> consumer) {
+        List<Consumer<AbstractEventArgs>> orDefault = topicMap.getOrDefault(eventType, new CopyOnWriteArrayList<>());
+        orDefault.remove(consumer);
+    }
+
+    public void clear(){
+        topicMap.clear();
+    }
+
+    public void publish(AbstractEventArgs eventArgs){
+        topicMap.getOrDefault(eventArgs.getEventType(), new CopyOnWriteArrayList<>()).forEach(consumer -> consumer.accept(eventArgs));
+    }
+}

+ 1 - 0
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/EventType.java

@@ -3,6 +3,7 @@ package cn.oleauto.protocol.common.event;
 public enum EventType {
     TRANSMIT_DISCONNECTED(0),
     TRANSMIT_CONNETED(1),
+
     TRANSMIT_SENT(2),
     TRANSMIT_RECEIVED(3),
 

+ 0 - 16
gateway/forecast-protocol-common/src/main/java/cn/oleauto/protocol/common/event/ProtocolMessageEventArgs.java

@@ -1,16 +0,0 @@
-package cn.oleauto.protocol.common.event;
-
-import lombok.Getter;
-
-public class ProtocolMessageEventArgs extends AbstractEventArgs{
-    @Getter
-    private long tmMs = System.currentTimeMillis();
-
-    @Getter
-    private byte[] data;
-
-    public ProtocolMessageEventArgs(Object sender, EventType eventType, byte[] data){
-        super(sender, eventType);
-        this.data = data;
-    }
-}