可无注解的 SpringBoot API文档生成工具

JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具。

前言

编写和维护API文档,对于后端程序员来说,是一件恼人但又不得不做的事情,我们都不喜欢写文档,除非项目前后端代码都是自己写的,否则API文档将是前后端协作不可或缺的沟通载体。
最佳实践是:先把接口设计好,在Mock的方法上写注释来生成API文档,这样做到前后端根据API文档并行开发。

为什么引入JApiDocs

相比Swagger要写一堆注解,Spring Rest Docs需要写测试用例,才能生成API文档,JApiDocs只要Controller类和方法的Java注释写完即可手动生成多种格式的API文档。
如下的简单一个参数的方法,Swagger要写两行又臭又长的注解,稍不留神就写错。

Swagger注解示例

先睹为快

没有对比就没有伤害,看一下我的java代码,没有任何多余的注解,只有常规的java规范方法注释。

    /**
     * 示例生成api文档方法
     *
     * @param productId 年龄
     * @param guo       姓氏
     * @return 商品库存DTO
     */
    @PostMapping("/apiDocDemo")
    public ProductReduceStockDTO apiDocDemo(@RequestParam Long productId, @RequestParam String guo) {
        return new ProductReduceStockDTO().setProductId(productId);
    }

生成后的Java API Doc如图:
API Doc效果图

快速上手

第一步

添加依赖

<dependency>
  <groupId>io.github.yedaxia</groupId>
  <artifactId>japidocs</artifactId>
  <version>1.4</version>
</dependency>

第二步

配置参数
你可以在任意一个类添加main方法,运行下面的代码:

DocsConfig config = new DocsConfig();
config.setProjectPath("your springboot project path"); // 项目根目录
config.setProjectName("ProjectName"); // 项目名称
config.setApiVersion("V1.0");       // 声明该API的版本
config.setDocsPath("your api docs path"); // 生成API 文档所在目录
config.setAutoGenerate(Boolean.TRUE);  // 配置自动生成
Docs.buildHtmlDocs(config); // 执行生成文档

如果没有意外,执行完上面的代码后,你就可以在配置的目录中看到生成的文档了。

编码规范

JApiDocs是通过解析Java源码来实现的,要使得JApiDocs正确工作,需要你在项目中的Controller书写遵循一定的编码规范。

1. 添加必要的代码注释

其中类注释会对应到一级接口分组,你也可以通过@description来指定分组名称;JApiDocs 会通过 @param 来寻找接口参数和进一步解析参数的内容。

package cn.iocoder.springboot.lab52.productservice.controller;

import cn.iocoder.springboot.lab52.productservice.dto.ProductReduceStockDTO;
import cn.iocoder.springboot.lab52.productservice.service.ProductService;
import io.github.yedaxia.apidocs.Docs;
import io.github.yedaxia.apidocs.DocsConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @description 商品相关操作控制器
 * @ClassName: ProductController
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/6/23 20:56
 * @Copyright:
 */
@RestController
@RequestMapping("/product")
public class ProductController {

    private Logger logger = LoggerFactory.getLogger(ProductController.class);

    @Autowired
    private ProductService productService;


    /**
     * 减库存操作
     *
     * @param productReduceStockDTO 商品及库存的DTO,用于减库存。
     * @param guo                   示例参数无意义。
     * @return
     */
    @PostMapping("/reduce-stock")
    public Boolean reduceStock(@RequestBody ProductReduceStockDTO productReduceStockDTO, @RequestParam String guo) {
        logger.info(guo + "[reduceStock] 收到减少库存请求, 商品:{}, 价格:{}", productReduceStockDTO.getProductId(),
                productReduceStockDTO.getAmount());
        try {
            productService.reduceStock(productReduceStockDTO.getProductId(), productReduceStockDTO.getAmount());
            // 正常扣除库存,返回 true
            return true;
        } catch (Exception e) {
            // 失败扣除库存,返回 false
            return false;
        }
    }

    /**
     * 示例生成api文档方法
     *
     * @param productId 年龄
     * @param guo       姓氏
     * @return 商品库存DTO
     */
    @PostMapping("/apiDocDemo")
    public ProductReduceStockDTO apiDocDemo(@RequestParam Long productId, @RequestParam String guo) {
        return new ProductReduceStockDTO().setProductId(productId);
    }

    public static void main(String[] args) {
        DocsConfig config = new DocsConfig();
        config.setProjectPath("D:\\dev\\GitRepository\\seata-at-httpclient-demo\\seata-at-httpclient-demo-product-service"); // 项目根目录
        config.setProjectName("商品服务"); // 项目名称
        config.setApiVersion("V1.0");       // 声明该API的版本
        config.setDocsPath("d:\\Japidoc"); // 生成API 文档所在目录
        config.setAutoGenerate(Boolean.TRUE);  // 配置自动生成
        Docs.buildHtmlDocs(config); // 执行生成文档
    }
}

/**
 * 商品减少库存 DTO
 */
public class ProductReduceStockDTO {

    /**
     * 商品编号
     */
    private Long productId;
    /**
     * 数量
     */
    private Integer amount;
    省略getter、setter方法,属性的注释将在文档中被使用.......

如果提交的表单是 application/x-www-form-urlencoded 类型的key/value格式,你可以在 SpringBoot 端通过在 @param 参数后添加字段解释或者在相关的JavaBean对象里面添加解释:

// 直接在java的 @param 注解中
@param userId 用户ID
// 在FormBean对象中
public class UserListForm extends PageForm{
    private Integer status; //用户状态
    private String name; //用户名
}

这种格式对于到文档中的参数描述将是表格的形式:

参数名 类型 必须 描述
status int 用户状态
name string 用户名

如果提交的表单是 application/json 类型的json数据格式,对应 SpringBoot 中的 @RequestBody 注解,在文档中则是 json 格式显示:

{
  "id": "long //用户ID",
  "name": "string //用户名",
  "phone": "long //电话",
  "avatar": "string //头像",
  "gender": "byte //性别"
}

2. 接口声明返回对象

我们知道,如果Controller声明了@RestController,SpringBoot会把返回的对象直接序列成Json数据格式返回给前端。 JApiDocs也利用了这一特性来解析接口返回的结果,但由于JApiDocs是静态解析源码的,因此你要明确指出返回对象的类型信息,JApiDocs支持继承、泛型、循环嵌套等复杂的类解析。

比如的apiDocDemo接口:


    /**
     * 示例生成api文档方法
     *
     * @param productId 年龄
     * @param guo       姓氏
     * @return 商品库存DTO
     */
    @PostMapping("/apiDocDemo")
    public ProductReduceStockDTO apiDocDemo(@RequestParam Long productId, @RequestParam String guo) {
        return new ProductReduceStockDTO().setProductId(productId);
    }

ProductReduceStockDTO表明了该接口返回的数据结构,经过JApiDocs处理后是这样的:

{
  "productId": "long //商品编号",
  "amount": "int //数量"
}

如果你不是通过返回对象的形式,你也可以通过JApiDocs提供的@ApiDoc注解来声明返回类型,你可以参考@ApiDoc章节的相关配置内容。

3. @ApiDoc

JApiDocs 默认只导出声明了@ApiDoc的接口,我们前面通过设置 config.setAutoGenerate(Boolean.TRUE) 来解除了这个限制。

如果你不希望把所有的接口都导出,你可以把autoGenerate设置关闭,在相关Controller类或者接口方法上通过添加@ApiDoc来确定哪些接口需要导出。

@ApiDoc声明在接口方法上的时候,它还拥有一些更灵活的设置,下面我们来看一下:

  • result: 这个可以直接声明返回的对象类型,如果你声明了,将会覆盖SpringBoot的返回对象
  • url: 请求URL,扩展字段,用于支持非SpringBoot项目
  • method: 请求方法,扩展字段,用于支持非SpringBoot项目

例子:

@ApiDoc(result = AdminVO.class, url = "/api/v1/admin/login2", method = "post")

4. @Ignore

如果你不想导出对象里面的某个字段,可以给这个字段加上@Ignore注解,这样JApiDocs导出文档的时候就会自动忽略掉了:

例子:

public class UserForm{
    @Ignore
    private Byte gender; //性别
}

导出更多格式

导出markdown

config.addPlugin(new MarkdownDocPlugin());

导出 pdf 或者 word

你可以通过 pandoc 把 markdown 格式转成 pdf 或者 word 格式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361