Panda api 高级使用说明

全局字段属性,不需要重复写相同的字段

我们在上一篇的简易教学文章中,用户登录接口和用户退出接口的response返回字段,都有一个code字段和msg字段,

response:{
    code:{...},
    msg:{...}
}

实际上,很有可能,我们整个系统的接口,都遵循同样的标准,所有接口都有code字段和msg字段,那么,每个地方的body和response都去写岂不是很麻烦;另外,可能我们整个网站绝大部分的接口都是要登录的,只有很少的一小部分不需要登录,每个都去设置也很麻烦啊。我承认就是懒,所以Panda api提供了一个简单的全局设置方法。

打开_settings.json5文件

// _settings.json5
{
  project_name: "Panda Api", /* 项目名称 */
  project_desc: "Panda Api is a simple api manage tool" /* 项目简述 */
}

我们增加global字段,内容变为:

// _settings.json5
{
  project_name: "Panda Api",
  project_desc: "Panda Api is a simple api manage tool",
  global: {
    apis:{
      auth: true,
      response:{
        code:{name:"返回的状态码", type:"number"},
        msg:{name:"返回的状态说明"}
      }
    },
  }
}

这样我们设置了全局所有api接口的auth字段全部为true,全部都有权限验证,全局的response都有了字段codemsg,我们可以打开我们的接口文档来查看http://localhost:9000

当然,我们的用户登录接口/login是不需要验证用户登录的,我们可以在用户登录接口中单独设置authfalse,这就像继承的概念一样。

如果某一个请求的response返回中不需要code或者不需要msg,我们也可以去掉。

{
    name:"用户登录",
    desc:"用户登录成功,接口会返回一个token",
    method: "POST",
    url:"/login/",
    auth:false, /* 单独设置登录接口不需要验证和用户登录状态 */
    body_mode:"json",
    body:...

code字段和msg字段,如果有的地方不需要这两个字段,就不能简单的像auth字段一样修改一个值,你要做的是再不需要的地方设置删除。

response:{
    code:"$del", /* 删除继承得来的code字段 */
    msg:"$del", /* 删除继承得来的msg字段 */
    ...
}

这个时候设置了值为$del的字段就没有了,我们可以刷新前端文档看一下。 或者也可以这么写:

response:{
    code:{$del:true}, /* 删除继承得来的code字段 */
    msg:{$del:true}, /* 删除继承得来的msg字段 */
    ...
}

如果平时我们调试的时候,想让某个设置的字段不出现,也可以用这个方法.例如:

body:{
    title:{name:"文章标题"},
    summary:{name:"文章摘要"},
    category_id:{name:"文章分类id", type:"number"},
    author_name:{name:"文章作者姓名"},
    source:{name:"文章来源", $del:true}, // 标记为不要source字段
    tag:{name:"文章标签", $del:true} // 标记为不要标签字段
},

继承重用相同的数据模型

有时候,几个接口的字段差不多,又只是几个接口会这样,是不能用全局设置来表示的,比如用户操作的文章列表的字段、文章添加的字段、文章查看的字段差不多,如果字段很多,每个都重复写就很累,虽然可以复制粘贴,但是维护的时候又要同时维护多个地方,带来了麻烦。

所以Panda api上我参考swagger设计了$ref语法,方便的引用数据模型,此外又增加了$exclude语法、$include语法和继承语法,让我们使用起来更方便,我们来看例子吧。

我们的文章的接口文档是这样的:

{
    name:"文章接口",
    apis:[
        {
            name:"文章添加/编辑",
            url:"/post/add/",
            body:{
                id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"PosInt"}, required:false}, // PosInt 表示正整数
                title:{name:"文章标题"},
                summary:{name:"文章摘要", type:"csummary"}, // csummary表示中文一小段文字
                category_id:{name:"文章分类id", type:"PosInt"},
                author_name:{name:"文章作者姓名", type:"cname"}, // cname表示中文姓名
                source:{name:"文章来源"},
                tag:{name:"文章标签"}
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                id: {name:"文章id"}
            }
        },
        {
            name:"查看文章",
            method:"GET",
            url:"/post/{id:\\d+}/",
            query:{
                page:{name:"分页", type:"PosInt"} // PosInt 表示正整数
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                total_page: {name:"总页数", type:"PosInt"},
                total_count: {name:"总记录数", type:"PosInt"},
                current_page: {name:"当前页码", type:"PosInt"},
                result:
                    [{
                        id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"PosInt"},
                        title:{name:"文章标题"},
                        summary:{name:"文章摘要", type:"csummary"},
                        category_name:{name:"文章分类名称"},
                        author_name:{name:"文章作者姓名", type:"cname"},
                        source:{name:"文章来源"},
                        tag:{name:"文章标签"},
                        read_count:{name:"阅读次数", type:"PosInt"},
                        created:{name:"创建时间", type:"PosInt"},
                        create_username:{name:"文章创建人姓名"}
                    }]
            }
        },
        {
            name:"文章列表",
            method:"POST",
            url:"/post/star/list/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"PosInt"}, /* 这时id是必有的 */
                        title:{name:"文章标题"},
                        category_name:{name:"文章分类名称"},
                        author_name:{name:"文章作者姓名", type:"cname"},
                        tag:{name:"文章标签"},
                        created:{name:"创建时间"},
                    }]
            }
        }
    ]
}

我们的文章接口文档里面一共有三个接口:文章添加,文章查看,文章列表,

如果以文章添加为基础来看的话,文章查看与文章添加的区别是:id变为了必填,category_id变为了category_name,增加了3个字段read_count阅读次数,created创建时间,create_username文章创建人姓名,

title:{name:"文章标题"},
summary:{name:"文章摘要"},
category_id:{name:"文章分类id", type:"number"},
author_name:{name:"文章作者姓名"},
source:{name:"文章来源"},
tag:{name:"文章标签"}

这个部分的字段是一模一样的,

再看文章列表,idtitleauthor_nametag,是一样的,category_nameauthor_namecreated是增加字段。

实际上,这些新增的字段也都是文章模型的属性,相同的不同字段分开写就导致如果发生字段调整,属性调整,要维护的地方就会很多,容易出错。

所以Panda api设计的是,我们可以统一设计一个模型,然后每次调用的时候,只调用需要的部分。这样写和维护就统一只在一个地方。

设计文章模型

创建文件夹 _data, 在_data文件夹中创建文件models.json5(文件名可以根据自己的需要设定),在models.json5中敲下面的代码:

{
    Article:{
        id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"number"}, /* 这时id是必有的 */
        title:{name:"文章标题"},
        summary:{name:"文章摘要"},
        category_id:{name:"文章分类id", type:"number"},
        category_name:{name:"文章分类名称"},
        author_name:{name:"文章作者姓名"},
        source:{name:"文章来源"},
        tag:{name:"文章标签"},
        read_count:{name:"阅读次数", type:"number"},
        created:{name:"创建时间", type:"number"},
        create_username:{name:"文章创建人姓名"}
    }
}

我们的文章接口可以这么改写:

{
    name:"文章接口",
    apis:[
        {
            name:"文章添加/编辑",
            url:"/post/add/",
            body:{
                $ref: "./_data/models.json5:Article",
                $exclude:["category_name", "read_count", "created", "create_username"],
                id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"number", required:false}
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                id: {name:"文章id"}
            }
        },
        {
            name:"查看文章",
            method:"POST",
            url:"/post/{id:\\d+}/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        $ref: "./_data/models.json5:Article",
                        $exclude:["category_id"]
                    }]
            }
        },
        {
            name:"文章列表",
            method:"POST",
            url:"/post/star/list/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        $ref: "./_data/models.json5:Article",
                        $include:["id", "title", "category_name", "author_name", "tag", "created"]
                    }]
            }
        }
    ]
}

设置只继承哪些字段,不继承哪些字段:$include$exclude语法

在文件添加接口中,我们使用$ref引用了文章模型,语法是:./_data/models.json5:Article 就是文件路径,后面是冒号:,后是具体的模型引用路径,如果只引用一个title字段,也可以这么写:$ref:"./_data/models.json5:Article/title"

接着有的字段我们是不要的,我们就可以使用$exclude语法,排除一些字段,$exclude:["category_name", "read_count", "created", "create_username"],,放入$exclude中的字段就不会被加载进来。

可以排除对象或数组的嵌套属性, 可以用/标识字段路径来删除,例如, 假设$ref的文章模型里面还有嵌套的对象和数组

Article:{
    id: {name:"用户id", type:"number"},
    title: {name:"文章标题"},
    category: {
        id:{name:"分类id", type:"number"},
        name:{name:"分类名称", type:"string"}
    },
    tags:[{
        id:{name:"标签id"},
        title:{name:"标签内容"}
    }]
}

假设我们$ref这个Article, 不要category对象的id, 不要tags对象列表的id.可以这么写: $exclude:["category/id", tags/0/id] :

...
{
    name:"文章添加/编辑",
    url:"/post/add/",
    body:{
        $ref: "./_data/models.json5:Article",
        $exclude:["category/id", "tags/0/id", "created"],
    },
    response:{
        id: {name:"文章id"}
    }
},
...

我们会得到下面的数据:

...
{
    name:"文章添加/编辑",
    url:"/post/add/",
    body:{
        id: {name:"用户id", type:"number"},
        title: {name:"文章标题"},
        category: {
            name:{name:"分类名称", type:"string"}
        },
        tags:[{
            title:{name:"标签内容"}
        }]
    },
    response:{
        id: {name:"文章id"}
    }
},
...

重写继承到的字段

我们在/post/add/接口,重写了id字段,id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"number", required:false}。当把字段在当前重写了,那么就会以当前字段为准。

可以重写对象或数组的嵌套属性,如果我们想重写嵌套到对象或数组中的属性,可以这么写:

Article:{
    id: {name:"用户id", type:"number"},
    title: {name:"文章标题"},
    category: {
        id:{name:"分类id", type:"number"},
        name:{name:"分类名称", type:"string"}
    },
    tags:[{
        id:{name:"标签id"},
        title:{name:"标签内容"}
    }]
}

如果我们$refArticle, 并且要重写category对象的id属性, 同时为category对象增加一个order, 为tags对象增加一个order, 可以这么写:

...
{
    name:"文章添加/编辑",
    url:"/post/add/",
    body:{
        $ref: "./_data/models.json5:Article",
        "category/id":{name:"新的分类id", type:"PosInt"},
        "category/order":{name:"分类排序", type:"int"},
        "tags/0/order":{name:"标签排序", type:"int"}
    },
    response:{
        id: {name:"文章id"}
    }
},
...

这么写以后,就会重写了category对象的id属性, 并为category对象增加order,为tags对象增加order. 变成下面的样子:

...
{
    name:"文章添加/编辑",
    url:"/post/add/",
    body:{
        id: {name:"用户id", type:"number"},
        title: {name:"文章标题"},
        category: {
            id:{name:"新的分类id", type:"PosInt"}, // 修改
            name:{name:"分类名称", type:"string"},
            order:{name:"分类排序", type:"int"} // 新增
        },
        tags:[{
            id:{name:"标签id"},
            title:{name:"标签内容"},
            order:{name:"标签排序", type:"int"} // 新增
        }]
    },
    response:{
        id: {name:"文章id"}
    }
},
...

文章列表中,我们一样的引入了Article模型,然后没用$exclude,而是用$include语法指定我们需要的字段,这么的好处是,当我们再Article模型中增加了字段,增加的字段不会在模型自动加载出来,因为系统只会加载$include里面的字段;但是$exclude语法因为一开始没有设置这个新增加的字段,所以在$exclude模型的情况,模型Article中增加的字段会自动加载出来。

如果同时使用了$include$exclude字段,那么先加载$include中设置的字段,然后再删除掉$exclude中设置的字段.

所以一共是四步操作:

  1. 写一个文章模型
  2. 引入模型, 当前就会继承引入模型的字段
  3. 设置需要的字段,排除不需要的字段
  4. 重写不一样的字段

指定按字段引用

我们还可以只引入某一个字段,比如有一个简单的文章列表接口,只需要文章标题和文章id,

{
    name:"简要文章列表",
    method:"POST",
    url:"/post/simple/list/",
    body:{
        page:{name:"分页", type:"number"}
    },
    response:{
        total_page: {name:"总页数", type:"number"},
        total_count: {name:"总记录数", type:"number"},
        current_page: {name:"当前页码", type:"number"},
        result:
            [{
                id:{$ref: "./_data/models.json5:Article/id"},
                title:{$ref: "./_data/models.json5:Article/title"}
            }]
    }
}

简化引用路径

每个地方都要去写./_data/models.json5这么一长段路径,很麻烦,万一路径变了,那么每个地方都要改。因此我们可以使用define语法。来定义路径,所以,我们的文章接口改进未这样:

{
    name:"文章接口",
    define:{
        model: "./_data/models.json5",
        article: "./_data/models.json5:Article"
    },
    apis:[
        {
            name:"文章添加/编辑",
            url:"/post/add/",
            body:{
                $ref: "$model:Article",
                $exclude:["category_name", "read_count", "created", "create_username"],
                id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"number", required:false},
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                id: {name:"文章id"}
            }
        },
        {
            name:"查看文章",
            method:"POST",
            url:"/post/{id:\\d+}/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        $ref: "$article",
                        $exclude:["category_id"]
                    }]
            }
        },
        {
            name:"文章列表",
            method:"POST",
            url:"/post/star/list/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        $ref: "$article",
                        $include:["id", "title", "category_name", "author_name", "tag", "created"]
                    }]
            }
        },
        {
            name:"简要文章列表",
            method:"POST",
            url:"/post/simple/list/",
            body:{
                page:{name:"分页", type:"number"}
            },
            response:{
                total_page: {name:"总页数", type:"number"},
                total_count: {name:"总记录数", type:"number"},
                current_page: {name:"当前页码", type:"number"},
                result:
                    [{
                        id:{$ref: "$article/id"},
                        title:{$ref: "$article/title"}
                    }]
            }
        }
    ]
}

文章地址支持正则匹配

查看文章接口,接口地址是这么写的:"url":"/post/{id:\\d+}/"

{
    name:"查看文章",
    method:"POST",
    url:"/post/{id:\\d+}/",

这么写的意思是:我们匹配的url是/post/id/,然后我们对id进行了约束,只要id是数字就会匹配,我也可以写单词匹配,/post/{id:\\w+}/,然后也可以写更复杂的正则匹配。

整个接口的复用

有的接口,会在多个地方去体现和是用,比如,用户操作有图片上传,文章上传有图片,后台管理员操作也有上传图片,或者多个角色都有上传图片,但是我们开发的时候,需要把文档放到一起。

所有复用的东西需要创建在_data目录,我们在_data里面,创建文件common_api.json5(文件名可以根据我们的实际需求修改),里面的代码是:


{
    photo_upload:{
        name:"上传照片",
        desc:"这个接口既可以上传处方单扫描件、本地处方单照片,还可以上传身份证扫描件和本地身份证照片",
        url:"/common/photo/upload/",
        method:"POST",
        body_mode:"form-data",
        body: {
            file: {name:"要上传的文件", desc:"如果是扫描件要转变为扫描好的本地文件", type:"image"},
            file_type:{name:"文件类型", desc:"分为处方单,身份证", $enum:[["id_no","身份证"], ["chufangdan", "处方单"], ["other", "其它普通照片"]]}
        },
        response:{
            $ref:"./_data/models.json5:Response",
            result:{
                upload_id:{name:"上传文件id", desc:"上传文件保存后的返回id", type:"number"},
                upload_url:{name:"上传文件后访问的url地址", $value:"$body/file:url"},
                ocr:{name:"OCR识别数据", desc:"上传文件系统自动识别OCR返回的结果,目前只有部分处方单和身份证有这个数据", required:false}
            }
        },
        test_data:[{
            body:{file:"aaa.jpg", file_type:"id_no"},
            response:{
                code: 1,
                msg: "",
                result: {
                    upload_id: 1,
                    upload_url: "/_upload/aaa.jpg",
                    ocr:{id_no:"533025199002160013", name:"张三", gender:"男", age:"29"}
                }
            }
        }]
    }
}

当我们定义好这个公共接口后,我们就可以在文章中直接引用这个接口

{
    name:"文章接口",
    define:{
        model: "./_data/models.json",
        article: "./_data/models.json:Article"
    },
    apis:[
        {
            $ref:"./_data/common_api.json:photo_upload",
            name:"照片上传"
        },
        {
            name:"文章添加/编辑",
            url:"/post/add/",
            body:{
                $ref: "$model:Article",
                $exclude:["category_name", "read_count", "created", "create_username"],
                id:{name:"文章id", desc:"添加提交的时候是没有id, 编辑提交的时候才有", type:"number", required:false},
            },
            response:{
                /* code 和 msg 字段来自全局了 */
                id: {name:"文章id"}
            }
        },
        ...
    ]
}

同样的,继承过来以后还是可以自定义里面的字段属性,比如这里name字段就进行了自定义,这里主要还有就是写上name方便维护的时候看

文件上传接口

在照片上传的接口中,有两个特殊的地方,首先:我们的body_modeform-data,也必须是form-data才会进行文件的接收。

test_data中,body中的上传文件的匹配用的是文件名进行匹配,

test_data的返回值response中,我们还可以获取文件的访问url地址,url访问地址为:_upload/ + 文件名。即如果我们的文件名是:aaa.jpg, 那么我们访问的文件url地址是:/_upload/aaa.jpg。当你访问这个地址: http://localhost:9000/_upload/aaa.jpg时,就可以访问到文件。

我们也可以在response的定义中,根据body直接获取上传文件的url。例如:

...
body: {
    file: {name:"要上传的文件", desc:"如果是扫描件要转变为扫描好的本地文件", type:"image"},
    file_type:{name:"文件类型", desc:"分为处方单,身份证", $enum:[["id_no","身份证"], ["chufangdan", "处方单"], ["other", "其它普通照片"]]}
},
response:{
    result:{
        upload_id:{name:"上传文件id", desc:"上传文件保存后的返回id", type:"number"},
        upload_url:{name:"上传文件后访问的url地址", $value:"$body/file:url"},
        ocr:{name:"OCR识别数据", desc:"上传文件系统自动识别OCR返回的结果,目前只有部分处方单和身份证有这个数据", required:false}
    }
},
...

$value:"$body/file:url"的意思就是 upload_url这个字段的值是通过$body中的file字段的可访问的url来进行输出。

引入cvs外部数据

正在搞...

2020-03-01 05:41

留言