基于swagger-decorator的实体类自动构建以及JavaScript中Swagger接口文档的生成在标注项目中,使用swagger-decorator添加适当的实体类或接口类注解,从而实现支持嵌套实体类验证生成、Sequelize等ORM模型生成、基于Swagger的接口文档生成等功能。如果你对JavaScript语法的使用还不清楚,可以参考JavaScript学习与实践资料索引或现代JavaScript开发:语法基础与实战系列文章。swagger-decorator:一个注解,多种用途swagger-decorator的初衷是为了简化JavaScript应用的开发。在编写JavaScript应用程序(Web前端&Node.js)时,发现我们经常需要重复创建实体类,添加注解或者进行类型验证,swagger-decorator希望能让开发者在一个地方注解,在其中使用多个地方。需要强调的是,笔者在多年的Java应用开发中也感受到过分过多的注解会大大削弱代码的可读性,因此笔者也建议适时舒舒服服地使用swagger-decorator而不是本末倒置,一味追求标注覆盖率。swagger-decorator已经可以用于实体类的生成和验证,SequelizeORM实体类的生成,面向Koa的路由注解和Swagger文档的自动生成。我们可以使用yarn或npm来安装swagger-decorator依赖项。需要注意的是,因为我们在开发中也会使用注解语法,所以还需要添加babel-plugin-transform-decorators-legacy插件进行语法兼容的转换。#使用npm安装依赖$npminstallswagger-decorator-S$#使用yarn安装依赖$yarnaddswagger-decorator$yarnaddbabel-plugin-transform-decorators-legacy-D#导入需要的工具函数import{wrappingKoaRouter,entityProperty,...}来自“swagger-decorator”;实体类注解swagger-decorator的核心API是对实体类的注解。该注解不会改变实体类的任何属性表现,只会在Built-ininnerEntityObject单例中记录该注解限制的属性特性,以备后用。属性注解entityProperty的方法描述如下:/***Description创建一个属性的描述*@paramtype基本类型self-表示为自身*@paramdescription描述*@paramrequired是否为必要参数*@paramdefaultValue默认值*@parampattern*@paramprimaryKey是否主键*@returns{Function}*/exportfunctionentityProperty({//生成接口文档需要的参数type="string",description="",required=false,defaultValue=undefined,//验证需要的参数pattern=undefined,//连接数据库需要的参数primaryKey=false}){}简单的用户实体类注解如下,其中数据类型类型支持Swagger的默认字符格式类型描述,也支持直接使用JavaScript类名或JavaScript数组。//@flowimport{entityProperty}from"../../src/entity/decorator";importUserPropertyfrom"./UserProperty";/***Description用户实体类*/exportdefaultclassUser{//编号@entityProperty({type:"integer",description:"userid,auto-generated",required:true})id:string=0;//姓名@entityProperty({type:"string",description:"用户名,3~12个字符",required:false})name:string="name";//邮箱@entityProperty({type:"string",description:"useremail",pattern:"email",required:false})email:string="email";//属性@entityProperty({type:UserProperty,description:"userproperty",required:false})property:UserProperty=newUserProperty();}exportdefaultclassUserProperty{//朋友列表@entityProperty({type:["number"],description:"userfriends,whichisuserids",required:false})friends:[number];}Swagger内部设置数据类型定义:CommonNametypeformatCommentsintegerintegerint32signed32bitslongintegerint64signed64bitsfloatnumberfloatdoublenumberdoublestringstringbytestringbytebase64encodedcharactersbinarystringbinaryanysequenceofoctetsbooleanbooleandatestringdateAsdefinedbyfull-date-RFC3339dateTimestringdate-timeAsdefinedbydate-time-RFC3339passwordstringpasswordUsedtohintUIstheinputneedstobeobscured.实例生成与校验实体类定义完毕之后,我们可以先使用instantiate函数为实体类生成一个实例;与直接使用new关键字创建不同,instantiate可以根据指定属性的数据类型或格式进行校验,并可以迭代生成嵌套的子对象/***描述从实体类生成对象并进行数据校验;注意这里会进行递归生成,即同时生成实体类对象*@paramEntityClass实体类*@paramdata数据对象*@paramignore是否忽略验证*@paramstrict是否忽略非预定义类attributes*@throws验证失败时抛出异常*/exportfunctioninstantiate(EntityClass:Function,data:{[string]:any},{ignore=false,strict=true}:{ignore:boolean,strict:boolean}={}):Object{}这里为了描述方便,用Jest测试用例来说明不同的使用场景:describe("测试实体类实例化函数",()=>{test("测试用户类实例化验证",()=>{expect(()=>{instantiate(User,{name:"name"}).toThrowError(/validatefail!/);});letuser=instantiate(User,{id:0,name:"name",email:"a@q.com"});//判断是否为User实例expect(user).toBeInstanceOf(User);});test("测试忽略参数即可允许忽略验证",()=>{instantiate(User,{name:"name"},{ignore:true});});test("测试严格参数可以控制是否忽略额外参数",()=>{letuser=instantiate(User,{name:"name",external:"external"},{ignore:true,strict:true});期望(用户).not.toHaveProperty(“外部”,“外部”);user=instantiate(User,{name:"name",external:"external"},{ignore:true,strict:false});期望(用户).toHaveProperty(“外部”,“外部”);});});describe("测试嵌套实例生成",()=>{test("测试可以递归生成嵌套实体类",()=>{letuser=instantiate(User,{id:0,property:{friends:[0]}});expect(user.property).toBeInstanceOf(UserProperty);});});Sequelize模型生成Sequelize是Node.js应用程序中常用的ORM框架。swagger-decorator提供了generateSequelizeModel函数,方便使用从实体类中已有的信息生成Sequelize对象模型;generateSequelizeModel第一个参数进入实体类,第二个参数进入需要重写的模型属性,第三个参数设置附加属性,比如驼峰命名是否转换为下划线命名等。等等constoriginUserSequelizeModel=generateSequelizeModel(User,{_id:{primaryKey:true}},{mappingCamelCaseToUnderScore:true});constUserSequelizeModel=sequelize.define("b_user",originUserSequelizeModel,{timestampsTables:false,underscored:zetrue,free});UserSequelizeModel.findAll({attributes:{exclude:[]}}).then(users=>{console.log(users);});AutomaticallygenerateannotationsfromFlowtypedeclaration作者习惯使用Flow作为静态JavaScript类型检测工具,因此作者添加了flowToDecorator函数,自动从声明的class文件中提取类型信息按流程;内部原理参考《ModernJavaScriptDevelopment:SyntaxBasicsandPracticalSkills》一书中的JavaScript语法树和代码转换章节。这个函数的使用方法是://@flowimport{flowToDecorator}from'../../../../src/transform/entity/flow/flow';test('测试从Flow中提取数据类型并转换为Swagger接口类',()=>{flowToDecorator('./TestEntity.js','./TestEntity.transformed.js').then(codeStr=>{console.log(codeStr);},err=>{console.error(err);});});这里对应的TestEntity是://@flowimportAnotherEntityfrom"./AnotherEntity";classEntity{//CommentstringProperty:string=0;类属性:实体=空;原始属性;@entityProperty({type:"string",description:"thisispropertydescription",required:true})decoratedProperty;}转换后的实体类是://@flowimport{entityProperty}from'swagger-decorator';importAnotherEntityfrom'./AnotherEntity';classEntity{//Comment@entityProperty({type:'string',required:false,description:'Comment'})stringProperty:string=0;@entityProperty({type:Entity,required:false})classProperty:Entityty=空;@entityProperty({type:'string',required:false})rawProperty;@entityProperty({type:'string',description:'thisispropertydescription',required:true})decoratedProperty;}接口注解用Swagger文档生成Swagger文档规范可以参考OpenAPI规范,对于swagger-decorator的实际使用,可以参考本项目的使用示例或者基于Koa2的Node.js应用模板封装路径由对象import{wrappingKoaRouter}from"swagger-decorator";...constRouter=require("koa-router");constrouter=newRouter();wrappingKoaRouter(router,"localhost:8080","/api",{title:"NodeServerBoilerplate",version:"0.0.1",description:"Koa2,koa-router,Webpack"});//定义默认路由router.get("/",asyncfunction(ctx,next){ctx.body={msg:"NodeServerBoilerplate"};});//使用扫描在classrouter.scan(UserController)中自动添加方法;定义接口类exportdefaultclassUserControllerextendsUserControllerDoc{@apiRequestMapping("get","/users")@apiDescription("getalluserslist")staticasyncgetUsers(ctx,next):[User]{ctx.body=[newUser()];}@apiRequestMapping("get","/user/:id")@apiDescription("通过id获取用户对象,只能访问自己或好友")staticasyncgetUserByID(ctx,next):User{ctx.body=newUser();}@apiRequestMapping("post","/user")@apiDescription("createnewuser")staticasyncpostUser():number{ctx.body={statusCode:200};}}负责UserController中的具体业务实现。为了避免过多的注解文档对代码的可读性造成干扰,作者建议在父类中声明路径和描述以外的信息;swagger-decorator会自动从接口类的直接父类中提取同名方法的描述文档exportdefaultclassUserControllerDoc{@apiResponse(200,"获取用户成功",[User])staticasyncgetUsers(ctx,next):[User]{}@pathParameter({name:"id",description:"userid",type:"integer",defaultValue:1})@queryParameter({name:"tags",description:"用户标签,用于过滤用户",required:false,type:"array",items:["string"]})@apiResponse(200,"getusersuccessful",User)staticasyncgetUserByID(ctx,next):User{}@bodyParameter({name:"user",description:"新用户对象,必须包含用户名",required:true,schema:User})@apiResponse(200,"createnewusersuccessfully",{statusCode:200})staticasyncpostUser():number{}}运行应用程序并打开swagger文档(PS.swagger-decorator包含SwaggerUI):/swagger/swagger/api.json
