nbsaas-boot代码生成器是一种用于快速生成Spring Boot项目代码的工具。其设计原理主要包括以下几个方面:
代码生成器使用模板引擎定义和生成代码。模板引擎允许开发人员创建带有占位符的代码模板,这些占位符将在生成代码时被具体内容替代。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
用户通过配置文件或界面提供生成规则、模板路径、输出路径等信息。这包括数据库连接信息、表名、字段信息等。
projectName: ad #多项目的时候模块名
multiple: true #是否多模块项目
templateDir: /template/vue #代码模块文件夹
entityPackage: com.nbsaas.boot.generator.entity
entities:
- Ad
outputPath: E:\codes\vue3\nbsaas-life-admin #项目根目录
basePackage: com.nbsaas.boot #基础包名
通过解析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
根据模板和元数据,代码生成器生成具体的源代码文件。在生成过程中,模板中的占位符被实际的元数据和字段信息替代。
生成器按照一定的目录结构组织生成的代码,以符合项目规范。这涉及将实体类、Api层、Resource层等组织在特定的包路径下。
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
com.{公司域名}.{主工程}.{子工程}
com.{公司域名}.{主工程}.{子工程}.data.entity
com.{公司域名}.{主工程}.{子工程}.data.repository
com.{公司域名}.{主工程}.{子工程}.rest.conver
com.{公司域名}.{主工程}.{子工程}.rest.resource
com.{公司域名}.{主工程}.{子工程}.ext.conver
com.{公司域名}.{主工程}.{子工程}.ext.resource
代码生成器允许用户定义自定义模板或修改现有模板,以适应不同项目和团队的需求。
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);
}
}
}