# lowCode **Repository Path**: xprj-Co_Ltd/lowCode ## Basic Information - **Project Name**: lowCode - **Description**: 这是一个低代码甚至是零代码的Java 后端快速开发框架,基于jdk25 Spring Boot + MyBatis Plus 构建,提供自动化 CRUD、动态 Join 查询、数据钩子(DataHook)等能力,定位为"低代码/敏捷开发"基础设施。通过灵活的页面设计器,以及可视化的表结构维护页面,快速构建一个满足基础CRUD功能的页面,完全可以做到零代码开发 - **Primary Language**: Java - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2026-03-03 - **Last Updated**: 2026-03-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: 低代码, 零代码 ## README # HT Platform 恒泰数字化快速开发平台
[![JDK](https://img.shields.io/badge/JDK-25-orange.svg)](https://openjdk.java.net/) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.6-brightgreen.svg)](https://spring.io/projects/spring-boot) [![Vue](https://img.shields.io/badge/Vue-3.5.22-green.svg)](https://vuejs.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-blue.svg)](https://www.typescriptlang.org/) [![MyBatis-Plus](https://img.shields.io/badge/MyBatis--Plus-3.5.14-blue.svg)](https://baomidou.com/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) 🚀 **零代码 · 低代码 · 高并发 · 企业级** [首页](#-关于项目) | [特性](#-核心特性) | [快速开始](#-快速开始) | [文档](#-文档) | [演示](#-在线演示) | [贡献](#-贡献指南)
--- ## 📖 关于项目 HT Platform 是一个基于 **JDK 25** + **Spring Boot 3.5.6** + **Vue 3.5.22** 构建的企业级快速开发平台。平台以"**零代码优先,低代码补充 **"为设计理念,通过可视化配置和强大的扩展能力,帮助开发者**减少 80% 的重复工作**,专注于业务创新。 本系统旨在构建一个减少重复工作的的快速开发平台,提升开发效率,并减少开发过程中重复工作。本系统基于jdk25构建,使用MyBatis-Plus作为数据库操作框架, 使用Caffeine + Redis构建起多级缓存框架,以适应高并发场景, 使用Druid作为数据库连接池,使用 knife4j作为接口文档生成工具,使用Lombok作为代码生成工具,使用spring security作为系统的权限认证架构, 自定义token生成算法,是的token简洁,减少数据传输,提高数据传输效率,token仅作为权限标识,不承载数据的传输。系统中基于mybatis-plus自定义 实现了常用的join查询,解决了mybatis-plus在需要使用join的情况下需要自己写sql的问题,完美的将sql降到最低,从而提升编码效率,开发者只需关注自己的 业务实现即可。 1. 平台提供了初始的前端模版,可以通过页面设计器配置化的方式来配置复杂的页面,页面结构通过树形结构展示与配置(暂不支持拖放组件),页面个组件都支持独立配置,达到直观与简洁的效果,可以配置出复杂的页面的结构。 2. 页面提供了动态SQL模板,通过配置化方式来配置动态SQL,从而达到动态SQL的配置化。将配置好的sql名称,填写到对应的页面组件数据源中,页面在读取数据的时候将从配置的sql中读取,简化sql,提高可维护性 3. 页面提供了可视化的表结构配置,可以定义表字段、索引、表关联登高级功能,可以直接从表结构生成想要的页面,例如单表格页面、单表单页面,或者表格与表单同时存在的页面。 4. 用户建立好了表结构,根据表结构配置出相关页面后,即可直接使用基础的增删改查功能,真正做到了零代码。如果与复杂的功能需求,系统定义了钩子函数,只需要在对应的钩子函数中添加自己的逻辑即可。大大减少了开发量。 5. 系统省去了传统的service层,使用钩子函数代替,但是如果有功能需要服务,建议保留service层。 ### 核心价值 - ⚡ **极速开发**:可视化页面设计器 + 自动化代码生成,开发效率提升 5-10 倍 - 🎯 **零代码实现**:90% 的常规业务需求无需编码,配置即可用 - 🔧 **灵活扩展**:钩子函数机制 + 完整的 API 体系,满足复杂业务场景 - 🛡️ **企业级安全**:国密算法加密 + Spring Security + 自定义 Token,保障数据安全 - 🚀 **高性能架构**:Caffeine + Redis 多级缓存,轻松应对高并发场景 --- ## ✨ 核心特性 ### 🔥 后端核心能力 系统支持国密加密操作,所有数据传输均通过加密后传输,数据密钥key随机生成,刷新页面会改变密钥,保证数据安全,以满足等保测评要求。密钥也采用了双向多重加密机制,确保密钥安全 #### 平台配置 ``` java ht: config: signature-filter: # 簽名过滤配置 enabled: false # 是否开启签名过滤 enabled-encrypt: # 是否开启数据加密 enabled: false # 是否开启签名过滤 ignore-paths: # 忽略放行的白名單 - /api/v1/user/login white-path: # 配置白名单 - /api/v1/user/login - /api/v1/user/logout ``` #### 钩子函数 无侵入式的业务扩展点,覆盖数据操作全生命周期:系统中定了常用的增删改查以及通用的逻辑处理,如果需要自定义对应的逻辑,例如添加用户时,需要同步处理用户的角色、部门登信息,此时需要自定义实现钩子函数。实现钩子函数需要继承实现org.ht.mybatis.hook.DataHook类, 由于低代码逻辑与常见的接口实体代码有些差别,因此钩子函数在低代码和常见接口代码中有细微差异。 1. 常见的接口中的钩子函数,需要有一个实体类,这个实体类对应的是数据库表对应,需要有对应的数据库操作的mapper,可以直接在钩子函数中使用其mapper无需单独注入,例如 ``` java @Component public class TableObjectHook extends DataHook { @Resource private TableColumnsMapper tableColumnsMapper; @Override public void beforeInsert(TableObject data) { // 检查表是否重复 TableObject one = baseMapper.selectOne(new QueryWrapper().lambda().eq(TableObject::getTableName, data.getTableName())); if (one != null) { // 如果修改的数据与原始数据不一致,则不允许修改 throw new BusinessRunTimeException("表已存在,不可创建相同的表"); } data.setDelFlag(YorN.N.name()); data.setOperationType("ADD"); } @Override public void afterInsert(TableObject data) { // 同步创建默认字段列 List cols = new ArrayList<>(); // id主键列 TableColumns col = new TableColumns(); col.setTableName(data.getTableName()); col.setTableId(data.getId()); col.setColumnCode("id"); col.setColumnName("主键"); col.setNotNull(YorN.Y.name()); col.setColumnType("bigint"); col.setShowPage(YorN.N.name()); col.setPersistentFlag(YorN.Y.name()); col.setApplyEdit(YorN.N.name()); col.setDelFlag(YorN.N.name()); cols.add(col); tableColumnsMapper.insert(cols); } @Override public void addBatch(List data) { } @Override public void beforeUpdate(TableObject data) { // 检查表是否重复 TableObject one = baseMapper.selectOne(new QueryWrapper().lambda().eq(TableObject::getTableName, data.getTableName())); if (!data.getId().equals(one.getId())) { // 如果修改的数据与原始数据不一致,则不允许修改 throw new BusinessRunTimeException("表已存在,不可创建相同的表"); } // 这里控制不能修改表属性、例如持久化、是否视图等 if (!one.getIsPersistence().equals(data.getIsPersistence())) { throw new BusinessRunTimeException("表属性不允许修改"); } if (!one.getIsView().equals(data.getIsView())) { throw new BusinessRunTimeException("表属性不允许修改"); } // 不允许直接修改旧表名称 if (!data.getOldName().equals(data.getTableName())) { data.setOldName(one.getOldName()); } data.setDelFlag(YorN.N.name()); data.setOperationType("EDIT"); } @Override public void afterUpdate(TableObject data) { } @Override public void beforeDelete(TableObject data) { throw new BusinessRunTimeException("不允许删除表结构"); } @Override public void updateBatch(List data) { } @Override public void afterDelete(TableObject data) { } @Override public void beforeDelete(List data) { } @Override public void afterDelete(List data) { } } ``` 2. 低代码的钩子函数与普通接口代码的钩子函数有细微差异,在写法上无区别,仅仅只需要在钩子函数上加上一个自定义注解@LowCodeHook,需要指定一个参数pageId,此参数需要与页面设计器中的应用名称保持一致, 统一需要继承自DataHook,固定泛型为DynamicEntity动态实体类。其他函数方法、写法与普通接口钩子函数一致,无区别,例如: ```java @Component @LowCodeHook(pageId = "role-page") public class RolePageHook extends DataHook { @Resource private RoleMenuMapper roleMenuMapper; @Resource private MenuMapper menuMapper; @Override public DynamicEntity afterQueryOne(DynamicEntity result) { Long id = result.getLong("id"); List roleMenus = roleMenuMapper.selectList( new QueryWrapper() .lambda() .eq(RoleMenu::getRoleId, id) ); List list = roleMenus.stream().map(x -> x.getMenuId().toString()).toList(); result.put("roleMenu", list); return result; } @Override public void beforeInsert(DynamicEntity data) { } @Override public void afterInsert(DynamicEntity data) { } @Override public void addBatch(List data) { } @Override public void beforeUpdate(DynamicEntity data) { } @Override public void afterUpdate(DynamicEntity data) { String roleMenu = data.getString("roleMenu"); if (StringUtils.isNotBlank(roleMenu)) { String[] split = roleMenu.split(","); // 转换为List List menuIds = new ArrayList<>(); for (String s : split) { menuIds.add(Long.parseLong(s)); } // 查询菜单的父级,确保菜单层级完整 List menus = menuMapper.selectList( new QueryWrapper() .lambda() .in(Menu::getId, menuIds) ); Map> collect = menus.stream().collect(Collectors.groupingBy(Menu::getParent)); // 遍历所有的parent,直到所有的parent都为0,并将非0的parent添加到menuIds中 List parentIds = new ArrayList<>(); if (collect.size() > 1) { collect.forEach((parentId, v) -> { if (parentId != 0L) { parentIds.add(parentId); menuIds.add(parentId); } }); } while (!parentIds.isEmpty()) { menus = menuMapper.selectList( new QueryWrapper() .lambda() .in(Menu::getId, parentIds) ); parentIds.clear(); for (Menu menu : menus) { if (menu.getParent() != 0L) { parentIds.add(menu.getParent()); menuIds.add(menu.getParent()); } } } Long roleId = data.getLong("id"); // 先删除操作 roleMenuMapper.deletePhysical("role_menu", new QueryWrapper().lambda().eq(RoleMenu::getRoleId, roleId)); // 在重新插入 List roleMenus = new ArrayList<>(); for (Long menuId : menuIds) { RoleMenu rm = new RoleMenu(); rm.setRoleId(roleId); rm.setMenuId(menuId); roleMenus.add(rm); } roleMenuMapper.insert(roleMenus); } } @Override public void beforeDelete(DynamicEntity data) { } @Override public void updateBatch(List data) { } @Override public void afterDelete(DynamicEntity data) { } @Override public void beforeDelete(List data) { } @Override public void afterDelete(List data) { } } ``` DataHook中提供了各个阶段的钩子操作,例如查询前、查询后、修改器、修改后等操作,DataHook接收一个泛型参数,参数为当前数据对应的数据对象实体类。可以直接使用其对应的baseMapper操作数据库 #### 智能 ORM 增强 系统重通过重写改造mybatis的AbstractWrapper类,实现了AbstractJoinWrapper,从而实现了自定义的join操作,使得mybaits-plus也支持join操作,提供了QueryJoinWrapper和LambdaQueryJoinWrapper两种形式的操作, 支持常见的left join、inner join、right join操作,写法简便易懂,支持mybatis-plus原有的where条件,以及orderBy、groupBy、having等操作。 ```java // 查询司机分页 @GetMapping("/driver/page") public R findOrderByDriver(Page page, Orders orders) { return R.ok(baseMapper.selectJoin(page, new QueryJoinWrapper<>(Orders.class) .lambda() .select(Orders::getOrderNo, Orders::getCreateTime, Orders::getSuplName, Orders::getDemandName, Orders::getReciverName, Orders::getConcatPhone, Orders::getAddress, Orders::getNeedTime, Orders::getDistance) .select(DeliveryTasks.class, DeliveryTasks::getStatus, DeliveryTasks::getId) .innerJoin(DeliveryTasks.class, DeliveryTasks::getOrderId, Orders::getId) .eq(DeliveryTasks::getDriverId, UserHelper.details().getTenantId()) )); } @Override public Page queryPage(HtPage page, Suppliers data, Wrapper wrapper) { return baseMapper.selectJoin(page, new QueryJoinWrapper<>(Suppliers.class) .lambda() .select(Suppliers::getId, Suppliers::getSuplName, Suppliers::getCreateTime) .select(CompanySuppliers.class, CompanySuppliers::getRemark, CompanySuppliers::getContactName, CompanySuppliers::getContactPhone, CompanySuppliers::getLevel, CompanySuppliers::getStatus) .innerJoin(CompanySuppliers.class, CompanySuppliers::getSupplierId, Suppliers::getId) .and(StringUtils.isNotBlank(data.getSearchKey()), w -> w .like(Suppliers::getSuplName, data.getSearchKey()) .or() .like(CompanySuppliers::getContactName, data.getSearchKey()) .or() .like(CompanySuppliers::getContactPhone, data.getSearchKey()) ) .eq(CompanySuppliers::getTenantId, UserHelper.details().getTenantId()) .orderByDesc(Suppliers::getCreateTime)); } ``` 以上示例代码中,查询司机分页,使用了join操作,查询了司机的订单信息,以及司机的供应商信息,并返回给前端。他会自动构建出一个Join操作。 #### 一对多、一对一的配置 项目中支持一对多、一对一的配置,仅仅只需要一个注解即可,在查询数据的时候会自动将一对多、一对一的数据关联起来,并返回给前端,进一步简化数据处理,提升开发效率。暂不支持多对多、多对一的操作。 1. 使用方法 ```java /** * 订单(Orders)表实体类 * * @author lgsh10086 * @since 2025-10-19 10:45:49 */ @Getter @Setter @SuppressWarnings("serial") @Schema(title = "订单", name = "orders") @TableName("orders") public class Orders extends AbstractEntity { /** * 订单编号 * tableName: order_no * entityName: orderNo */ @Schema(name = "orderNo", description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED) @TableField("order_no") private String orderNo; // ---- 省略其他属性 // 一对多配置,根据订单ID查询订单明细 @TableField(exist = false) @OneToMany(joinColumn = "order_id", mainColumn = "id") private List items; // 一对一配置,根据订单查询配送信息 @Schema(name = "tasks", description = "配送信息 发货信息") @TableField(exist = false) @OneToOne( joinColumn = "order_id", mainColumn = "id", selectColumns = {"driver_id", "loading_image_url"} ) private DeliveryTasks tasks; @Schema(name = "receivingRecords", description = "收货信息") @TableField(exist = false) @OneToOne( joinColumn = "order_id", mainColumn = "id", selectColumns = {"receiving_status", "receiving_image_urls", "reason"} ) private ReceivingRecords receivingRecords; // 一对一配置,支持中间表 @Schema(name = "drivers", description = "司机信息") @TableField(exist = false) @OneToOne( mainColumn = "id", middleTable = "delivery_tasks", middleMainColumn = "order_id", middleTargetColumn = "driver_id", joinColumn = "id", selectColumns = {"driver_name", "phone", "avatar"} ) private Drivers drivers; // 一对多配置,根据司机ID查询车辆信息,可以指定查询的字段 @TableField(exist = false) @Schema(name = "vhls", description = "车辆信息") @OneToMany( joinEntity = Vhls.class, joinTable = "vhls", joinColumn = "driver_id", mainColumn = "id", selectColumns = {"vhl_img", "driving_license_front", "driving_license_back", "license_plate", "vin", "engine_no", "vhl_brand", "vhl_model", "vhl_color", "tong", "remark"} ) private List vhls; public BigDecimal getTotalAmount() { if (null != totalAmount) { return totalAmount; } return BigDecimal.ZERO; } } ``` 一对一、一对多均支持配置中间表,只需要在实体属性中添加对应注解即可,在查询返回数据时会自动查询对应的数据并返回前端。此功能仅在查看详情时生效 #### 多级缓存架构 自研 **Caffeine + Redis** 二级缓存框架,支持: - 📦 本地缓存(Caffeine):微秒级响应 - ☁️ 分布式缓存(Redis):集群共享 - 🔄 缓存一致性:基于 Redis Pub/Sub 的实时同步 - ⏰ 智能过期:TTL + 懒加载策略 #### 国密安全体系 - 🔐 **数据传输加密**:SM2/SM3/SM4 国密算法 - 🔑 **动态密钥管理**:每次刷新页面随机生成密钥 - 🛡️ **双向认证**:客户端 + 服务端双重加密验证 - ✅ **等保合规**:满足国家信息安全等级保护要求 ### 🎨 前端核心能力 前端使用vue3+typescript开发,与后端数据交互使用axios,基于vxe-ui为页面组件模板开发页面。与后端全部采用国密算法进行数据传输,确保数据安全。前端提供了仅供开发人员使用的几个配置页面: 1. 菜单管理,用于配置页面路由导航与相关按钮,便于后续的权限配置 2. 值域,此为数据选项,如果一个属性有多个值的时候,需要在这里配置,便于后续在配置页面的时候需要用到,例如下拉框、多选按钮等 3. 表结构设计器,这里提供了可视化的表结构管理,可以动态的添加维护表字段、索引、表关联等 4. SQL设计器,如果常用的简单sql无法满足数据要求,可以在这里动态的配置sql,免去了自己手写导致的语法相关问题 5. 页面设计器,用于动态配置生成功能页面,实现零代码或低代码效果。 ## 菜单管理 菜单管理,用于配置页面路由导航与相关按钮,便于后续的权限配置 ![img.png](imgs/img.png) 添加导航菜单 ![img.png](imgs/add-menu.png) ## 值域 值域,此为数据选项,如果一个属性有多个值的时候,需要在这里配置,便于后续在配置页面的时候需要用到,例如下拉框、多选按钮等, 如果选项来源于数据库中的数据时,也支持配置查询sql ![img.png](imgs/value-domain.png) 值域详情,详情下方列表即为值域的选项 ![img.png](imgs/value-domain-detail.png) ## 表结构设计器 表结构设计器,用于动态的添加维护表字段、索引、表关联等 表结构列表 ![img.png](imgs/table-list.png) 添加表信息 ![img.png](imgs/add-table.png) 表详情 ![img.png](imgs/table-detail.png) 直接根据表结构生成页面 ![img.png](imgs/build-app.png) ## SQL设计器 图形化配置 SQL,告别手写 SQL: - 📝 **可视化编辑器**:拖拽式 SQL 构建 - 🔍 **智能提示**:表名、字段名自动联想 - 🧪 **在线调试**:即配即测,实时预览结果 ![img.png](imgs/sql-list.png) ![img.png](imgs/build-sql.png) ### 🎨 前端核心能力 基于 **VXE-Table** + **VXE-UI** 打造的企业级低代码平台: 页面设计器,用于动态组装所需的页面,支持构建表格、表单甚至图表(此功能待完善)等复杂页面 - 🌳 **树形结构配置**:直观的组件层级管理 - 🎭 **组件化开发**:50+ 内置组件,支持自定义扩展 - 📱 **响应式布局**:自适应 PC/移动端 - 🎨 **主题定制**:支持多套 UI 主题切换 ![img.png](imgs/app-list.png) 页面设计 ![img.png](imgs/page-design.png) #### 4. 访问系统 - 前端地址:http://localhost:3333 - 后端接口:http://localhost:8888/plat - 接口文档:http://localhost:8888/plat/doc.html --- ## 📚 文档 ### 核心功能文档 - [📘 平台配置说明](#平台配置) - [🔌 钩子函数使用指南](#钩子函数) - [🔗 Join 查询详解](#join-操作) - [📊 一对多/一对一配置](#一对多一对一的配置) - [🎨 前端配置手册](#前端配置) ### 视频教程 > 🎬 视频教程正在制作中,敬请期待... ### API 文档 系统集成了 **Knife4j** 接口文档,启动后可访问: - Knife4j: `http://localhost:8888/plat/doc.html` --- ## 🎯 在线演示 > 💡 演示环境部署中,预计上线时间:2026 年 Q2 演示账号(开放后提供): - 管理员:admin / admin --- #### 待完善的功能 1. 角色授权菜单功能 2. 图表数据功能 3. 配置式的导入导出、pdf等功能 4. Bpm流程引擎功能 #### 启动命令 项目使用maven打包构建,业务模块在server模块中,admin模块是后台管理模块 ``` java native-image -jar admin.jar ``` 此项目可用于学习,也可用于企业项目落地,作为一个快速开发平台,能极大的减少开发时间,提高开发效率。项目中还有许多待完善的地方,例如bpm流程引擎、导入导出等功能。如果你觉得此项目还不错,能给你带来一些帮助,请用你发财的小手帮忙点一个star,或者你对此项目感兴趣,也可以加入一起维护开发 ### 联系作者 微信:lgsh_10086 邮箱:615917296@qq.com