通过代码使用apifox.json格式导出接口文档

1.前言

最近项目交付需要提交接口文档,由我来完成这个任务,但是接口文档的编写绝对是很折磨人的一件事情,就比如我要完成的接口文档就有几百个接口,如果手动一个个去写的话,不仅浪费时间,还很容易写错,所以我就想能不能有什么工具可以帮忙导出接口word文档,比如swagger、apifox等,但是发现它们都不能直接导出word,需要一定的转换,并且导出的模板是固定的,后续可能还会花大把时间来调整格式,所以就决定自己写代码来导出文档

最终我选择根据apifox的接口json格式文件来导出文档,它就和swagger的json格式内容类似,都包含了所有接口的信息,实际上swagger或apifox生成的文档也是根据这些接口信息生成的,而apifox的文档比swagger的包含更多信息,swagger只会读取swagger相关注解的内容,而apifox的会包含一些其他注解和注释的信息,再加上我们目前前后端联调也是用的apifox

2.实现

2.1 获取接口信息文件

首先右键选择导出
在这里插入图片描述
然后选择Apifox格式的文件,选择要导出的接口点击导出
在这里插入图片描述
此时就会获得mianshiya.apifox.json这样一个.apifox.json格式的文件

2.2 文件格式分析

首先对前面得到的文件进行分析,主要使用的apiCollectionschemaCollection字段中的数据,分别为接口信息和数据对象模型
在这里插入图片描述
apiCollection字段实际上是一个数组,分别对应不同目录下的数据,但我在使用的时候只会在根目录下进行操作,在根目录下一般在根据不同controller又建立不同的子目录,对应的就是items字段
在这里插入图片描述
items字段同样是一个数组,每一个元素就代表一个controller里的接口,每个元素里目前对我有用的就是name字段和items字段,分别对应controller的名称和每一个具体接口的信息
在这里插入图片描述
这里的嵌套的itesm字段里可用的信息就很多了,比如name以及api字段里的method, path, paramters, responses, responseExamples, requestBody等,所以主要解析的内容就是这些字段
在这里插入图片描述
而上面这些字段可能存在$ref字段,它的值是一开始说的schemaCollection数据模型中数据的id,并且存在嵌套的关系,就是schemaCollection里的一个数据模型同样会使用$ref字段
在这里插入图片描述

2.3 代码实现

首先因为需要解析json文件以及创建word文档,所以需要引入一些依赖,解析json使用的是fastjson,操作word则是poi

		<!-- POI基础依赖 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.3.0</version>
        </dependency>
        <!-- POI对OOXML格式的支持 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.3.0</version>
        </dependency>
        <!-- 必要的XML解析库 -->
        <dependency>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
            <version>5.1.1</version>
        </dependency>
        <!-- POI对图表等功能的支持(可选,根据需求添加) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.1.2</version> <!-- 可选版本,根据需要调整 -->
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

接着是一些原文档对应的数据模型类

@Data
public class Item {

    private String name;

    private String id;

    private Api api;

    private Schema schema;

    private List<Item> items;
}
@Data
public class Schema {

    private JsonSchema jsonSchema;
}
@Data
public class JsonSchema {

    private String type;

    // 保证原数据的顺序
    private LinkedHashMap<String, JSONObject> properties;

    private List<String> required;

    @JSONField(name = "$ref")
    private String ref;

    private String description;
}
@Data
public class Api {

    private String id;

    private String method;

    private String path;

    private Parameter parameters;

    private List<Response> responses;

    private List<ResponseExample> responseExamples;

    private RequestBody requestBody;
}
@Data
public class Parameter {

    private List<Query> query;
}
@Data
public class Response {

    private JsonSchema jsonSchema;
}
@Data
public class ResponseExample {

    private String name;

    private String data;
}
@Data
public class RequestBody {

    private String type;

    private JsonSchema jsonSchema;

	private List<Query> parameters;
}
@Data
public class Query {

    private String name;

    private Boolean required;

    private String description;

    private String type;
}

接着是两个生成表格用的数据对象

@Data
public class RequestData {

    private String name;

    private String position;

    private String type;

    private String required;

    private String description;

    public List<String> list() {
        return Arrays.asList(name, position, type, required, description);
    }
}
@Data
public class ResponseData {

    private String name;

    private String type;

    private String description;

    public List<String> list() {
        return Arrays.asList(name, type, description);
    }
}

接着就是主要的生成文档代码

public class JustTest {

    /**
     * 核心的接口信息数据
     */
    private static List<Item> apiItems;

    /**
     * 核心的数据对象数据
     */
    private static List<Item> schemaItems;

    /**
     * 解析的apifox.json文件地址
     */
    private static final String filePath = "d://desktop/mianshiya.apifox.json";

    /**
     * 输出文件名,也可以修改为绝对地址
     */
    public static final String outputFileName = "output-mianshiya.docx";

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        // 解析apifox.json文件数据
        parseApifoxJson();
        // 转换为word文旦
        translateToWord();

        System.out.printf("总耗时: %s s\n", (System.currentTimeMillis() - start) / 1000.0);
    }

    /**
     * 解析json文件
     */
    private static void parseApifoxJson() {
        long start = System.currentTimeMillis();
        StringBuilder content = new StringBuilder();

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line);
            }

            String json = content.toString();
            System.out.println("json总长度: " + json.length());
            JSONObject jsonObject = JSONObject.parseObject(json);

            // 获取取apiCollection[0].items
            JSONObject apiCollection0 = (JSONObject)jsonObject.getJSONArray("apiCollection").get(0);
            String jsonApiCollectionItems = apiCollection0.getString("items");
            apiItems = JSONObject.parseObject(jsonApiCollectionItems, new TypeReference<List<Item>>() {});

            // 获取schemaCollection[0].items
            JSONObject schemaCollection0 = (JSONObject)jsonObject.getJSONArray("schemaCollection").get(0);
            String jsonSchemaCollection0Items = schemaCollection0.getString("items");
            schemaItems = JSONObject.parseObject(jsonSchemaCollection0Items, new TypeReference<List<Item>>() {});

            long end = System.currentTimeMillis();
            System.out.printf("解析apifox.json总耗时: %s s\n", (end - start) / 1000.0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将数据转换为word文档
     *
     * @throws Exception
     */
    private static void translateToWord() throws Exception {
        // 创建一个新的Word文档
        XWPFDocument document = new XWPFDocument();
        // 标题的1级序号
        int no = 1;
        // 标题的2级序号
        int categoryNo = 0;
        for (Item apiItem : apiItems) {
            categoryNo++;
            // 获取分类名(一般对应的是controller的名称)
            String categoryName = apiItem.getName();
            List<Item> items = apiItem.getItems();

            // 创建一级标题
            XWPFParagraph heading1 = document.createParagraph();
            // 设置大纲级别为1(一级标题)
            setHeadingLevel(heading1, 1);
            XWPFRun run1 = heading1.createRun();
            String title1 = String.format("%s.%s %s", no, categoryNo, categoryName);
            run1.setText(title1);
            // 加粗
            run1.setBold(true);
            // 字体大小
            run1.setFontSize(16);
            // 字体
            run1.setFontFamily("宋体");

            // todo
            System.out.printf("-----------%s.%s %s-----------\n", no, categoryNo, categoryName);

            // 标题的三级序号
            int itemNo = 0;
            for (Item item : items) {
                itemNo++;
                // 获取接口名称
                String itemName = item.getName();
                Api api = item.getApi();
                // 获取请求方法
                String method = api.getMethod();
                // 获取请求路径
                String path = api.getPath();

                // 创建二级标题
                XWPFParagraph heading2 = document.createParagraph();
                // 设置大纲级别为2(二级标题)
                setHeadingLevel(heading2, 2);
                XWPFRun run2 = heading2.createRun();
                String title2 = String.format("%s.%s.%s %s\n", no, categoryNo, itemNo, itemName);
                run2.setText(title2);
                // 加粗
                run2.setBold(true);
                // 字体大小
                run2.setFontSize(14);
                // 字体
                run2.setFontFamily("宋体");

                createNormalContent(document, "接口地址", true);
                createNormalContent(document, String.format("%s %s", method.toUpperCase(), path), false);

                // todo
                System.out.printf("%s.%s.%s %s\n", no, categoryNo, itemNo, itemName);
                System.out.println("接口地址");
                System.out.printf("%s %s\n", method.toUpperCase(), path);

                // 创建请求参数表格数据
                RequestData requestDataHead = new RequestData();
                requestDataHead.setName("名称");
                requestDataHead.setPosition("位置");
                requestDataHead.setType("类型");
                requestDataHead.setRequired("必须");
                requestDataHead.setDescription("描述");
                List<RequestData> requestDataList = new ArrayList<>();
                requestDataList.add(requestDataHead);

                Parameter parameters = api.getParameters();
                List<Query> querys = parameters.getQuery();
                RequestBody requestBody = api.getRequestBody();

                createNormalContent(document, "请求参数", true);

                // todo
                System.out.println("请求参数");

                // 如果有query参数
                if (!CollectionUtils.isEmpty(querys)) {

                    for (Query query : querys) {
                        RequestData requestData = new RequestData();
                        requestData.setName(query.getName());
                        requestData.setPosition("query");
                        requestData.setType(query.getType());
                        requestData.setRequired(query.getRequired() ? "是" : "否");
                        requestData.setDescription(query.getDescription());

                        requestDataList.add(requestData);
                    }
                }
                // 如果为body传输数据
                if ("application/json".equals(requestBody.getType())) {
                    JsonSchema jsonSchema = requestBody.getJsonSchema();
                    String ref = jsonSchema.getRef();

                    requestDataList.addAll(getSchemaRequestData(ref));
                }
                // 其他情况如文件上传
                if (!CollectionUtils.isEmpty(requestBody.getParameters())) {
                    for (Query query : requestBody.getParameters()) {
                        RequestData requestData = new RequestData();
                        requestData.setName(query.getName());
                        requestData.setPosition("body");
                        requestData.setType(query.getType());
                        requestData.setRequired(query.getRequired() ? "是" : "否");
                        requestData.setDescription(query.getDescription());

                        requestDataList.add(requestData);
                    }
                }

                // 创建表格
                createTable(document, requestDataList.stream().map(RequestData::list).collect(Collectors.toList()));

                for (RequestData requestData : requestDataList) {

                    // todo
                    System.out.printf("%s %s %s %s %s\n", requestData.getName(), requestData.getPosition(),
                        requestData.getType(), requestData.getRequired(), requestData.getDescription());
                }

                createNormalContent(document, "响应参数", true);

                // todo
                System.out.println("响应参数");

                // 创建响应参数表格数据
                ResponseData responseDataHead = new ResponseData();
                responseDataHead.setName("名称");
                responseDataHead.setType("类型");
                responseDataHead.setDescription("描述");
                List<ResponseData> responseDataList = new ArrayList<>();
                responseDataList.add(responseDataHead);

                List<Response> responses = api.getResponses();
                if (!CollectionUtils.isEmpty(responses)) {
                    // 只取第一个成功响应
                    Response response = responses.get(0);
                    // 一般来说对于一个项目都是有统一返回类的,所以响应参数基本都是取jsonSchema字段里引用的id,这个id对应SchemaCollection中item的id
                    JsonSchema jsonSchema = response.getJsonSchema();
                    String ref = jsonSchema.getRef();
                    if (ref != null) {
                        getSchemaResponseData(ref, responseDataList, 0);
                    }
                }

                // 创建表格
                createTable(document, responseDataList.stream().map(ResponseData::list).collect(Collectors.toList()));

                for (ResponseData responseData : responseDataList) {

                    // todo
                    System.out.printf("| %s | %s | %s |\n", responseData.getName(), responseData.getType(),
                        responseData.getDescription());
                }

                createNormalContent(document, "响应示例", true);

                // todo
                System.out.println("响应示例");

                List<ResponseExample> responseExamples = api.getResponseExamples();
                if (!CollectionUtils.isEmpty(responseExamples)) {
                    // 只取第一个成功响应示例
                    ResponseExample responseExample = responseExamples.get(0);
                    responseExample.getData();

                    createCodeContent(document, responseExample.getData());

                    // todo
                    System.out.println(responseExample.getData());
                }
            }
        }

        // 将文档保存到文件
        try (FileOutputStream out = new FileOutputStream(outputFileName)) {
            document.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 关闭文档
        document.close();

        System.out.println("Word文档创建成功!");
    }

    /**
     * 创建文档内容
     *
     * @param document document
     * @param content 内容
     * @param bold 是否加粗
     */
    private static void createNormalContent(XWPFDocument document, String content, boolean bold) {
        XWPFParagraph pathContent = document.createParagraph();
        XWPFRun pathRunContent = pathContent.createRun();
        pathRunContent.setText(content);
        pathRunContent.setBold(bold);
        pathRunContent.setFontFamily("宋体");
    }

    /**
     * 创建代码块
     *
     * @param document document
     * @param content 代码块内容
     */
    private static void createCodeContent(XWPFDocument document, String content) {
        // 按行拆分内容,因为换行符\n在word里显示是一个空格,会导致格式混乱
        String[] lines = content.split("\n");

        for (String line : lines) {
            // 为每一行创建一个段落
            XWPFParagraph paragraph = document.createParagraph();
            XWPFRun run = paragraph.createRun();
            run.setText(line); // 添加文本
            run.setFontFamily("Consolas"); // 设置等宽字体
            run.setFontSize(10);

            // 设置代码块背景颜色
            CTShd shd = paragraph.getCTP().addNewPPr().addNewShd();
            shd.setFill("F0F0F0"); // 设置背景颜色为浅灰色
            shd.setVal(STShd.CLEAR);
        }
    }

    /**
     * 创建表格
     *
     * @param document document
     * @param list 表格内容
     */
    private static void createTable(XWPFDocument document, List<List<String>> list) {
        int row = list.size();
        int col = list.get(0).size();
        // 只有一行说明仅有head,不创建表格,展示内容为无
        if (row == 1) {
            createNormalContent(document, "无", false);
            return;
        }

        // 创建表格
        XWPFTable table = document.createTable(row, col);
        table.setWidth("100%");
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                table.getRow(i).getCell(j).setText(list.get(i).get(j));
            }
        }
    }

    /**
     * 获取请求表格数据
     *
     * @param schemaId 引用的schemaId
     * @return
     */
    private static List<RequestData> getSchemaRequestData(String schemaId) {
        List<RequestData> list = new ArrayList<>();

        if (schemaId == null) {
            return list;
        }

        Item item = getSchemaItemById(schemaId);
        Schema schema = item.getSchema();
        JsonSchema jsonSchema = schema.getJsonSchema();
        List<String> required = new ArrayList<>();
        HashSet<String> requiredSet = new HashSet<>();
        if (!CollectionUtils.isEmpty(jsonSchema.getRequired())) {
            requiredSet.addAll(jsonSchema.getRequired());
        }
        LinkedHashMap<String, JSONObject> properties = jsonSchema.getProperties();
        for (Map.Entry<String, JSONObject> entry : properties.entrySet()) {
            String key = entry.getKey();
            JSONObject value = entry.getValue();

            RequestData requestData = new RequestData();
            // 获取type字段,如果没有该字段说明是引用,并且type字段可能为数组也可能为字符串
            String typeString = value.getString("type");
            if (typeString != null) {
                String type;
                if (typeString.startsWith("[")) {
                    JSONArray typeArray = value.getJSONArray("type");
                    // typeArray的值如果是数组,如: ["string", "null"],只取第一个
                    type = (String)typeArray.get(0);

                } else {
                    type = typeString;
                }

                requestData.setName(key);
                requestData.setPosition("body");
                requestData.setType(type);
                requestData.setDescription(value.getString("description"));
                if (requiredSet.contains(key)) {
                    requestData.setRequired("是");
                } else {
                    requestData.setRequired("否");
                }
                list.add(requestData);

            }
        }

        return list;
    }

    /**
     * 获取响应表格数据,涉及到多层引用,使用递归处理
     *
     * @param schemaId 引用的schemaId
     * @param list 响应数据
     * @param level 递归层级
     */
    private static void getSchemaResponseData(String schemaId, List<ResponseData> list, int level) {
        if (schemaId == null) {
            return;
        }

        // 子字段添加前缀
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < level; i++) {
            builder.append("  ");
        }
        if (level != 0) {
            builder.append("└");
        }
        String namePrefix = builder.toString();

        Item item = getSchemaItemById(schemaId);
        Schema schema = item.getSchema();
        JsonSchema jsonSchema = schema.getJsonSchema();
        LinkedHashMap<String, JSONObject> properties = jsonSchema.getProperties();
        for (Map.Entry<String, JSONObject> entry : properties.entrySet()) {
            String key = entry.getKey();
            JSONObject value = entry.getValue();

            ResponseData responseData = new ResponseData();
            // 获取type字段,如果没有该字段说明是引用,并且type字段可能为数组也可能为字符串
            String typeString = value.getString("type");
            if (typeString != null) {
                String type;
                if (typeString.startsWith("[")) {
                    JSONArray typeArray = value.getJSONArray("type");
                    // typeArray的值如果是数组,如: ["string", "null"],只取第一个
                    type = (String)typeArray.get(0);

                } else {
                    type = typeString;
                }

                responseData.setName(namePrefix + key);
                responseData.setType(type);
                responseData.setDescription(value.getString("description"));
                list.add(responseData);

                // 数组类型为引用需要继续递归
                if ("array".equals(type)) {
                    JSONObject items = value.getJSONObject("items");
                    String ref = items.getString("$ref");
                    getSchemaResponseData(ref, list, level + 1);
                }
            } else {
                String ref = value.getString("$ref");
                String description = value.getString("description");
                responseData.setName(namePrefix + key);
                responseData.setType("object");
                responseData.setDescription(description);
                list.add(responseData);

                // 引用类型需要继续递归
                getSchemaResponseData(ref, list, level + 1);
            }
        }
    }

    /**
     * 根据schemaId获取Item
     *
     * @param schemaId 引用的schemaId
     * @return
     */
    private static Item getSchemaItemById(String schemaId) {
        for (Item schemaItem : schemaItems) {
            if (schemaItem.getId().equals(schemaId)) {
                return schemaItem;
            }
        }
        throw new RuntimeException("找不到id为" + schemaId + "的schema,请检查源文件数据");
    }

    /**
     * 设置段落的大纲级别
     *
     * @param paragraph 段落
     * @param level 大纲级别(1为一级标题,2为二级标题,以此类推)
     */
    private static void setHeadingLevel(XWPFParagraph paragraph, int level) {
        CTP ctp = paragraph.getCTP();
        CTPPr ppr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
        ppr.addNewOutlineLvl().setVal(BigInteger.valueOf(level - 1));
    }
}

2.4 文档示例

导出的文档示例如下
在这里插入图片描述

3.结尾

我目前也只是根据有的几个项目的json文件分析的,可能有些类型没考虑到,比如ai对话常用的流式输出,还有的项目post请求却使用form表单传输字段数据等;文档的样式也可以自行修改代码poi操作更改样式或者用wps、word工具打开修改样式;文档的格式也可以根据代码自行调整

最后别忘了检查一下文档,以免错误

### 实现 Android Widget 点击事件 为了实现在 Android 中的 Widget 点击事件处理,可以创建 `ComponentName` 对象来指定广播接收器类。通过使用 `RemoteViews` 设置点击监听器并发送广播消息给组件名称所指向的广播接收者。 ```java thisWidget = new ComponentName(context, PictureAppWidgetProvider.class); ``` 当用户点击 Widget 上的一个按钮或其他可交互视图时,可以通过设置 `PendingIntent` 来响应这些操作[^2]。具体来说,在更新 AppWidget 的时候,定义一个带有特定动作字符串的意图,并将其封装到 `PendingIntent` 中: ```java // 创建 Intent 并设置 Action 字符串用于区分不同类型的点击事件 Intent intent = new Intent(context, PictureAppWidgetProvider.class); intent.setAction(ACTION_WIDGET_CLICK); // 将此 Intent 转换为 PendingIntent 类型以便 RemoteViews 使用 PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // 获取要配置点击行为的 RemoteViews 对象 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); // 给目标 View (例如 Button) 添加点击事件处理器 views.setOnClickPendingIntent(R.id.button_id, pendingIntent); ``` 最后一步是在广播接收器内部捕获该点击事件的动作,并执行相应的逻辑处理。这通常在一个继承自 `AppWidgetProvider` 或其他形式的广播接收者的类里完成: ```java @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); String action = intent.getAction(); if (ACTION_WIDGET_CLICK.equals(action)) { // 处理点击事件的具体业务逻辑... } } ``` 上述代码片段展示了如何在 Android 应用程序的小部件上添加基本的点击反馈机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值