nbsaas-boot代码生成器是一种用于快速生成Spring Boot项目代码的工具。其设计原理主要包括以下几个方面:
1. 模板引擎:
代码生成器使用模板引擎定义和生成代码。模板引擎允许开发人员创建带有占位符的代码模板,这些占位符将在生成代码时被具体内容替代。nbsaas-boot的模板引擎使用的是FreeMarker。
{主工程} {主工程}.template {主工程}.template.hibernate #hibernate代码模板目录 {主工程}.template.jpa #jpa代码模板目录 {主工程}.template.mybatis-plus #mybatis-plus代码模板目录 {主工程}.template.vue3 #vue3 代码模板目录
git代码地址 https://gitee.com/cng1985/nbsaas-boot/tree/main/code-generator/src/main/resources/template
2. 配置文件:
用户通过配置文件或界面提供生成规则、模板路径、输出路径等信息。这包括数据库连接信息、表名、字段信息等。
projectName: ad #多项目的时候模块名 multiple: true #是否多模块项目 templateDir: /template/vue #代码模块文件夹 entityPackage: com.nbsaas.boot.generator.entity entities: - Ad outputPath: E:codesvue3 bsaas-life-admin #项目根目录 basePackage: com.nbsaas.boot #基础包名
3. 元数据解析:
通过解析Java注解,代码生成器提取实体类的元数据信息,例如表名、字段名、主键等。这为后续代码生成提供了必要的信息。
nbsaas-boot代码生成注解包括
@BeanExt
@CatalogClass
@ComposeView
@CreateByUser
@FieldConvert
@FieldName
@FormAnnotation
@FormExtField
@FormField
@LbsClass
@NoHandle
@NoResponse
@NoSimple
@PermissionClass
@PermissionDataClass
@SearchBean
@SearchItem
@TenantPermissionClass
@UniqueField
@VersionClass
@Dict @DictItem @DictKey
@StoreStateBean
4. 代码生成:
根据模板和元数据,代码生成器生成具体的源代码文件。在生成过程中,模板中的占位符被实际的元数据和字段信息替代。
5. 代码结构:
生成器按照一定的目录结构组织生成的代码,以符合项目规范。这涉及将实体类、Api层、Resource层等组织在特定的包路径下。
Api模块结构规范
com.{公司域名}.{主工程}.{子工程} com.{公司域名}.{主工程}.{子工程}.api.apis com.{公司域名}.{主工程}.{子工程}.api.domain.enums com.{公司域名}.{主工程}.{子工程}.api.domain.request com.{公司域名}.{主工程}.{子工程}.api.domain.response com.{公司域名}.{主工程}.{子工程}.api.domain.simple com.{公司域名}.{主工程}.{子工程}.ext.apis com.{公司域名}.{主工程}.{子工程}.ext.domain.enums com.{公司域名}.{主工程}.{子工程}.ext.domain.request com.{公司域名}.{主工程}.{子工程}.ext.domain.response com.{公司域名}.{主工程}.{子工程}.ext.domain.simple
Resource模块结构规范
com.{公司域名}.{主工程}.{子工程} com.{公司域名}.{主工程}.{子工程}.data.entity com.{公司域名}.{主工程}.{子工程}.data.repository com.{公司域名}.{主工程}.{子工程}.rest.conver com.{公司域名}.{主工程}.{子工程}.rest.resource com.{公司域名}.{主工程}.{子工程}.ext.conver com.{公司域名}.{主工程}.{子工程}.ext.resource
6. 定制化选项
代码生成器允许用户定义自定义模板或修改现有模板,以适应不同项目和团队的需求。
7.部分核心代码
public class ApiCommand extends BaseCommand { @Override public ResponseObject<?> handle(InputRequestObject context) { Config config = inputRequestObject.getConfig(); makeCode("Api", "."+config.getModuleName()+".api.apis"); return ResponseObject.success(); } @Override public String outPath() { Config config = inputRequestObject.getConfig(); if (config.getMultiple()) { return config.getOutputPath() + "\apis\[[]]-api".replace("[[]]", config.getProjectName()); } else { return config.getOutputPath(); } } }
处理元数据提取
public interface BeanHandle { void handle(Class<?> object, FormBean formBean); }
通过Reflections实例化BeanHandle 接口,这样可以动态增加元数据处理功能。
Reflections reflections = new Reflections("com.nbsaas.boot.generator.handle"); Set<Class<? extends BeanHandle>> handleList = reflections.getSubTypesOf(BeanHandle.class); for (Class<? extends BeanHandle> handle : handleList) { if (Modifier.isAbstract(handle.getModifiers())) { continue; } try { BeanHandle beanHandle = handle.newInstance(); beanHandle.handle(object, formBean); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } }
例子
元数据实体代码
@Data @FormAnnotation(title = "商品管理", model = "商品", menu = "1,164,165") @Entity @Table(name = "bs_product") public class Product extends AbstractEntity { public static Product fromId(Long id) { Product result = new Product(); result.setId(id); return result; } @FormField(title = "商品编码", grid = true, col = 12) private String barCode; @SearchItem(label = "商家", name = "shop", key = "shop.id", operator = Operator.eq, classType = Long.class,show = false) @Comment("商家id") @JoinColumn(name = "shop_id") @FieldConvert @FieldName @ManyToOne(fetch = FetchType.LAZY) private Shop shop; @FormField(title = "商品分组",grid = true, col = 12) @FieldName @FieldConvert @ManyToOne(fetch = FetchType.LAZY) private ProductGroup productGroup; @Comment("商品状态 1上架 2下架") @Dict(items = { @DictItem(value = 1, label = "上架"), @DictItem(value = 2, label = "下架") }) private Integer state; @FormField(title = "商品主图", sortNum = "2", grid = false, col = 12) private String logo; @FormField(title = "商品缩略图", sortNum = "2", grid = false, col = 12) private String thumbnail; @SearchItem(label = "商品名称", name = "name") @FormField(title = "商品名称", sortNum = "2", grid = true, col = 12) private String name; @FormField(title = "商品简介", sortNum = "111", grid = false, col = 12, type = InputType.textarea) private String summary; @Column(length = 65538) private String note; @FormField(title = "库存", sortNum = "2", grid = true, col = 12) private Long stockNum; @FormField(title = "即时库存", sortNum = "2", grid = true, col = 12) private Long realStock; //更新库存日期 private Date stockDate; @FormField(title = "价格", sortNum = "2", grid = true, col = 12, sort = true) private BigDecimal price; @FormField(title = "餐盒费", sortNum = "2", grid = true, col = 12) private BigDecimal mealFee; @FormField(title = "是否开启规格", sortNum = "2", col = 12) private Boolean skuEnable; @FormField(title = "折扣", sortNum = "2", col = 12) private BigDecimal discount; @Type(type = "io.hypersistence.utils.hibernate.type.json.JsonType") @Column(columnDefinition = "json") private List<Spec> specs; private BigDecimal minPrice; private BigDecimal maxPrice; @SearchItem(label = "storeState", name = "storeState",classType = StoreState.class,operator = Operator.eq) private StoreState storeState; }
生成代码
package com.nbsaas.boot; import com.nbsaas.boot.command.ControllerShopCommand; import com.nbsaas.boot.command.MapperPackageCommand; import com.nbsaas.boot.command.MapperXmlCommand; import com.nbsaas.boot.generator.GeneratorApp; import com.nbsaas.boot.generator.beans.FormBean; import com.nbsaas.boot.generator.beans.FormBeanConvert; import com.nbsaas.boot.generator.command.common.*; import com.nbsaas.boot.generator.command.jpa.RepositoryCommand; import com.nbsaas.boot.generator.config.Config; import com.nbsaas.boot.generator.context.InputRequestObject; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.sql.SQLException; import java.util.List; /** * Hello world! */ public class App { public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException { makeCodes("config/shop.yml"); makeCodes("config/shopArticle.yml"); makeCodes("config/talk.yml"); makeCodes("config/ad.yml"); makeCodes("config/product.yml"); makeCodes("config/customer.yml"); makeCodes("config/promote.yml"); makeCodes("config/common.yml"); makeCodes("config/order.yml"); makeCodes("config/store.yml"); makeCodes("config/room.yml"); } private static void makeCodes(String configFile) throws IOException, SQLException, ClassNotFoundException { Yaml yaml = new Yaml(); String baseFile = GeneratorApp.class.getClassLoader().getResource("").getFile(); File f = new File(baseFile + configFile); //读入文件 Config config = yaml.loadAs(Files.newInputStream(f.toPath()), Config.class); config.setBase(baseFile); List<String> tables = config.getEntities(); if (tables == null) { return; } for (String table : tables) { FormBean formBean = new FormBeanConvert().convertClass(Class.forName(config.getEntityPackage() + "." + table)); InputRequestObject context = new InputRequestObject(); context.setConfig(config); context.setFormBean(formBean); new DomainCommand() .after(new ApiCommand()) .after(new ConvertCommand()) .after(new ControllerAdminCommand()) .after(new ControllerFront2Command()) .after(new RestCommand()) .after(new ExtApiCommand()) .after(new RepositoryCommand()) .after(new ExtResourceCommand()) .after(new MapperXmlCommand()) .after(new MapperPackageCommand()) .after(new ControllerShopCommand()) .after(new FieldCommand()) .after(new FinishCommand()).execute(context); } } }