二次开发指南
二次开发指南
本篇指导你如何在平台上进行二次开发,包括新增业务模块、新增设计器组件、自定义代码生成模板、接入 AI 模型。
先读架构
二次开发前建议先读 ,理解两条数据通路(静态实体通路 / 动态表通路)、请求链路、API 路径约定与多租户机制。
一、新增业务模块(后端 + 前端)
以「商品管理」为例,参照现有 ERP 模块(erp_product)的分层结构。平台静态实体通路遵循 Controller -> Service(Impl) -> Mapper -> Entity 标准分层,基于 MyBatis-Plus。
后端五件套
新建一个业务模块需在 backend/src/main/java/com/lowcode/ 下创建 5 个文件,包名按业务域归类(如 erp / hr)。
1. Entity 实体类
entity/erp/ErpProduct.java:
package com.lowcode.entity.erp;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lowcode.common.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("erp_product")
public class ErpProduct extends BaseEntity {
private String productCode;
private String productName;
private BigDecimal salePrice;
private String status;
// ... 其他字段
}关键点
- 继承
com.lowcode.common.BaseEntity,自动获得id/createTime/updateTime/createBy/updateBy/deleted/tenantId字段。 @TableName("表名")指定表名,@Data生成 getter/setter,@EqualsAndHashCode(callSuper = true)包含父类字段。- 字段名驼峰,MyBatis-Plus 自动映射蛇形列名(
map-underscore-to-camel-case: true)。 BaseEntity的deleted字段配合logic-delete-field: deleted自动逻辑删除。
2. Mapper 接口
mapper/erp/ErpProductMapper.java:
package com.lowcode.mapper.erp;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lowcode.entity.erp.ErpProduct;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ErpProductMapper extends BaseMapper<ErpProduct> {
}继承 BaseMapper<Entity> 即获得 insert / deleteById / updateById / selectById / selectList 等方法,无需写 SQL。平台无 XML Mapper。
3. Service 接口
service/erp/IErpProductService.java:
package com.lowcode.service.erp;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lowcode.entity.erp.ErpProduct;
public interface IErpProductService extends IService<ErpProduct> {
}继承 IService<Entity> 获得 save / removeById / updateById / getById / list / page 等方法。
4. ServiceImpl 实现类
service/impl/erp/ErpProductServiceImpl.java:
package com.lowcode.service.impl.erp;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lowcode.entity.erp.ErpProduct;
import com.lowcode.mapper.erp.ErpProductMapper;
import com.lowcode.service.erp.IErpProductService;
import org.springframework.stereotype.Service;
@Service
public class ErpProductServiceImpl
extends ServiceImpl<ErpProductMapper, ErpProduct>
implements IErpProductService {
}继承 ServiceImpl<Mapper, Entity> 即实现全部 IService 方法。有自定义业务逻辑时在此类加方法。
5. Controller 控制器
controller/erp/ErpProductController.java:
package com.lowcode.controller.erp;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lowcode.common.PageHelper;
import com.lowcode.common.Result;
import com.lowcode.entity.erp.ErpProduct;
import com.lowcode.service.erp.IErpProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/erp/product")
public class ErpProductController {
@Autowired
private IErpProductService productService;
@GetMapping("/page")
public Result<Page<ErpProduct>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) {
LambdaQueryWrapper<ErpProduct> wrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.isEmpty()) {
wrapper.like(ErpProduct::getProductName, keyword)
.or().like(ErpProduct::getProductCode, keyword);
}
wrapper.orderByDesc(ErpProduct::getCreateTime);
return Result.success(PageHelper.doPage(productService, wrapper, pageNum, pageSize));
}
@GetMapping("/{id}")
public Result<ErpProduct> getById(@PathVariable Long id) {
return Result.success(productService.getById(id));
}
@PostMapping
public Result<Void> add(@RequestBody ErpProduct product) {
productService.save(product);
return Result.success("新增成功");
}
@PutMapping
public Result<Void> update(@RequestBody ErpProduct product) {
productService.updateById(product);
return Result.success("修改成功");
}
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
productService.removeById(id);
return Result.success("删除成功");
}
}关键约定
@RequestMapping("/api/模块名")必须带/api前缀。- 返回值统一用
Result<T>包装。 - 分页用自研
PageHelper.doPage(service, wrapper, pageNum, pageSize),不要用 MyBatis-Plus 的PaginationInnerInterceptor(平台未引入mybatis-plus-jsqlparser依赖)。 LambdaQueryWrapper构造条件,TenantLineInnerInterceptor会自动追加tenant_id。
建表 SQL
新建实体对应的表,需包含 BaseEntity 字段:
CREATE TABLE erp_product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT DEFAULT 1,
product_code VARCHAR(50) NOT NULL,
product_name VARCHAR(200) NOT NULL,
sale_price DECIMAL(18,4),
status CHAR(1) DEFAULT '1',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(50),
update_by VARCHAR(50),
deleted TINYINT DEFAULT 0
);字段约定
所有业务表应包含:id(主键)、tenant_id(租户,默认 1)、create_time / update_time / create_by / update_by(BaseEntity 自动填充)、deleted(逻辑删除,0 正常 1 删除)。
前端三件套
1. API 模块
frontend/src/api/erp/sale.ts(参考现有写法):
import { request } from '@/utils/request'
// 分页查询
export const getProductPage = (params?: any) => {
return request.get<any>('/erp/product/page', { params })
}
// 详情
export const getProductDetail = (id: number) => {
return request.get<any>(`/erp/product/${id}`)
}
// 新增
export const createProduct = (data: any) => {
return request.post<any>('/erp/product', data)
}
// 修改
export const updateProduct = (data: any) => {
return request.put<any>('/erp/product', data)
}
// 删除
export const deleteProduct = (id: number) => {
return request.delete<any>(`/erp/product/${id}`)
}路径不要带 /api
request 的 baseURL 已是 /api,请求路径写 /erp/product/page 即可,不要写 /api/erp/product/page,否则双重前缀 404。详见 。
2. 页面视图
在 frontend/src/views/erp/ 下新建 product/index.vue,用 Element Plus 表格 + 表单实现列表与编辑。参考 frontend/src/views/erp/ 现有页面。
3. 路由配置
在 frontend/src/router/index.ts 的 Layout children 中添加:
{
path: 'erp/product',
name: 'ErpProduct',
component: () => import('@/views/erp/product/index.vue'),
meta: { title: '商品管理', requiresAuth: true }
}同时在系统「菜单管理」中配置对应菜单(sys_menu 表),分配权限标识(如 erp:product:list)。
验证
- 后端重启(无热重载,见 )。
- 前端 Vite HMR 自动刷新。
- 用
admin登录,访问新菜单,测试 CRUD。
二、新增设计器组件
低代码平台的表单设计器基于 @form-create/element-ui,列表 / 页面设计器为自研。新增设计器组件需关注:
表单设计器组件
表单设计器使用 @form-create/designer(版本 3.4.0)。新增自定义表单组件:
- 在
frontend/src/components/下新建组件,确保可被form-create注册。 - 在表单设计器配置中注册组件(参考
frontend/src/views/designer/或frontend/src/components/现有注册逻辑)。 - 后端表单 JSON 保存到
lc_form.form_json,运行时由RuntimeController渲染。
表设计器字段类型
lc_column_meta.html_type 控制字段在前端的渲染控件(input / select / radio / checkbox / datetime / image 等)。
字段配置白名单
新增字段类型时,TableDesigner.vue 中的三处白名单(字段类型列表、查询方式列表、HTML 控件列表)必须同步添加,否则新类型不会出现在设计器下拉中。LcColumnMeta 实体的 MyBatis-Plus 是全字段透传。详见项目记忆中的「表设计器字段配置白名单陷阱」。
三、自定义代码生成模板
平台提供应用导出功能,把低代码应用导出为独立可运行的 Spring Boot / 移动端工程。模板位于 backend/src/main/resources/templates/export/。
模板目录结构
templates/export/
├── admin-ui/ # 导出工程的前端(Vue3 管理后台)
├── biz-module/ # 业务模块后端模板
├── multi-module/ # 多模块 Maven 工程模板(_root / biz / bootstrap / system)
├── system-module/ # 系统模块后端模板(_resources / common / config / security / system)
└── uni-app/ # 移动端 uni-app 模板模板使用 FreeMarker(spring-boot-starter-freemarker,后缀 .ftl),由 ApplicationExportController 调用导出服务渲染。
修改模板注意事项
改模板需同步 3 处
修改 templates/export/ 下的模板时,需同步检查三处:
- 模板文件本身(
.ftl)。 - 导出服务中引用该模板的 Java 代码(
com.lowcode.export包下,如ApplicationExportController/AppExportService)。 - 导出工程的
pom.xml/ 依赖配置(若涉及依赖变更)。
AppExportService 每次导出会新建一个 FreeMarker Configuration 实例,因此改 target/classes 下的模板即生效,无需重启(开发时可直接改编译输出目录验证)。
模板变量
模板渲染时注入的主要变量(以 system-module 为例):
author-- 生成代码作者(generator.author,默认lowcode)packageName-- 包名(generator.package-name,默认com.lowcode)moduleName-- 模块名(generator.module-name,默认system)tablePrefix-- 表前缀(generator.table-prefix,默认sys_)- 表 / 字段元数据(来自
lc_table_meta/lc_column_meta)
配置见 application.yml:
generator:
author: lowcode
package-name: com.lowcode
module-name: system
table-prefix: sys_调试模板
- 改模板
.ftl文件。 - 若不想重启后端,可直接改
backend/target/classes/templates/export/下的编译产物验证(AppExportService每次新建 Configuration)。 - 调用
POST /api/application/{appId}/export-springboot触发导出,下载 zip 检查产物。
Lombok × JDK21 影响导出工程
导出的工程 pom 在 JDK 21 + Maven 下编译可能崩 TypeTag(与主工程同样问题)。建议导出工程也用 JDK 17 编译,或在 IDEA 中编译。详见 。
四、接入 AI 模型
平台内置 AI 助手功能,支持对话、生成 SQL、生成表单配置等。AI 模型配置存储在 sys_ai_model_config 表。
配置入口
通过「系统管理 -> AI 模型配置」菜单,或接口 /api/system/ai-model:
| 接口 | 说明 |
|---|---|
GET /api/system/ai-model/list | 模型配置列表 |
POST /api/system/ai-model | 新增模型配置 |
PUT /api/system/ai-model | 修改配置 |
PUT /api/system/ai-model/setDefault/{id} | 设为默认模型 |
POST /api/system/ai-model/test | 测试模型连通性 |
配置字段包括:模型类型、API Key、接口端点、模型名称、是否默认、启用状态等。
AI 能力接口
AiAssistantController(/api/ai)提供:
POST /api/ai/chat-- 通用对话POST /api/ai/form-designer-chat-- 表单设计器对话(SSE 流式响应,text/event-stream)POST /api/ai/generate/sql-- 根据自然语言生成建表 SQLPOST /api/ai/generate/form-config-- 生成表单配置POST /api/ai/generate/table-fields-- 生成表字段建议POST /api/ai/executeSql-- 执行 SQLPOST /api/ai/applyForm-- 应用 AI 生成的表单
接入新模型
- 在 AI 模型配置中新增一条记录,填写模型类型、API Key、端点。
- 设为默认模型(
PUT /api/system/ai-model/setDefault/{id})。 - 测试连通性(
POST /api/system/ai-model/test)。 - 在
AiAssistantController/ AI Service 中按模型类型适配请求格式(若模型接口与现有适配器不同,需扩展AiAssistantService的模型调用逻辑)。
SSE 流式
/api/ai/form-designer-chat 返回 text/event-stream,前端用 EventSource 或 fetch 流式接收,实现打字机效果。接入新模型时需保证模型接口支持流式输出(SSE)。
五、二次开发检查清单
- [ ] 后端:Controller 路径带
/api,返回Result<T>,分页用PageHelper.doPage - [ ] 后端:Entity 继承
BaseEntity,@TableName指定表名 - [ ] 后端:表含
tenant_id/create_time/update_time/create_by/update_by/deleted - [ ] 后端:敏感接口加
@PreAuthorize注解(当前方法级授权不完整) - [ ] 前端:API 路径不带
/api,用requestfrom@/utils/request - [ ] 前端:路由 + 菜单 + 权限标识三处同步配置
- [ ] 设计器:新增字段类型同步
TableDesigner.vue三处白名单 - [ ] 模板:改
templates/export/同步 3 处(模板 / 导出服务 / pom) - [ ] 安全:
jwt.secret改随机密钥,Redis 换自有实例 - [ ] 多租户:异步 / 线程池场景手动传递并清理
TenantContext
更多约定见 ,常见报错见 。
