当前位置: 首页 > 科技观察

基于swagger-decorator的实体类自动构建以及JavaScript中Swagger接口文档的生成

时间:2023-03-15 15:20:34 科技观察

基于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安装依赖$npminallswagger-decorator-S$#使用yarn安装依赖$yarnaddswagger-decorator$yarnaddbabel-plugin-transform-decorators-legacy-D#导入需要的工具函数import{wrappingKoaRouter,entityProperty,...}from"swagger-decorator";实体类注解swagger-decorator的核心API是对实体类的注解。这个注解不会改变实体类的任何属性表现,只是会在内置的innerEntityObject单例中记录下这个注解限制的属性特性,以备后用。属性注解entityProperty的方法说明如下:/***Description创建一个属性的描述*@paramtype基本类型自表示为自身*@paramdescription描述*@paramrequired为必填参数*@paramdefaultValue默认值*@parampattern*@paramprimaryKey是否为主键*@returns{Function}*/exportfunctionentityProperty({//生成接口文档需要的参数type="string",description="",required=false,defaultValue=undefined,//需要验证Parameterpattern=undefined,//数据库连接需要的参数primaryKey=false}){}简单的用户实体类注解如下,其中数据类型类型支持Swagger默认的字符格式类型描述,也支持直接使用JavaScript类名或JavaScript数组。//@f??lowimport{entityProperty}from"../../src/entity/decorator";importUserPropertyfrom"./UserProperty";/***描述用户实体类*/exportdefaultclassUser{//Number@entityProperty({type:"integer",description:"userid,auto-generated",required:true})id:string=0;//Name@entityProperty({type:"string",description:"用户名,3~12个字符",required:false})name:string="name";//Email@entityProperty({type:"string",description:"useremail",pattern:"email",required:false})email:string="email";//property@entityProperty({type:UserProperty,description:"userproperty",required:false})property:UserProperty=newUserProperty();}exportdefaultclassUserProperty{//friendlist@entityProperty({type:["number"],description:"userfriends,whichsuserids",required:false})friends:[number];}Swagger内置数据类型定义:CommonNametypeformatCommentsintegerintegerint32signed32bitslongintegerint64signed64bitsfloatnumberfloatdoublenumberdoublestringstringbytestringbytebase64encodedcharactersbinarystringbinaryanysequenceofoctetsbooleanbooleandatestringdateAsdefinedbyfull-date-RFC3339dateTimestringdate-timeAsdefinedbydate-time-RFC3339passwordstringpasswordUsedtohintUIstheinputneedstobeobscured.实例生成与校验实体类定义完毕之后,我们首先可以使用instantiateThefunctiongeneratesaninstancefortheentityclass;unlikedirectcreationusingthenewkeyword,instantiatecanverifyaccordingtothedatatypeorformatofthespecifiedattribute,andcaniterativelygeneratenestedsub-objects/***Description从实体类生成对象并进行数据校验;注意这里会进行递归生成,即同时生成实体类对象*@paramEntityClass实体类*@paramdata数据对象*@paramignore是否忽略校验Verify*@paramstrict是否忽略非预定义类属性*@throws验证失败时会抛出异常*/exportfunctioninstantiate(EntityClass:Function,data:{[string]:any},{ignore=false,strict=true}:{ignore:boolean,strict:boolean}={}):Object{}这里为了描述方便,使用Jest测试用例来说明不同的使用场景:/***描述从实体类生成对象并进行数据校验;注意这里会进行递归生成,即同时生成实体类对象*@paramEntityClass实体类*@paramdata数据对象*@paramignore是否忽略校验*@paramstrict是否忽略非预定义类属性*@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});expect(user).not.toHaveProperty("外部“,外部”);用户=实例化(用户,{名称:“名称”,外部:“外部”},{忽略:真,严格:假});期望(用户).toHaveProperty(“外部”,"external");});});describe("测试嵌套实例生成",()=>{test("测试可以递归生成嵌套实体类",()=>{letuser=instantiate(User,{id:0,property:{friends:[0]}});expect(user.property).toBeInstanceOf(UserProperty);});});Sequelize模型生成Sequelize是Node.js应用中常用的ORM框架,swagger-decorator提供generateSequelizeModel函数,方便利用已有的信息从实体类生成Sequelize对象模型;gener的第一个参数ateSequelizeModel为实体类输入,第二个参数为需要覆盖的模型属性输入,第三个参数设置附加属性,如是否将驼峰命名转换为下划线命名等。constoriginUserSequelizeModel=generateSequelizeModel(User,{_id:{primaryKey:true}},{mappingCamelCaseToUnderScore:true});constUserSequelizeModel=sequelize.define("b_user",originUserSequelizeModel,{timestamps:false,underscored:true,freezeTableName:true});UserSequelizeModel.findAll({attributes:{exclude:[]}}).then(users=>{console.log(users);});从Flow类型声明中自动生成注解作者习惯使用Flow作为JavaScript的静态类型检测工具,因此作者添加了flowToDecorator函数来自动从Flow声明的类文件中提取类型信息;内部原理参考《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);},错误=>{console.error(err);});});这里对应的TestEntity是://@flowimportAnotherEntityfrom./AnotherEntity";classEntity{//CommentstringProperty:string=0;classProperty:Entity=null;rawProperty;@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:Entity=null;@entityProperty({type:'string',required:false})rawProperty;@entityProperty({type:'string',description:'thisispropertydescription',required:true})decoratedProperty;}接口注解与Swagger文档生成Swagger文档规范请参考OpenAPI规范,swagger-decorator的实际使用请参考本项目的使用示例或基于Koa2的Node.js应用模板封装路径由对象导入{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"});//definedefaultrouterouter.get("/",asyncfunction(ctx,next){ctx.body={msg:"NodeServerBoilerplate"};});//usescantoautoaddmethodinclassrouter.scan(UserController);定义接口类exportdefaultclassUserControllerextendsUserControllerDoc{@apiRequestMapping("get","/users")@apiDescription("getalluserslist")staticasyncgetUsers(ctx,next):[用户]{ctx.body=[newUser()];}@apiRequestMapping("get","/user/:id")@apiDescription("getuserobjectbyid,onlyaccessselfforfriends")staticasyncgetUserByID(ctx,next):User{ctx.body=newUser();}@apiRequestMapping("post","/user")@apiDescription("createnewuser")staticasyncpostUser():number{ctx.body={statusCode:200};}}负责UserController中的具体业务实现。为了避免过多的注解文档对代码可读性的干扰,作者的建议是在父类声明中放置路径和描述以外的信息;swagger-decorator会自动从接口类的直接父类中提取同名方法的描述文档exportdefaultclassUserControllerDoc{@apiResponse(200,"getuserssuccessfully",[User])staticasyncgetUsers(ctx,next):[User]{}@pathParameter({name:"id",description:"userid",type:"integer",defaultValue:1})@queryParameter({name:"tags",description:"usertags,forfilteringusers",required:false,type:"array",items:["string"]})@apiResponse(200,"getusersuccessfully",User)staticasyncgetUserByID(ctx,next):User{}@bodyParameter({name:"user",description:"thenewuserobject,mustincludeusername",required:true,schema:User})@apiResponse(200,"createnewusersuccessfully",{statusCode:200})staticasyncpostUser():number{}}运行applicationrunyourapplicationandopenswaggerdocs(PS.swagger-decoratorcontainsSwaggerUI):/swagger/swagger/api.json联系]点这里看作者更多好文章