New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

czh-api

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

czh-api

A CLI tool to generate TypeScript API clients from Swagger/OpenAPI documents.

latest
npmnpm
Version
1.0.5
Version published
Weekly downloads
9
-73.53%
Maintainers
1
Weekly downloads
 
Created
Source

CZH API

czh-api 是一款功能强大且灵活的前端 API 同步工具,灵感来源于 Pont。它能够根据 Swagger/OpenAPI 文档自动生成 TypeScript API 代码,帮助您保持前后端接口的一致性,提高开发效率,并减少手动编写代码带来的错误。

✨ 功能特性

  • 多标准支持: 完美兼容 Swagger v2, OpenAPI v3, 以及 Knife4j 风格的 JSON 文档。
  • OpenAPI 3.1.0 支持: 自动检测并转换 OpenAPI 3.1.0 规范到兼容的 3.0.x 版本,完美支持 FastAPI 生成的文档。
  • 智能模块生成: 根据接口的 x-package 字段(例如 com.czh.SysConfigController -> sysConfig 模块)自动对 API 进行模块化分组,当该字段缺失时,则使用 URL 的第一段作为备用方案。
  • 清晰的命名规范: API 函数名采用 HTTP方法 + 驼峰式路径 的方式生成(例如 POST /sys/user/add -> postSysUserAdd),确保了全局唯一性和代码的可读性。
  • 可定制化模板: 提供 Handlebars (.hbs) 模板,覆盖 API、类型和模块索引文件,允许您完全自定义生成的代码风格。
  • 自动化类型生成: 为所有请求参数和响应数据生成 TypeScript 类型定义(types.ts)。
  • 详尽的 JSDoc 注释: 为每个 API 函数自动生成全面的 JSDoc 注释,包含接口描述以及每个参数详尽的 @param 注解, 参数名称带[]则后端为非必填。
  • 灵活的配置: 使用 czh-api.config.json 文件来管理所有设置,例如 Swagger 地址、输出目录、需要排除的路径以及自定义导入语句等。
  • NPM 包与命令行工具: 已封装为 Node.js 命令行工具,便于在任何前端项目中安装和使用。

🚀 安装

通过 npm 全局安装本工具:

npm install -g czh-api

🛠️ 使用说明

主要命令

czh-api 提供了以下核心命令来管理您的 API 代码生成:

命令别名 (alias)描述
czh-api-h, --help显示帮助信息。列出所有可用的命令和选项。
czh-api -v--version查看版本号。显示当前安装的 czh-api 的版本。
czh-api init初始化项目。在当前目录下创建 czh-api.config.json 配置文件和 czh-api-template 模板文件夹。
czh-api build生成 API 代码。根据配置文件中的 url 获取远程 API 文档,并在指定的 outputDir 目录中生成所有 TypeScript 模块、类型和索引文件。

配置文件 (czh-api.config.json)

init 命令会生成此文件。您需要根据项目需求进行编辑:

{
  "url": "https://your-swagger-docs/v2/api-docs",
  "outputDir": "./src/api",
  "templates": "./czh-api-template",
  "customImports": [
    "import http from '@/utils/http';"
  ],
  "excludePaths": [
    "/sys/log",
    "/tool/gen"
  ],
  "includePaths": [
    "/sys/user",
    "/sys/role"
  ]
}

配置项说明:

选项类型是否必须描述
urlstring您的 Swagger/OpenAPI JSON 文档地址。
outputDirstring用于存放生成的 API 模块的目录。
templatesstring指向您的自定义模板目录的路径。默认为 init 命令生成的目录。
customImportsstring[]一个自定义导入语句的数组,会被添加到每个生成的 API 文件的顶部。
excludePathsstring[]一个 URL 路径前缀的数组。任何以此数组中前缀开头的 API 都将被忽略。
includePathsstring[]一个 URL 路径前缀的数组。如果配置了此项,则只同步以这些前缀开头的 API。
pathPrefixesarray路径前缀配置数组,用于自定义模块分组和二级分包。详见下方说明。

路径前缀配置 (pathPrefixes)

pathPrefixes 允许您根据 API 路径前缀自定义模块分组和二级分包策略。

配置格式:

{
  "pathPrefixes": [
    {
      "path": "/api/expert",
      "packageName": "expert"
    },
    {
      "path": "/api/user"
      // 不指定 packageName,将自动使用驼峰命名: apiUser
    }
  ]
}

配置项说明:

字段类型是否必须描述
pathstring要匹配的路径前缀,如 /api/expert
packageNamestring自定义包名。如果不指定,将自动将路径转为驼峰命名(如 /api/expert -> apiExpert

分包规则:

  • 一级包名: 由 packageName 指定,或自动从 path 转换为驼峰命名
  • 二级包名: 使用路径前缀后的第一级路径作为子模块名

示例:

假设配置了:

{
  "pathPrefixes": [
    {
      "path": "/api/expert",
      "packageName": "expert"
    }
  ]
}

API 路径分包结果:

  • /api/expert/certification/submitexpert/certification
  • /api/expert/profile/updateexpert/profile
  • /api/expert/infoexpert (没有二级路径时直接使用包名)

生成的目录结构:

src/api/
├── expert/
│   ├── certification/
│   │   ├── certification.ts
│   │   ├── types.ts
│   │   └── index.ts
│   └── profile/
│       ├── profile.ts
│       ├── types.ts
│       └── index.ts

📝 模板配置

czh-api 使用 Handlebars 模板引擎来生成代码。运行 czh-api init 后会在 czh-api-template 目录下生成默认模板,您可以根据项目需求自定义。

模板文件

文件名用途
api.hbs生成每个 API 函数的代码
types.hbs生成类型定义文件(如有)
index.hbs生成模块索引文件(如有)

可用变量

api.hbs 模板中,您可以使用以下变量:

变量名类型描述
functionNamestring生成的函数名,如 postSysUserAdd
descriptionstring接口描述
methodstringHTTP 方法:get, post, put, delete
pathstring请求路径,路径参数已转为模板字符串格式
hasParamsboolean是否有查询/路径参数
hasDataboolean是否有请求体
requestParamsTypeNamestring请求参数的类型名
requestBodyTypeNamestring请求体的类型名
responseTypeNamestring响应数据的类型名
contentTypestringContent-Type,如 multipart/form-data
jsdocParamsarrayJSDoc 参数列表,包含 name, type, description, required

内置 Helpers

Helper用法描述
eq{{#if (eq method "delete")}}...{{/if}}判断两个值是否相等

模板示例

默认的 api.hbs 模板:

/**
 * @description {{description}}
 {{#if jsdocParams}}
 {{#each jsdocParams}}
 * @param { {{this.type}} } {{#unless this.required}}[{{/unless}}{{this.name}}{{#unless this.required}}]{{/unless}} - {{this.description}}
 {{/each}}
 {{/if}}
 */
export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeName}}{{/if}}{{#if hasData}}{{#if hasParams}}, {{/if}}data: {{requestBodyTypeName}}{{/if}}) => {
  return http.request<{{responseTypeName}}>({
    url: `{{path}}`,
    method: '{{method}}',
    {{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
    {{#if contentType}}
    headers: { 'Content-Type': '{{contentType}}' },
    {{/if}}
  });
};

如果您的 HTTP 客户端使用 request.get(), request.post() 这种风格,并且 delete 方法需要改为 del,可以这样写:

export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeName}}{{/if}}{{#if hasData}}{{#if hasParams}}, {{/if}}data: {{requestBodyTypeName}}{{/if}}) => {
  return request.{{#if (eq method "delete")}}del{{else}}{{method}}{{/if}}<{{responseTypeName}}>({
    url: `{{path}}`,
    {{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
    {{#if contentType}}
    headers: { 'Content-Type': '{{contentType}}' },
    {{/if}}
  });
};

🐍 FastAPI 兼容性

czh-api 完全支持 FastAPI 生成的 OpenAPI 3.1.0 文档。工具会自动检测 OpenAPI 版本并进行必要的转换。

FastAPI 推荐配置

如果您在使用 FastAPI 时遇到兼容性问题,可以在创建 FastAPI 应用时指定 OpenAPI 版本:

from fastapi import FastAPI

# 方法1: 指定 OpenAPI 版本为 3.0.2(推荐)
app = FastAPI(openapi_version="3.0.2")

# 方法2: 使用默认 3.1.0(czh-api 会自动转换)
app = FastAPI()

常见问题解决

如果遇到 "Missing $ref pointer" 错误,通常是由于 OpenAPI 3.1.0 到 3.0.x 转换过程中的引用问题。建议:

  • 优先方案: 在 FastAPI 中配置使用 OpenAPI 3.0.2
  • 备选方案: 检查 FastAPI 文档中是否有循环引用或复杂的 schema 定义
  • 临时方案: 使用 excludePaths 配置排除有问题的接口路径

示例配置

{
  "url": "http://localhost:8000/openapi.json",
  "outputDir": "./src/api",
  "customImports": [
    "import request from '@/utils/request';"
  ],
  "excludePaths": [
    "/docs",
    "/redoc"
  ]
}

如果您克隆了本仓库并希望进行二次开发、贡献或发布您自己的版本,请遵循以下步骤:

1. 安装依赖

在项目根目录下运行,安装所有开发和运行时所需的依赖。

npm install

2. 编译源码

此命令会将 src/ 目录下的 TypeScript 源码编译成 JavaScript,并输出到 dist/ 目录。同时,它会自动复制必要的模板文件。

npm run build

3. 本地测试

使用 npm link 可以将本地开发版本的包链接到全局,让您可以在任何地方通过 czh-api 命令来测试您的修改,而无需发布到 NPM。

npm link

4. 发布到 NPM

当您准备好发布新版本时,请执行此命令。 注意: 在发布前,请确保您已登录 NPM (npm login),并在 package.json 中更新了包的版本号。

npm publish

发布到npm官方仓库需要切到官方源

npm config set registry https://registry.npmjs.org/
npm config set //registry.npmjs.org/:_authToken=你的token
npm publish

5. 解除本地链接/卸载

如果您想解除本地的链接状态,可以使用 unlink 命令。如果您想从全局卸载,请使用 uninstall

# 解除本地开发链接
npm unlink czh-api

# 或,全局卸载包
npm uninstall -g czh-api

6. 如果服务端是knife4j风格的JSON文档

6.1 SpringBoot3配置

SpringBoot3项目直接复制6.3配置文件即可

6.2 SpringBoot2配置

复制6.3 把jakarta替换为javax

6.3 SpringBoot配置

ResponseWrapper.java

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

public class ResponseWrapper extends HttpServletResponseWrapper {
    private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    private final StringWriter stringWriter = new StringWriter();
    private PrintWriter writer;
    private boolean writerUsed = false;
    private boolean outputStreamUsed = false;

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (outputStreamUsed) {
            throw new IllegalStateException("getOutputStream() has already been called for this response");
        }
        if (writer == null) {
            writer = new PrintWriter(stringWriter);
        }
        writerUsed = true;
        return writer;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (writerUsed) {
            throw new IllegalStateException("getWriter() has already been called for this response");
        }
        outputStreamUsed = true;
        return new ServletOutputStream() {
            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {
                // Do nothing
            }

            @Override
            public void write(int b) throws IOException {
                outputStream.write(b);
            }
        };
    }

    public String getContent() {
        if (writerUsed) {
            writer.flush();
            return stringWriter.toString();
        } else if (outputStreamUsed) {
            return outputStream.toString();
        }
        return "";
    }
}

ApiDocsFormDataFilter.java

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Order(1)
public class ApiDocsFormDataFilter implements Filter {

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    // 缓存路径到Controller的映射关系
    private final Map<String, String> pathToControllerMap = new ConcurrentHashMap<>();

    // 特殊指定的formdata路径(文件上传等)
    private static final Set<String> FORCE_FORM_DATA_PATHS = Set.of();

    /**
     * 初始化时自动扫描所有Controller映射
     */
    @PostConstruct
    public void initControllerMappings() {
        try {
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = 
                requestMappingHandlerMapping.getHandlerMethods();
            
            for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
                RequestMappingInfo mappingInfo = entry.getKey();
                HandlerMethod handlerMethod = entry.getValue();
                
                // 获取Controller类的完整名称
                String controllerClass = handlerMethod.getBeanType().getName();
                
                // 获取路径模式
                Set<String> patterns = mappingInfo.getPatternValues();
                for (String pattern : patterns) {
                    // 清理路径模式(移除路径变量)
                    String cleanPath = cleanPathPattern(pattern);
                    pathToControllerMap.put(cleanPath, controllerClass);

                    // 同时存储原始路径
                    pathToControllerMap.put(pattern, controllerClass);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清理路径模式,移除路径变量
     */
    private String cleanPathPattern(String pattern) {
        // 移除路径变量 {id} -> 空
        return pattern.replaceAll("\\{[^}]+\\}", "");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 只处理 /v3/api-docs 请求
        if (httpRequest.getRequestURI().contains("/v3/api-docs")) {
            
            // 创建响应包装器来捕获原始响应
            ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
            
            try {
                // 继续执行原始请求
                chain.doFilter(request, responseWrapper);
                
                // 获取原始响应内容
                String originalContent = responseWrapper.getContent();
                
                // 智能标识请求类型
                String modifiedContent = smartMarkRequestTypes(originalContent);
                
                // 写入修改后的内容
                response.setContentType("application/json;charset=UTF-8");
                response.setContentLength(modifiedContent.getBytes(StandardCharsets.UTF_8).length);
                response.getOutputStream().write(modifiedContent.getBytes(StandardCharsets.UTF_8));
                response.getOutputStream().flush();
                
            } catch (Exception e) {
                e.printStackTrace();
                // 如果出错,返回原始内容
                chain.doFilter(request, response);
            }
        } else {
            chain.doFilter(request, response);
        }
    }

    private String smartMarkRequestTypes(String originalContent) {
        try {
            // 使用 fastjson2 解析
            JSONObject rootJson = JSON.parseObject(originalContent);
            
            // 处理所有路径
            JSONObject paths = rootJson.getJSONObject("paths");
            if (paths != null) {
                processAllPathsSmart(paths);
            }
            
            return rootJson.toJSONString();
        } catch (Exception e) {
            e.printStackTrace();
            return originalContent;
        }
    }

    private void processAllPathsSmart(JSONObject paths) {
        for (String pathKey : paths.keySet()) {
            JSONObject pathItem = paths.getJSONObject(pathKey);
            if (pathItem != null) {
                processPathItemSmart(pathKey, pathItem);
            }
        }
    }

    private void processPathItemSmart(String pathKey, JSONObject pathItem) {
        // 检查所有HTTP方法
        Arrays.asList("get", "post", "put", "delete", "patch").forEach(method -> {
            JSONObject operation = pathItem.getJSONObject(method);
            if (operation != null) {
                smartProcessOperation(operation, pathKey, method);
            }
        });
    }

    private void smartProcessOperation(JSONObject operation, String pathKey, String method) {
        String requestType = "params"; // 默认为params
        String contentType = null;

        // 1. 强制指定的formdata路径
        if (FORCE_FORM_DATA_PATHS.contains(pathKey)) {
            requestType = "formdata";
            contentType = "multipart/form-data";
            convertToFormData(operation);
        }
        // 2. 检查是否有 requestBody (对应 @RequestBody)
        else {
            JSONObject requestBody = operation.getJSONObject("requestBody");
            if (requestBody != null) {
                JSONObject content = requestBody.getJSONObject("content");
                if (content != null) {
                    // 检查各种 content-type
                    if (content.containsKey("application/json")) {
                        // @RequestBody + JSON
                        requestType = "json";
                        contentType = "application/json";
                    }
                    else if (content.containsKey("application/x-www-form-urlencoded")) {
                        // 表单数据 -> 转为 formdata
                        requestType = "formdata";
                        contentType = "multipart/form-data";
                        convertFormUrlencodedToFormData(content);
                    }
                    else if (content.containsKey("multipart/form-data")) {
                        // 已经是 formdata
                        requestType = "formdata";
                        contentType = "multipart/form-data";
                    }
                    // 检查是否包含文件上传
                    else if (hasFileUpload(content) || isFileUploadPath(pathKey)) {
                        requestType = "formdata";
                        contentType = "multipart/form-data";
                        convertToFormData(operation);
                    }
                }
            }
            // 3. 没有 requestBody,检查是否有查询参数
            else {
                JSONArray parameters = operation.getJSONArray("parameters");
                if (parameters != null) {
                    boolean hasQueryParams = false;
                    for (int i = 0; i < parameters.size(); i++) {
                        JSONObject param = parameters.getJSONObject(i);
                        if (param != null && "query".equals(param.getString("in"))) {
                            hasQueryParams = true;
                            break;
                        }
                    }
                    if (hasQueryParams) {
                        requestType = "params";
                    }
                }
                
                // 4. 根据路径和方法推断
                if (isFileUploadPath(pathKey)) {
                    requestType = "formdata";
                    contentType = "multipart/form-data";
                } else if ("post".equals(method) || "put".equals(method) || "patch".equals(method)) {
                    // POST/PUT/PATCH 但没有 requestBody,可能是表单提交
                    requestType = "formdata";
                    contentType = "multipart/form-data";
                }
            }
        }

        // 添加自定义扩展字段
        operation.put("x-request-type", requestType);
        if (contentType != null) {
            operation.put("x-content-type", contentType);
        }
        
        // 🆕 自动获取Controller包路径
        String controllerClass = getControllerClassAuto(pathKey);
        if (controllerClass != null) {
            operation.put("x-package", controllerClass);
        }
    }

    /**
     * 自动获取Controller类路径(从缓存的映射中查找)
     */
    private String getControllerClassAuto(String pathKey) {
        // 1. 直接匹配
        String controllerClass = pathToControllerMap.get(pathKey);
        if (controllerClass != null) {
            return controllerClass;
        }
        
        // 2. 模糊匹配(处理路径变量的情况)
        for (Map.Entry<String, String> entry : pathToControllerMap.entrySet()) {
            String mappedPath = entry.getKey();
            String mappedController = entry.getValue();
            
            // 检查是否是路径变量匹配
            if (isPathMatch(pathKey, mappedPath)) {
                return mappedController;
            }
        }
        
        // 3. 前缀匹配
        String longestMatch = "";
        String bestController = null;
        for (Map.Entry<String, String> entry : pathToControllerMap.entrySet()) {
            String mappedPath = entry.getKey();
            String mappedController = entry.getValue();
            
            // 去除路径变量后进行前缀匹配
            String cleanMappedPath = cleanPathPattern(mappedPath);
            String cleanPathKey = cleanPathPattern(pathKey);
            
            if (cleanPathKey.startsWith(cleanMappedPath) && cleanMappedPath.length() > longestMatch.length()) {
                longestMatch = cleanMappedPath;
                bestController = mappedController;
            }
        }
        
        return bestController;
    }

    /**
     * 检查路径是否匹配(支持路径变量)
     */
    private boolean isPathMatch(String actualPath, String patternPath) {
        // 简单的路径变量匹配
        String[] actualParts = actualPath.split("/");
        String[] patternParts = patternPath.split("/");
        
        if (actualParts.length != patternParts.length) {
            return false;
        }
        
        for (int i = 0; i < actualParts.length; i++) {
            String actualPart = actualParts[i];
            String patternPart = patternParts[i];
            
            // 如果是路径变量,跳过
            if (patternPart.startsWith("{") && patternPart.endsWith("}")) {
                continue;
            }
            
            // 必须完全匹配
            if (!actualPart.equals(patternPart)) {
                return false;
            }
        }
        
        return true;
    }

    // ... 其他方法保持不变 ...
    
    /**
     * 将 application/x-www-form-urlencoded 转换为 multipart/form-data
     */
    private void convertFormUrlencodedToFormData(JSONObject content) {
        if (content.containsKey("application/x-www-form-urlencoded")) {
            Object formContent = content.get("application/x-www-form-urlencoded");
            content.remove("application/x-www-form-urlencoded");
            content.put("multipart/form-data", formContent);
        }
    }

    /**
     * 将 application/json 转换为 multipart/form-data
     */
    private void convertToFormData(JSONObject operation) {
        JSONObject requestBody = operation.getJSONObject("requestBody");
        if (requestBody != null) {
            JSONObject content = requestBody.getJSONObject("content");
            if (content != null && content.containsKey("application/json")) {
                Object jsonContent = content.get("application/json");
                content.remove("application/json");
                content.put("multipart/form-data", jsonContent);
            }
        }
    }

    /**
     * 检查是否包含文件上传字段
     */
    private boolean hasFileUpload(JSONObject content) {
        // 检查各种 content-type 中是否有文件字段
        for (String contentType : content.keySet()) {
            JSONObject typeContent = content.getJSONObject(contentType);
            if (typeContent != null) {
                JSONObject schema = typeContent.getJSONObject("schema");
                if (schema != null && checkSchemaForFiles(schema)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检查 schema 中是否包含文件字段
     */
    private boolean checkSchemaForFiles(JSONObject schema) {
        JSONObject properties = schema.getJSONObject("properties");
        if (properties != null) {
            for (String fieldName : properties.keySet()) {
                JSONObject field = properties.getJSONObject(fieldName);
                if (field != null) {
                    // 检查是否为文件类型
                    if ("string".equals(field.getString("type")) && "binary".equals(field.getString("format"))) {
                        return true;
                    }
                    
                    // 检查字段名是否包含文件相关关键词
//                    if (fieldName.toLowerCase().contains("file") ||
//                        fieldName.toLowerCase().contains("upload") ||
//                        fieldName.toLowerCase().contains("image") ||
//                        fieldName.toLowerCase().contains("document") ||
//                        fieldName.toLowerCase().contains("attachment")) {
//                        return true;
//                    }
                    
                    // 检查数组类型的文件
                    if ("array".equals(field.getString("type"))) {
                        JSONObject items = field.getJSONObject("items");
                        if (items != null && "string".equals(items.getString("type")) && "binary".equals(items.getString("format"))) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 根据路径判断是否为文件上传接口
     */
    private boolean isFileUploadPath(String pathKey) {
        return pathKey.contains("/upload") || 
               pathKey.contains("/file") || 
               pathKey.contains("/image") ||
               pathKey.contains("/document") ||
               pathKey.contains("/attachment");
    }
}

Keywords

api

FAQs

Package last updated on 19 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts