示例1
后端
src/main/java/com/weiyu/modules/system/dto/request/UserQueryRequest.java
package com.weiyu.modules.system.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; /** * 用户查询请求对象 */ @Schema(description = "用户查询请求对象") @Data @NoArgsConstructor public class UserQueryRequest { /** * 数据指令 */ @Schema(description = "数据指令", allowableValues = { "all", "valid", "invalid", "working", "depart", "retire", "accept-group-member", "accept-group-addable-member", "assay-group-member", "assay-group-addable-member", "assay-group-assayer", "assay-task-assayer", "test-report-type-preparer", "test-report-type-verifier", "test-report-type-issuer", "wspj-report-type-preparer", "wspj-report-type-verifier", "wspj-report-type-issuer", "test-report-preparer", "test-report-verifier", "test-report-issuer", "wspj-report-preparer", "wspj-report-verifier", "wspj-report-issuer", "report-preparer" }) private String dataCommand; /** * 主键 ID(受理组别 ID、检测组别 ID、报告类型 ID 等) */ @Schema(description = "主键 ID") private Integer id; }enum转换脚本
scripts\fix-enum.js
/** * 将 OpenAPI 生成的联合类型(如 'A' | 'B' | 'C')转换为 TypeScript 枚举(enum) * 使用场景:当后端枚举在 OpenAPI 中被生成为字符串字面量联合类型时, * 通过此脚本将其替换为真正的 enum 定义,便于前端使用。 * * 注意:该脚本会直接修改 ../src/openapi/types.gen.ts 文件,建议在生成 API 后执行。 */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; // 获取当前文件的目录路径(ES 模块中替代 __dirname) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 需要处理的文件路径(根据项目结构调整) const filePath = path.resolve(__dirname, "../src/openapi/types.gen.ts"); // 读取文件内容 let content = fs.readFileSync(filePath, "utf-8"); /** * 正则表达式:匹配形如 "fieldName: 'A' | 'B' | 'C';" 的模式 * - (\w+): 捕获字段名 * - 后面的 ((?:'[^']*'\s*\|\s*)+'[^']*') 捕获由 '字符串' 和 '|' 组成的联合类型字符串 * - 最后的分号 \s*; 匹配结尾 */ const propRegex = /(\w+):\s*((?:'[^']*'\s*\|\s*)+'[^']*')\s*;/g; // 存储需要生成的枚举信息,key 为成员排序后的唯一标识,value 为 { enumName, members } const enumMap = new Map(); let match; // 遍历所有匹配的属性 while ((match = propRegex.exec(content)) !== null) { const fieldName = match[1]; // 字段名,例如 "workflowInstanceNode" const unionStr = match[2]; // 联合类型字符串,例如 "'FIRST' | 'MIDDLE' | 'LAST'" // 提取所有成员值(去掉引号和空格) const members = unionStr.split("|").map((s) => s.trim().replace(/'/g, "")); // 生成枚举名称:将字段名首字母大写,例如 "WorkflowInstanceNode" let enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1); // 生成唯一键:将成员排序后拼接,用于判断相同成员集合的枚举 const key = members.sort().join("|"); // 如果该成员集合尚未记录,则存入 map if (!enumMap.has(key)) { enumMap.set(key, { enumName, members }); } else { // 如果已经存在相同成员集合的枚举,复用已有的枚举名,避免重复定义 const existing = enumMap.get(key); enumName = existing.enumName; } // 替换原属性类型为枚举名 const original = match[0]; // 原始匹配到的完整字符串 const replaced = `${fieldName}: ${enumName};`; // 替换后的内容 content = content.replace(original, replaced); } // 生成所有枚举的定义代码 let enumDefinitions = ""; for (const { enumName, members } of enumMap.values()) { // 每个枚举成员一行,格式:成员名 = '成员值' const enumBody = members.map((m) => ` ${m} = '${m}'`).join(",\n"); enumDefinitions += `export enum ${enumName} {\n${enumBody}\n}\n\n`; } // 如果有生成的枚举定义,则将其插入到文件开头(注释和空行之后) if (enumDefinitions) { const lines = content.split("\n"); let insertIndex = 0; // 跳过开头的空行和注释行(避免插入到注释中间) while ( insertIndex < lines.length && (lines[insertIndex].trim() === "" || lines[insertIndex].trim().startsWith("//")) ) { insertIndex++; } // 在第一个非注释、非空行之前插入枚举定义 lines.splice(insertIndex, 0, enumDefinitions.trim()); content = lines.join("\n"); // 写回文件 fs.writeFileSync(filePath, content); console.log(`✅ 成功转换 ${enumMap.size} 个枚举`); } else { console.log("ℹ️ 没有找到需要转换的联合类型属性"); }生成的前端类型
src\openapi\types.gen.ts
/** * 用户查询请求对象 */ export type UserQueryRequest = { /** * 数据指令 */ dataCommand?: 'all' | 'valid' | 'invalid' | 'working' | 'depart' | 'retire' | 'accept-group-member' | 'accept-group-addable-member' | 'assay-group-member' | 'assay-group-addable-member' | 'assay-group-assayer' | 'assay-task-assayer' | 'test-report-type-preparer' | 'test-report-type-verifier' | 'test-report-type-issuer' | 'wspj-report-type-preparer' | 'wspj-report-type-verifier' | 'wspj-report-type-issuer' | 'test-report-preparer' | 'test-report-verifier' | 'test-report-issuer' | 'wspj-report-preparer' | 'wspj-report-verifier' | 'wspj-report-issuer' | 'report-preparer'; /** * 主键 ID */ id?: number; };示例2
后端
src/main/java/com/weiyu/modules/system/dto/request/UserQueryRequest.java
package com.weiyu.modules.system.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; /** * 用户查询请求对象 */ @Schema(description = "用户查询请求对象") @Data @NoArgsConstructor public class UserQueryRequest { /** * 数据指令 */ @Schema(description = "数据指令", allowableValues = { "all", "valid", "invalid", "working", "depart", "retire", "accept-group-member", "accept-group-addable-member", "assay-group-member", "assay-group-addable-member", "assay-group-assayer", "assay-task-assayer", "test-report-type-preparer", "test-report-type-verifier", "test-report-type-issuer", "wspj-report-type-preparer", "wspj-report-type-verifier", "wspj-report-type-issuer", "test-report-preparer", "test-report-verifier", "test-report-issuer", "wspj-report-preparer", "wspj-report-verifier", "wspj-report-issuer", "report-preparer" }) private String userDataCommand; // 变量名为 userDataCommand,openapi生成的枚举为 UserDataCommand(变量名首字母变为大写) /** * 主键 ID(受理组别 ID、检测组别 ID、报告类型 ID 等) */ @Schema(description = "主键 ID") private Integer id; }enum转换脚本
scripts\fix-enum.js
/** * 将 OpenAPI 生成的联合类型(如 'A' | 'B' | 'C')转换为 TypeScript 枚举(enum) * 使用场景:当后端枚举在 OpenAPI 中被生成为字符串字面量联合类型时, * 通过此脚本将其替换为真正的 enum 定义,便于前端使用。 * * 注意:该脚本会直接修改 ../src/openapi/types.gen.ts 文件,建议在生成 API 后执行。 */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; // 获取当前文件的目录路径(ES 模块中替代 __dirname) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 需要处理的文件路径(根据项目结构调整) const filePath = path.resolve(__dirname, "../src/openapi/types.gen.ts"); // 读取文件内容 let content = fs.readFileSync(filePath, "utf-8"); /** * 将原始字符串值转换为合法的枚举键名 * - 纯数字:前加下划线(如 "0" → "_0") * - 其他:替换连字符为下划线,并转为大写(如 "all" → "ALL", "accept-group-member" → "ACCEPT_GROUP_MEMBER") */ function toEnumKey(value) { if (/^\d+$/.test(value)) { return "_" + value; } return value.replace(/-/g, "_").toUpperCase(); } /** * 正则表达式:匹配形如 "fieldName: 'A' | 'B' | 'C';" 或 "fieldName?: 'A' | 'B' | 'C';" * 同时支持单引号和双引号 * - (\w+): 捕获字段名 * - \?? 匹配可能存在的可选标记 ? * - 后面的 ((?:['"][^'"]*['"]\s*\|\s*)+['"][^'"]*['"]) 捕获由 '字符串' 或 "字符串" 和 '|' 组成的联合类型字符串 * - 最后的分号 \s*; 匹配结尾 */ const propRegex = /(\w+)\??:\s*((?:['"][^'"]*['"]\s*\|\s*)+['"][^'"]*['"])\s*;/g; // 存储需要生成的枚举信息,key 为成员排序后的唯一标识,value 为 { enumName, members } const enumMap = new Map(); let match; // 遍历所有匹配的属性 while ((match = propRegex.exec(content)) !== null) { const fieldName = match[1]; // 字段名,例如 "workflowInstanceNode" const unionStr = match[2]; // 联合类型字符串,例如 "'FIRST' | 'MIDDLE' | 'LAST'" // 提取所有成员值(去掉引号和空格) const members = unionStr.split("|").map((s) => s.trim().slice(1, -1)); // 生成枚举名称:将字段名首字母大写,例如 "WorkflowInstanceNode" let enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1); // 生成唯一键:将成员排序后拼接,用于判断相同成员集合的枚举 const key = members.sort().join("|"); // 如果该成员集合尚未记录,则存入 map if (!enumMap.has(key)) { enumMap.set(key, { enumName, members }); } else { // 如果已经存在相同成员集合的枚举,复用已有的枚举名,避免重复定义 const existing = enumMap.get(key); enumName = existing.enumName; } // 替换原属性类型为枚举名(保留字段名的问号) const original = match[0]; // 原始匹配到的完整字符串 const replaced = `${fieldName}?: ${enumName};`; // 替换后的内容 content = content.replace(original, replaced); } // 生成所有枚举的定义代码 let enumDefinitions = ""; for (const { enumName, members } of enumMap.values()) { // 每个枚举成员一行,格式:成员名 = '成员值',成员名使用合法的枚举键名 const enumBody = members.map((m) => ` ${toEnumKey(m)} = '${m}'`).join(",\n"); enumDefinitions += `export enum ${enumName} {\n${enumBody}\n}\n\n`; } // 如果有生成的枚举定义,则将其插入到文件开头(注释和空行之后) if (enumDefinitions) { const lines = content.split("\n"); let insertIndex = 0; // 跳过开头的空行和注释行(避免插入到注释中间) while ( insertIndex < lines.length && (lines[insertIndex].trim() === "" || lines[insertIndex].trim().startsWith("//")) ) { insertIndex++; } // 在第一个非注释、非空行之前插入枚举定义 lines.splice(insertIndex, 0, enumDefinitions.trim()); content = lines.join("\n"); // 写回文件 fs.writeFileSync(filePath, content); console.log(`✅ 成功转换 ${enumMap.size} 个枚举`); } else { console.log("ℹ️ 没有找到需要转换的联合类型属性"); }生成的前端类型
src\openapi\types.gen.ts
export enum UserDataCommand { ACCEPT_GROUP_ADDABLE_MEMBER = 'accept-group-addable-member', ACCEPT_GROUP_MEMBER = 'accept-group-member', ALL = 'all', ASSAY_GROUP_ADDABLE_MEMBER = 'assay-group-addable-member', ASSAY_GROUP_ASSAYER = 'assay-group-assayer', ASSAY_GROUP_MEMBER = 'assay-group-member', ASSAY_TASK_ASSAYER = 'assay-task-assayer', DEPART = 'depart', INVALID = 'invalid', REPORT_PREPARER = 'report-preparer', RETIRE = 'retire', TEST_REPORT_ISSUER = 'test-report-issuer', TEST_REPORT_PREPARER = 'test-report-preparer', TEST_REPORT_TYPE_ISSUER = 'test-report-type-issuer', TEST_REPORT_TYPE_PREPARER = 'test-report-type-preparer', TEST_REPORT_TYPE_VERIFIER = 'test-report-type-verifier', TEST_REPORT_VERIFIER = 'test-report-verifier', VALID = 'valid', WORKING = 'working', WSPJ_REPORT_ISSUER = 'wspj-report-issuer', WSPJ_REPORT_PREPARER = 'wspj-report-preparer', WSPJ_REPORT_TYPE_ISSUER = 'wspj-report-type-issuer', WSPJ_REPORT_TYPE_PREPARER = 'wspj-report-type-preparer', WSPJ_REPORT_TYPE_VERIFIER = 'wspj-report-type-verifier', WSPJ_REPORT_VERIFIER = 'wspj-report-verifier' } /** * 用户查询请求对象 */ export type UserQueryRequest = { /** * 数据指令 */ userDataCommand?: UserDataCommand; /** * 主键 ID */ id?: number; };示例3
后端
src/main/java/com/weiyu/modules/system/dto/request/UserQueryRequest.java
package com.weiyu.modules.system.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; /** * 用户查询请求对象 */ @Schema(description = "用户查询请求对象") @Data @NoArgsConstructor public class UserQueryRequest { /** * 数据指令 */ @Schema(description = "数据指令", allowableValues = { "all", "valid", "invalid", "working", "depart", "retire", "accept-group-member", "accept-group-addable-member", "assay-group-member", "assay-group-addable-member", "assay-group-assayer", "assay-task-assayer", "test-report-type-preparer", "test-report-type-verifier", "test-report-type-issuer", "wspj-report-type-preparer", "wspj-report-type-verifier", "wspj-report-type-issuer", "test-report-preparer", "test-report-verifier", "test-report-issuer", "wspj-report-preparer", "wspj-report-verifier", "wspj-report-issuer", "report-preparer" }) private String dataCommand; /** * 主键 ID(受理组别 ID、检测组别 ID、报告类型 ID 等) */ @Schema(description = "主键 ID") private Integer id; }enum转换脚本
scripts\fix-enum.js
/** * 将 OpenAPI 生成的联合类型(如 'A' | 'B' | 'C')转换为 TypeScript 枚举(enum) * 使用场景:当后端枚举在 OpenAPI 中被生成为字符串字面量联合类型时, * 通过此脚本将其替换为真正的 enum 定义,便于前端使用。 * * 注意:该脚本会直接修改 ../src/openapi/types.gen.ts 文件,建议在生成 API 后执行。 */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; // 获取当前文件的目录路径(ES 模块中替代 __dirname) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 需要处理的文件路径(根据项目结构调整) const filePath = path.resolve(__dirname, "../src/openapi/types.gen.ts"); // 读取文件内容 let content = fs.readFileSync(filePath, "utf-8"); /** * 将原始字符串值转换为合法的枚举键名 * - 纯数字:前加下划线(如 "0" → "_0") * - 其他:替换连字符为下划线,并转为大写(如 "all" → "ALL", "accept-group-member" → "ACCEPT_GROUP_MEMBER") */ function toEnumKey(value) { if (/^\d+$/.test(value)) { return "_" + value; } return value.replace(/-/g, "_").toUpperCase(); } /** * 正则表达式:匹配形如 "fieldName: 'A' | 'B' | 'C';" 或 "fieldName?: 'A' | 'B' | 'C';" * 同时支持单引号和双引号 * - (\w+): 捕获字段名 * - \?? 匹配可能存在的可选标记 ? * - 后面的 ((?:['"][^'"]*['"]\s*\|\s*)+['"][^'"]*['"]) 捕获由 '字符串' 或 "字符串" 和 '|' 组成的联合类型字符串 * - 最后的分号 \s*; 匹配结尾 */ const propRegex = /(\w+)\??:\s*((?:['"][^'"]*['"]\s*\|\s*)+['"][^'"]*['"])\s*;/g; // 存储需要生成的枚举信息,key 为成员排序后的唯一标识,value 为 { enumName, members } const enumMap = new Map(); // 不需要自动转换的字段(由手动维护的前端枚举接管) const excludeFields = ["dataCommand", "rejectMode"]; let match; // 遍历所有匹配的属性 while ((match = propRegex.exec(content)) !== null) { // 排除不需要自动转换的字段(如 dataCommand 由手动维护的前端枚举接管) if (excludeFields.includes(match[1])) continue; const fieldName = match[1]; // 字段名,例如 "workflowInstanceNode" const unionStr = match[2]; // 联合类型字符串,例如 "'FIRST' | 'MIDDLE' | 'LAST'" // 提取所有成员值(去掉引号和空格) const members = unionStr.split("|").map((s) => s.trim().slice(1, -1)); // 生成枚举名称:将字段名首字母大写,例如 "WorkflowInstanceNode" let enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1); // 生成唯一键:将成员排序后拼接,用于判断相同成员集合的枚举 const key = members.sort().join("|"); // 如果该成员集合尚未记录,则存入 map if (!enumMap.has(key)) { enumMap.set(key, { enumName, members }); } else { // 如果已经存在相同成员集合的枚举,复用已有的枚举名,避免重复定义 const existing = enumMap.get(key); enumName = existing.enumName; } // 替换原属性类型为枚举名(保留字段名的问号) const original = match[0]; // 原始匹配到的完整字符串 const replaced = `${fieldName}?: ${enumName};`; // 替换后的内容 content = content.replace(original, replaced); } // 生成所有枚举的定义代码 let enumDefinitions = ""; for (const { enumName, members } of enumMap.values()) { // 每个枚举成员一行,格式:成员名 = '成员值',成员名使用合法的枚举键名 const enumBody = members.map((m) => ` ${toEnumKey(m)} = '${m}'`).join(",\n"); enumDefinitions += `export enum ${enumName} {\n${enumBody}\n}\n\n`; } // 如果有生成的枚举定义,则将其插入到文件开头(注释和空行之后) if (enumDefinitions) { const lines = content.split("\n"); let insertIndex = 0; // 跳过开头的空行和注释行(避免插入到注释中间) while ( insertIndex < lines.length && (lines[insertIndex].trim() === "" || lines[insertIndex].trim().startsWith("//")) ) { insertIndex++; } // 在第一个非注释、非空行之前插入枚举定义 lines.splice(insertIndex, 0, enumDefinitions.trim()); content = lines.join("\n"); // 写回文件 fs.writeFileSync(filePath, content); console.log(`✅ 成功转换 ${enumMap.size} 个枚举`); } else { console.log("ℹ️ 没有找到需要转换的联合类型属性"); }生成的前端类型
src\openapi\types.gen.ts
/** * 用户查询请求对象 */ export type UserQueryRequest = { /** * 数据指令 */ dataCommand?: 'all' | 'valid' | 'invalid' | 'working' | 'depart' | 'retire' | 'accept-group-member' | 'accept-group-addable-member' | 'assay-group-member' | 'assay-group-addable-member' | 'assay-group-assayer' | 'assay-task-assayer' | 'test-report-type-preparer' | 'test-report-type-verifier' | 'test-report-type-issuer' | 'wspj-report-type-preparer' | 'wspj-report-type-verifier' | 'wspj-report-type-issuer' | 'test-report-preparer' | 'test-report-verifier' | 'test-report-issuer' | 'wspj-report-preparer' | 'wspj-report-verifier' | 'wspj-report-issuer' | 'report-preparer'; /** * 主键 ID */ id?: number; };