Kotlin+SpringBoot与Swagger生成API文档详解

1、前言与目的

工程架构,请看我之前撰写的 Kotlin +SpringBoot + MyBatis完美搭建最简洁最酷的前后端分离框架进行完善

  • 使用Swagger开放源码和专业工具集,为用户、团队和企业简化API开发。

  • 简单的来说,Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务的接口文档。

  • 减少开发人员编写API文档,可以通过代码生成器生成API文档。

2、API工具有哪些呢?

  • 这里列举一下常见的工具清单,以及常见比较好用的推荐给大家伙使用
名称 官方 说明
Swagger https://swagger.io/ 推荐
Apidocjs http://apidocjs.com/ 推荐
confluence https://www.atlassian.com/software/confluence 推荐
showdoc https://www.showdoc.cc/
eolinker https://www.eolinker.com/#/
MinDoc https://www.iminho.me/
apizza https://apizza.net/pro/#/
RAP http://rapapi.org/org/index.do
raml https://raml.org/
APIJSON https://jsonapi.org/
  • 为了手机的布局问题,这里提供图片更为直观


    API工具.png
  • 由于前三者我都用过,经过开发过程中个人觉得最快生成的文档的是Swagger和Apidocjs,而且confluence需要自己写文档,从时间上的成本会高点,如果说到规范文档这方面肯定它比前2者会更优

  • apidocjs ,这个生成方式需要安装NodeJS,定好模板,然后使用Nodejs命令进行生成相应的接口API文档,只需要懂点简单的语法和规则即可,不需要像Swagger那么麻烦要整合Spring或SpringBoot等方式,然后再打包把工程进行发布前端才可以看到相应的API,然而apidocjs 生成静态的Html即可。

  • 其他无使用过,不发表其他建议。希望通过以上的列表,根据自己的情况进行选择与学习

3、springfox大致原理:

  • springfox的大致原理就是,在项目启动的过种中,spring上下文在初始化的过程,框架自动跟据配置加载一些swagger相关的bean到当前的上下文中,并自动扫描系统中可能需要生成api文档那些类, 并生成相应的信息缓存起来。如果项目MVC控制层用的是springMvc那么会自动扫描所有Controller类,并生成对应的文档描述数据.

  • 该数据是json格式,通过路径:项目地址/ v2/api-docs可以访问到该数据,然后swaggerUI根据这份数据生成相应的文档描述界面。
    因为我们能拿到这份数据,所以我们也可以生成自己的页面.

4、Swagger注解详解

ApiModel 属性

属性名称 数据类型 默认值 说明
value String 类名 为模型提供备用名称
description String "" 提供详细的类描述
parent Class<?> parent Void.class 为模型提供父类以允许描述继承关系
discriminatory String "" 支持模型继承和多态,使用鉴别器的字段的名称,可以断言需要使用哪个子类型
subTypes Class<?>[] {} 从此模型继承的子类型数组
reference String "" 指定对应类型定义的引用,覆盖指定的任何其他元数据
  • 为了手机的布局问题,这里提供图片更为直观


    ApiModel .png

ApiModelProperty 属性

属性名称 数据类型 默认值 说明
value String "" 属性简要说明
name String "" 运行覆盖属性的名称。重写属性名称
allowableValues String "" 限制参数可接收的值,三种方法,固定取值,固定范围
access String "" 过滤属性,参阅:io.swagger.core.filter.SwaggerSpecFilter
notes String "" 目前尚未使用
dataType String "" 参数的数据类型,可以是类名或原始数据类型,此值将覆盖从类属性读取的数据类型
required boolean false 是否为必传参数,false:非必传参数; true:必传参数
position int 0 允许在模型中显示排序属性
hidden boolean false 隐藏模型属性,false:不隐藏; true:隐藏
example String "" 属性的示例值
readOnly boolean false 指定模型属性为只读,false:非只读; true:只读
reference String "" 指定对应类型定义的引用,覆盖指定的任何其他元数据
allowEmptyValue boolean false 允许传空值,false:不允许传空值; true:允许传空值
  • 为了手机的布局问题,这里提供图片更为直观


    ApiModelProperty .png

5、代码实现

swagger 依赖的jar

<!--swagger 依赖-->
  <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
        <version>2.8.0</version>
  </dependency>
 <dependency>
    <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.8.0</version>
</dependency>
  • 第一种方式 SwaggerConfiguration 代码实现

/*
  * 访问路径: http://localhost:8080/swagger-ui.html#/
 */
@EnableSwagger2 // 启用Swagger
@Configuration
open class SwaggerConfiguration {
    @Bean
    open fun createRestApi(): Docket {   
         return  Docket(DocumentationType.SWAGGER_2).select()
          .apis(RequestHandlerSelectors
          .withMethodAnnotation(ApiOperation::class.java)).build();
    }
    
}
  • 第二种方式 SwaggerConfiguration 代码实现


/*
  * 访问路径: http://localhost:8080/swagger-ui.html#/
 */
@EnableSwagger2 // 启用Swagger
open class SwaggerConfiguration {

    companion object {
        const val SWAGGER_SCAN_BASE_PACKAGE = "com.flong";
        const val VERSION = "1.0.0";
    }

    @Bean
    open fun createRestApi(): Docket {
        return Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
        .apis(RequestHandlerSelectors.basePackage(SWAGGER_SCAN_BASE_PACKAGE))
          //  api接口包扫描路径
         // 只对restApi路径下面的所有连接请求
        //.paths(regex("/restApi.*"))
        // 可以根据url路径设置哪些请求加入文档,忽略哪些请求
        .paths(PathSelectors.any()) .build();
        
    }

    fun apiInfo(): ApiInfo {
            // 设置文档的标题
        return ApiInfoBuilder().title("Swagger2 接口文档示例")
            // 设置文档的描述->1.Overview
            .description("更多内容请关注:https://github.com/jilongliang")
            // 设置文档的版本信息-> 1.1 Version information
            .version(VERSION)
            .contact("liangjl")
             // 设置文档的License信息->1.3 License information
            .termsOfServiceUrl("www.apache.org")
            .build();
    }
}
  • Model代码配置


/**
 *data保持数据data class就是一个类中只包含一些数据字段,类似于vo,pojo,java bean。一般而言,
 *我们在Java中定义了这个数据类之后要重写一下toString,equals等方法。要生成get,set方法
 *https://www.cnblogs.com/liuliqianxiao/p/7152773.html
 *注意mybatis查询数据,然后封装实体的时候,构造方法这里有点儿坑,查询的字段必须与构造方法一直。
 * 
 * @ApiModelProperty()用于方法,字段; 表示对model属性的说明或者数据操作更改 
 * value–字段说明 
 * name–重写属性名字 
 * dataType–重写属性类型 
 * required–是否必填 
 * example–举例说明
 * hidden–隐藏
 * notes用于提示内容 
 */
@TableName("t_user")
@ApiModel(value = "用户对象", description = "用户对象")
data class User constructor(
    
    @TableId(value = "user_id", type = IdType.ID_WORKER)
    @ApiModelProperty(value = "用户Id",notes = "用户Id")
    var userId: Long?= null,//用户Id主键,IdWork生成
    
    @ApiModelProperty(value = "用户名称",notes ="用户名称")
    var userName: String? = null,//用户名
    
    
    @ApiModelProperty(value = "用户密码",notes ="用户密码")
    var passWord: String? = null,//密码
    
    @TableLogic
    @JSONField(name = "isDeleted")
    @ApiModelProperty(value = "删除状态,0-未删除,1-已删除",notes ="删除状态,0-未删除,1-已删除")
    var isDeleted: Int? = null,//删除
    
    @ApiModelProperty(value = "创建时间",notes ="创建时间")
    var createTime: Date? = null //创建时间,允许为空,让数据库自动生成即可

  ) :Serializable{
 
    //手动重写toString方法
    override fun toString(): String {
        return "[User(userId = $userId,userName = $userName, passWord=$passWord,isDeleted=$isDeleted,createTime=$createTime)]"
    }

    //equals
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as User
        if (userId != other.userId) return false
        if (userName != other.userName) return false
        if (passWord != other.passWord) return false
        if (isDeleted != other.isDeleted) return false
        if (createTime != other.createTime) return false
        return true
    }
    override fun hashCode(): Int {
        var result = userId?.hashCode() ?: 0
        result = 31 * result + (userName?.hashCode() ?: 0)
        result = 31 * result + (passWord?.hashCode() ?: 0)
        result = 31 * result + (isDeleted?.hashCode() ?: 0)
        result = 31 * result + (createTime?.hashCode() ?: 0)
      
        return result
    }

}
  • Controller代码


@RestController
@RequestMapping("/rest")
@Api("用户模块")
open class UserController : BaseController(){
    
    @Autowired private lateinit var userService: UserService
 
    
    @ApiOperation(value="调整页面")
    @GetMapping("/list1")
    fun list1():  String{
        return "NewFile" //跳转页面
    }
    
    //添加
    @ApiOperation(value="添加用户")
    @PostMapping("/add")
    fun add():  Unit{
        userService.addUser()
    }
    
    //删除
    @ApiOperation(value="删除用户")
    @DeleteMapping("/deletedById")
    fun deletedById(userId : Long):  Unit{
        userService.deleteById(userId);
    }
    //更新
    @ApiOperation(value="更新用户")
    @PostMapping("/update")
    fun update(user : User):  Unit{
        userService.updateById(user)
    }
    //根据Id查询用户
    @ApiOperation(value="根据Id查询用户")
    @GetMapping("/getUserId")
    fun getUserId(userId :Long):Any?{
        var user = userService.getUserId(userId);
        if(user ==null){
            var msgCode = UserMsgCode.FIND_NOT_USER;
            throw BaseException(msgCode.code!! ,msgCode.message!!)
        }
        return userService.getUserId(userId)
    }
    
    //查询所有
    @ApiOperation(value="查询所有用户信息")
    @GetMapping("/queryAllUser")
    fun queryAllUser():List<User>?{
        return userService.queryAllUser()
    }
    //分页查询
    @ApiOperation(value="用户列表分页",notes="用户列表分页")
    @GetMapping("listPage")
    fun listPage(query :UserQuery) :PageVO<UserRespVo> ? {
        var listPage = userService.listPage(query);
        return listPage;
    }
    
}
  • Application 入口启动代码

@EnableAsync
@EnableSwagger2
@Configuration
@EnableScheduling
@EnableAutoConfiguration  //启用读取配置
@ComponentScan("com.flong.kotlin")  //扫描com.flong文件目录下
@SpringBootApplication(scanBasePackages = ["com.flong.kotlin"] )
//@SpringBootApplication(scanBasePackages = arrayOf("com.flong.kotlin")) 这种写法也OK
open class Application :WebMvcConfigurerAdapter{
    //无参数构造方法,为了解决WebMvcConfigurerAdapter类的内部问题,
    constructor()
    
  /**添加swagger-ui的资源文件拦截器的支持addResourceHandlers是
  WebMvcConfigurerAdapter里面的 */
    override fun addResourceHandlers( registry:ResourceHandlerRegistry) {
        // super.addResourceHandlers(registry);
        registry.addResourceHandler("/WEB-INF/views/**").addResourceLocations("/WEB-INF/views/");
        /** 添加swagger-ui的资源文件拦截器的支持 */
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
    @Bean
    open fun jspViewResolver(): InternalResourceViewResolver {
        var resolver = InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    //静态类
    companion object {
        /**启动SpringBoot的主入口.*/
        @JvmStatic fun main(args: Array<String>) {
            //*args的星号表示引用相同类型的变量
            SpringApplication.run(Application::class.java, *args)
        }
    }
}

注意点:
constructor()无参数构造方法,为了解决WebMvcConfigurerAdapter类的内部问题,实现 WebMvcConfigurerAdapter此类主要是为addResourceHandlers了添加加swagger-ui的资源文件拦截器的支持

6、运行接口

  • Swagger-UI.png
  • Model实体类

7、工程代码

工程代码在本人的GitHub的dev-swagger分支上

8 、总结与建议

1 、以上问题根据搭建 kotlin与Swagger 实际情况进行总结整理,除了技术问题查很多网上资料,通过自身进行学习之后梳理与分享。

2、 在学习过程中也遇到很多困难和疑点,如有问题或误点,望各位老司机多多指出或者提出建议。本人会采纳各种好建议和正确方式不断完善现况,人在成长过程中的需要优质的养料。

3、 希望此文章能帮助各位老铁们更好去了解如何在 kotlin上搭建Swagger,也希望您看了此文档或者通过找资料亲身经历实操学习效果会更好。

备注:此文章属于本人原创,欢迎转载和收藏.

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