UNPKG

bytefun

Version:

一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台

573 lines (513 loc) 22.3 kB
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 };