news 2026/6/10 3:08:00

关于枚举:后端请求对象通过openapi生成前端数据类型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
关于枚举:后端请求对象通过openapi生成前端数据类型

示例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; };
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 3:04:23

苹果 WWDC27 未提 tvOS,硬件限制下 Apple TV 何时迎重大更新?

WWDC27&#xff1a;tvOS 意外缺席 每年的苹果全球开发者大会&#xff08;WWDC&#xff09;都是了解苹果操作系统未来方向的重要窗口。然而在今年的主题演讲中&#xff0c;除了一张列有所有现有苹果操作系统、旁边标着“27”的图表外&#xff0c;竟只字未提 tvOS。演讲结构按功能…

作者头像 李华
网站建设 2026/6/10 2:57:54

48.夏至

六月下旬&#xff0c;夏至已过&#xff0c;白昼长到了极限。下午六点&#xff0c;夕阳依然高悬&#xff0c;明晃晃、金灿灿地挂在西边楼宇的缝隙间&#xff0c;光线灼热而锐利&#xff0c;将万物的影子拉得又细又长。空气是凝滞的&#xff0c;闷热的&#xff0c;没有风&#xf…

作者头像 李华
网站建设 2026/6/10 2:56:49

45|提示与 Agent 的评测:行为正确性、工具使用正确性

在上一篇&#xff0c;我们讲了如何用“金标集”来给普通的 AI 问答系统打分。 但如果你开发的是一个 Agent&#xff08;智能体&#xff09;&#xff0c;情况就完全不同了。 普通的问答系统&#xff0c;你只看它**“说了什么”&#xff08;最终答案对不对&#xff09;。 而对于 …

作者头像 李华
网站建设 2026/6/10 2:51:19

OBS多平台直播插件完整指南:3步实现一键多平台推流

OBS多平台直播插件完整指南&#xff1a;3步实现一键多平台推流 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是否曾经为了在不同直播平台切换推流设置而感到烦恼&#xff1f;每次直…

作者头像 李华
网站建设 2026/6/10 2:50:23

从打样到万件:人形机器人髋关节加工难点怎么解决?

一个人形机器人髋关节加工的需求背景 去年下半年&#xff0c;深圳某头部人形机器人初创团队带来了他们新一代双足机器人的髋关节零件加工需求——这是他们整机研发的核心卡脖子环节&#xff0c;之前找的三家加工厂都没能做出合格的零件&#xff0c;项目进度已经拖了快一个月&am…

作者头像 李华