bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
573 lines (513 loc) • 22.3 kB
text/typescript
import * as fs from 'fs';
import * as path from 'path';
/**
* 数据库列定义接口
*/
export interface ColumnDefinition {
name: string;
type: string;
comment: string;
primaryKey: string;
}
/**
* 模块定义接口
*/
export interface ModuleDefinition {
moduleName: string;
tableName: string;
tableComment: string;
columns: ColumnDefinition[];
}
/**
* 代码生成配置接口
*/
export interface CodeGenConfig {
jsonPath: string;
outputBaseDir: string;
}
// 跳过的字段名
const SKIP_FIELDS = new Set<string>([
'id', 'creator', 'create_time', 'updater', 'update_time', 'version', 'deleted', 'industry', 'tenant_id'
]);
/**
* TypeScript 版本的 CodeGen 类
* 用于根据 JSON 配置生成 Java 企业级应用的各种文件
* @author chen
*/
export class TemplateCodeGen {
/**
* 将下划线命名转为 camelCase
*/
private static toCamelCase(input: string): string {
return input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* 首字母大写
*/
private static toPascalCase(input: string): string {
const camel = this.toCamelCase(input);
return camel.charAt(0).toUpperCase() + camel.substring(1);
}
/**
* 将驼峰命名转为小写下划线
*/
private static toSnakeCase(input: string): string {
return input
.replace(/([a-z])([A-Z])/g, '$1_$2') // 小写字母后跟大写字母
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') // 大写字母后跟大写字母+小写字母
.toLowerCase();
}
/**
* 类型映射
*/
private static convertType(type: string): string {
const typeMap: Record<string, string> = {
'String': 'String',
'Long': 'Long',
'Integer': 'Integer',
'BigDecimal': 'BigDecimal',
'LocalDateTime': 'LocalDateTime',
'Date': 'Date'
};
return typeMap[type] || 'String';
}
/**
* 示例 main 方法
*/
public static async main(): Promise<void> {
const config: CodeGenConfig = {
jsonPath: 'path/to/your/input.json',
outputBaseDir: 'path/to/output/java'
};
await this.generateAllFiles(config.jsonPath, config.outputBaseDir);
}
/**
* 生成所有文件
*/
public static async generateAllFiles(jsonPath: string, outputBaseDir: string): Promise<void> {
try {
// 验证输入参数
if (!jsonPath || !outputBaseDir) {
throw new Error('jsonPath and outputBaseDir are required');
}
// 检查文件是否存在
await fs.promises.access(jsonPath, fs.constants.F_OK);
const jsonContent = await fs.promises.readFile(jsonPath, 'utf-8');
const modules = JSON.parse(jsonContent) as ModuleDefinition[];
if (!Array.isArray(modules) || modules.length === 0) {
throw new Error('Invalid JSON format: expected array of modules');
}
for (const module of modules) {
// 处理模块名
module.moduleName = this.processModuleName(module.moduleName);
await this.generateModuleFiles(module, outputBaseDir + "/" + module.moduleName);
}
} catch (error) {
console.error('Error generating files:', error);
throw error;
}
}
/**
* 为单个模块生成文件
*/
private static async generateModuleFiles(module: ModuleDefinition, outputBaseDir: string): Promise<void> {
const { moduleName, tableName, tableComment, columns } = module;
// 生成类名
const names = this.generateClassNames(tableName);
// 生成目录结构
const directories = this.generateDirectories(outputBaseDir);
// 创建所有目录
await this.createDirectories(directories);
// 生成所有文件
await this.generateAllClassFiles(directories, names, tableComment, columns, moduleName, outputBaseDir);
}
/**
* 处理模块名,如果不包含'biz'则添加
*/
private static processModuleName(moduleName: string): string {
if (!moduleName.includes('biz')) {
const lastDashIndex = moduleName.lastIndexOf('-');
if (lastDashIndex !== -1) {
return moduleName.slice(0, lastDashIndex) + '-biz-' + moduleName.slice(lastDashIndex + 1);
}
}
return moduleName;
}
/**
* 生成所有类名
*/
private static generateClassNames(tableName: string) {
const simpleName = this.toPascalCase(tableName);
return {
entityName: simpleName + 'Entity',
simpleName,
voName: 'Client' + simpleName + 'VO',
convertName: 'Client' + simpleName + 'Convert',
daoName: simpleName + 'Dao',
queryName: 'Client' + simpleName + 'Query',
serviceName: 'Client' + simpleName + 'Service',
serviceImplName: 'Client' + simpleName + 'ServiceImpl',
controllerName: 'Client' + simpleName + 'Controller'
};
}
/**
* 生成目录结构
*/
private static generateDirectories(outputBaseDir: string) {
const basePackage = 'src/main/java/com/bytefun/biz';
return {
entityDir: path.join(outputBaseDir, basePackage, 'entity'),
voDir: path.join(outputBaseDir, basePackage, 'vo/client'),
convertDir: path.join(outputBaseDir, basePackage, 'convert/client'),
daoDir: path.join(outputBaseDir, basePackage, 'dao'),
serviceDir: path.join(outputBaseDir, basePackage, 'service/client'),
serviceImplDir: path.join(outputBaseDir, basePackage, 'service/client/impl'),
configDir: path.join(outputBaseDir, basePackage, 'config'),
controllerDir: path.join(outputBaseDir, basePackage, 'controller/client'),
queryDir: path.join(outputBaseDir, basePackage, 'query/client'),
xmlDir: path.join(outputBaseDir, 'src/main/resources/mapper')
};
}
/**
* 创建所有目录
*/
private static async createDirectories(directories: Record<string, string>): Promise<void> {
const dirPromises = Object.values(directories).map(dir =>
fs.promises.mkdir(dir, { recursive: true })
);
await Promise.all(dirPromises);
}
/**
* 生成所有类文件
*/
private static async generateAllClassFiles(
directories: Record<string, string>,
names: ReturnType<typeof TemplateCodeGen.generateClassNames>,
tableComment: string,
columns: ColumnDefinition[],
moduleNameNew: string,
outputBaseDir: string
): Promise<void> {
const fileGenerationTasks = [
this.writeToFile(directories.entityDir, `${names.entityName}.java`, this.buildEntityClass(names.entityName, tableComment, columns)),
this.writeToFile(directories.voDir, `${names.voName}.java`, this.buildVOClass(names.voName, tableComment, columns)),
this.writeToFile(directories.convertDir, `${names.convertName}.java`, this.buildConvertClass(names.simpleName, tableComment)),
this.writeToFile(directories.daoDir, `${names.daoName}.java`, this.buildDaoClass(names.simpleName, tableComment)),
this.writeToFile(directories.xmlDir, `${names.simpleName}Dao.xml`, this.buildXmlMapper(names.simpleName, columns)),
this.writeToFile(directories.serviceDir, `${names.serviceName}.java`, this.buildServiceInterface(names.simpleName, tableComment)),
this.writeToFile(directories.serviceImplDir, `${names.serviceImplName}.java`, this.buildServiceImpl(names.simpleName, tableComment)),
this.writeToFile(directories.configDir, 'ModuleServiceFactoryConfig.java', this.buildServiceFactory(names.simpleName, names.serviceName)),
this.writeToFile(directories.queryDir, `${names.queryName}.java`, this.buildQueryClass(names.simpleName, tableComment)),
this.writeToFile(directories.controllerDir, `${names.controllerName}.java`, this.buildController(names.simpleName, tableComment)),
this.writeToFile(outputBaseDir, 'pom.xml', this.buildPomXml(moduleNameNew, moduleNameNew))
];
await Promise.all(fileGenerationTasks);
}
/**
* 写入文件
*/
private static async writeToFile(baseDir: string, fileName: string, content: string): Promise<void> {
const filePath = path.join(baseDir, fileName);
const parentDir = path.dirname(filePath);
// 检查父目录是否存在,不存在则创建
try {
await fs.promises.access(parentDir, fs.constants.F_OK);
} catch {
// 父目录不存在,创建它
await fs.promises.mkdir(parentDir, { recursive: true });
}
// 检查文件是否已存在,如果存在则跳过写入
try {
await fs.promises.access(filePath, fs.constants.F_OK);
// 文件已存在,跳过写入
return;
} catch {
// 文件不存在,继续写入
}
// 写入文件,使用 UTF-8 编码
await fs.promises.writeFile(filePath, content, 'utf-8');
}
/**
* 构建 Entity 类
*/
private static buildEntityClass(tableName: string, comment: string, columns: ColumnDefinition[]): string {
const className = this.toPascalCase(tableName);
const dbTableName = this.toSnakeCase(tableName.substring(0, tableName.length - 6));
const fieldDefinitions = columns
.filter(col => !SKIP_FIELDS.has(col.name.toLowerCase()))
.map(col => {
const { name, type, comment: colComment, primaryKey } = col;
const convertedType = this.convertType(type);
const isPrimaryKey = primaryKey === 'true';
const annotation = isPrimaryKey
? ' @TableId(type = IdType.AUTO)'
: ` @TableField("${name}")`;
return ` /** ${colComment} */
${annotation}
private ${convertedType} ${this.toCamelCase(name)};
`;
})
.join('\n');
return `package com.bytefun.biz.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.util.*;
import com.bytefun.framework.common.entity.BaseEntity;
/** ${comment} */
@EqualsAndHashCode(callSuper = false)
@Data
@TableName("${dbTableName}")
public class ${className} extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
${fieldDefinitions}
}
`;
}
/**
* 构建 VO 类
*/
private static buildVOClass(className: string, comment: string, columns: ColumnDefinition[]): string {
const fieldDefinitions = columns
.filter(col => !SKIP_FIELDS.has(col.name.toLowerCase()))
.map(col => {
const { name: fieldName, type: fieldType, comment: fieldComment } = col;
const type = this.convertType(fieldType);
const dateFormatAnnotation = type === 'Date'
? '\n @JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)'
: '';
return ` @Schema(description = "${fieldComment}")${dateFormatAnnotation}
private ${type} ${this.toCamelCase(fieldName)};
`;
})
.join('\n');
return `package com.bytefun.biz.vo.client;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
import com.bytefun.framework.common.utils.DateUtils;
/** ${comment} */
@Data
@Schema(description = "${comment}")
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "业务类型")
private Integer businessType;
${fieldDefinitions}
}
`;
}
/**
* 构建 Convert 类
*/
private static buildConvertClass(simpleName: string, comment: string): string {
return `package com.bytefun.biz.convert.client;\n\n` +
`import com.bytefun.biz.entity.${simpleName}Entity;\n` +
`import com.bytefun.biz.vo.client.Client${simpleName}VO;\n` +
`import org.mapstruct.Mapper;\n` +
`import org.mapstruct.factory.Mappers;\n` +
`import java.util.List;\n\n` +
`/** ${comment} */\n` +
`@Mapper\n` +
`public interface Client${simpleName}Convert {\n` +
` Client${simpleName}Convert INSTANCE = Mappers.getMapper(Client${simpleName}Convert.class);\n` +
` ${simpleName}Entity convert(Client${simpleName}VO vo);\n` +
` Client${simpleName}VO convert(${simpleName}Entity entity);\n` +
` List<Client${simpleName}VO> convertList(List<${simpleName}Entity> list);\n` +
`}\n`;
}
/**
* 构建 Dao 类
*/
private static buildDaoClass(simpleName: string, comment: string): string {
return `package com.bytefun.biz.dao;\n\n` +
`import com.bytefun.framework.common.dao.BaseDao;\n` +
`import com.bytefun.biz.entity.${simpleName}Entity;\n` +
`import org.apache.ibatis.annotations.Mapper;\n\n` +
`/** ${comment} */\n` +
`@Mapper\n` +
`public interface ${simpleName}Dao extends BaseDao<${simpleName}Entity> {\n` +
`}\n`;
}
/**
* 构建 XML Mapper
*/
private static buildXmlMapper(simpleName: string, columns: ColumnDefinition[]): string {
const resultMappings = columns
.filter(col => col.name.toLowerCase() !== 'id')
.map(col => ` <result property="${this.toCamelCase(col.name)}" column="${col.name}"/>`)
.join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bytefun.biz.dao.${simpleName}Dao">
<resultMap id="exampleMap" type="com.bytefun.biz.entity.${simpleName}Entity">
<id property="id" column="id"/>
${resultMappings}
</resultMap>
</mapper>
`;
}
/**
* 构建 Service 接口
*/
private static buildServiceInterface(simpleName: string, comment: string): string {
return `package com.bytefun.biz.service.client;\n\n` +
`import com.bytefun.framework.common.page.PageResult;\n` +
`import java.math.BigDecimal;\n` +
`import com.bytefun.framework.common.service.BaseService;\n` +
`import com.bytefun.biz.entity.${simpleName}Entity;\n\n` +
`/** ${comment} */\n` +
`public interface Client${simpleName}Service extends BaseService<${simpleName}Entity> {\n` +
`}\n`;
}
/**
* 构建 Service 实现类
*/
private static buildServiceImpl(simpleName: string, comment: string): string {
return `package com.bytefun.biz.service.client.impl;\n\n` +
`import lombok.AllArgsConstructor;\n` +
`import java.math.BigDecimal;\n` +
`import com.bytefun.framework.common.service.impl.BaseServiceImpl;\n` +
`import com.bytefun.biz.dao.${simpleName}Dao;\n` +
`import com.bytefun.biz.entity.${simpleName}Entity;\n` +
`import com.bytefun.biz.service.client.Client${simpleName}Service;\n` +
`import org.springframework.context.annotation.Primary;\n` +
`import org.springframework.stereotype.Service;\n\n` +
`/** ${comment} */\n` +
`@Primary\n` +
`@Service\n` +
`@AllArgsConstructor\n` +
`public class Client${simpleName}ServiceImpl extends BaseServiceImpl<${simpleName}Dao, ${simpleName}Entity> implements Client${simpleName}Service {\n` +
`}\n`;
}
/**
* 构建 Service 工厂配置
*/
private static buildServiceFactory(simpleName: string, serviceName: string): string {
return `package com.bytefun.biz.config;\n\n` +
`import com.bytefun.biz.service.client.${serviceName};\n` +
`import com.bytefun.framework.factory.ServiceFactory;\n` +
`import java.math.BigDecimal;\n` +
`import org.springframework.context.ApplicationContext;\n` +
`import org.springframework.context.annotation.Bean;\n` +
`import org.springframework.context.annotation.Configuration;\n\n` +
`/** Service工厂配置 */\n` +
`@Configuration\n` +
`public class ModuleServiceFactoryConfig {\n\n` +
` @Bean\n` +
` public ServiceFactory<${serviceName}> client${simpleName}ServiceFactory(ApplicationContext applicationContext) {\n` +
` return new ServiceFactory<>(applicationContext, ${serviceName}.class);\n` +
` }\n` +
`}\n`;
}
/**
* 构建 Query 类
*/
private static buildQueryClass(simpleName: string, comment: string): string {
return `package com.bytefun.biz.query.client;\n\n` +
`import com.bytefun.framework.common.query.Query;\n` +
`import io.swagger.v3.oas.annotations.media.Schema;\n` +
`import java.math.BigDecimal;\n` +
`import java.util.*;\n` +
`import lombok.Data;\n` +
`import lombok.EqualsAndHashCode;\n\n` +
`/** ${comment}查询 */\n` +
`@Data\n` +
`@EqualsAndHashCode(callSuper = false)\n` +
`@Schema(description = "${comment}查询")\n` +
`public class Client${simpleName}Query extends Query {\n` +
` @Schema(description = "业务类型")\n` +
` private Integer businessType;\n` +
`}\n`;
}
/**
* 构建 Controller 类
*/
private static buildController(simpleName: string, comment: string): string {
return `package com.bytefun.biz.controller.client;\n\n` +
`import com.bytefun.biz.convert.client.Client${simpleName}Convert;\n` +
`import com.bytefun.biz.query.client.Client${simpleName}Query;\n` +
`import com.bytefun.biz.service.client.Client${simpleName}Service;\n` +
`import com.bytefun.biz.vo.client.Client${simpleName}VO;\n` +
`import com.bytefun.framework.factory.ServiceFactory;\n` +
`import io.swagger.v3.oas.annotations.Operation;\n` +
`import io.swagger.v3.oas.annotations.tags.Tag;\n` +
`import lombok.AllArgsConstructor;\n` +
`import java.math.BigDecimal;\n` +
`import com.bytefun.framework.common.page.PageResult;\n` +
`import com.bytefun.framework.common.utils.Result;\n` +
`import com.bytefun.biz.entity.${simpleName}Entity;\n` +
`import org.springframework.web.bind.annotation.*;\n` +
`import javax.validation.Valid;\n` +
`import java.util.List;\n\n` +
`/** ${comment} */\n` +
`@RestController\n` +
// `@RequestMapping("client/biz/${simpleName.toLowerCase()}")\n` +
`@Tag(name="${comment}")\n` +
`@AllArgsConstructor\n` +
`public class Client${simpleName}Controller {\n\n` +
` private final Client${simpleName}Service client${simpleName}Service;\n` +
` private final ServiceFactory<Client${simpleName}Service> serviceFactory;\n\n` +
`}\n`;
}
/**
* 构建 POM XML 文件
*/
private static buildPomXml(moduleName: string, description: string): string {
return `<?xml version="1.0" encoding="UTF-8"?>\n` +
`<project xmlns="http://maven.apache.org/POM/4.0.0"\n` +
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n` +
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">\n` +
` <parent>\n` +
` <groupId>com.bytefun</groupId>\n` +
` <artifactId>bytefun-boot-biz-common</artifactId>\n` +
` <version>\${revision}</version>\n` +
` </parent>\n` +
` <modelVersion>4.0.0</modelVersion>\n` +
` <artifactId>${moduleName}</artifactId>\n` +
` <description>${description}</description>\n` +
` <packaging>jar</packaging>\n\n` +
` <dependencies>\n` +
` <dependency>\n` +
` <groupId>com.bytefun</groupId>\n` +
` <artifactId>bytefun-boot-framework</artifactId>\n` +
` <version>\${revision}</version>\n` +
` </dependency>\n` +
` </dependencies>\n` +
`</project>\n`;
}
}
// 导出独立的生成函数,方便外部调用
export const generateAllFiles = TemplateCodeGen.generateAllFiles.bind(TemplateCodeGen);
// 导出工具函数
export const utils = {
toCamelCase: (input: string) => input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
toPascalCase: (input: string) => {
const camel = input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
return camel.charAt(0).toUpperCase() + camel.substring(1);
},
convertType: (type: string) => {
const typeMap: Record<string, string> = {
'String': 'String',
'Long': 'Long',
'Integer': 'Integer',
'BigDecimal': 'BigDecimal',
'LocalDateTime': 'LocalDateTime',
'Date': 'Date'
};
return typeMap[type] || 'String';
}
};
// 默认导出
export default {
TemplateCodeGen,
generateAllFiles,
utils,
SKIP_FIELDS
};