React前后端分离,前端如何更好的和后台调接口方案分享

什么是JSON Schema

怎么定义呢,说句实话我是写完了才发现,这不跟传说的JSON Schema差不多么,so,我就说ta是JSON Schema(我理解的,正不正经不知道)。

正经的定义

Json Schema定义了一套词汇和规则,这套词汇和规则用来定义Json元数据,且元数据也是通过Json数据形式表达的。Json元数据定义了Json数据需要满足的规范,规范包括成员、结构、类型、约束等。

我因为啥写

一直以来和后台进行联调这件事都是一件很"困扰"的事情,主要是沟通成本太大,其主要集中在接口文档的沟通上,不是这个参数值不用传,就是那个值必须写的,或者某些参数必须是什么类型的。还都不落实到文档,这么说也不严谨,他有文档,但不“清晰”,还是避免不了沟通,或者有改动他不更新,毕竟制定规矩并严格遵守可没直接口口相传来的“舒服”,就是图方便,但这就很危险,大家一旦同步错乱,功能就写错,那浪费的人力物力还有空口白牙,没处说理去所引起的心态崩了,这简直无法接受,与其在困扰中忍受,不是想个办法“变一变”,so,我就搞了这个。

那么开始上菜

设计思路

首先我期望ta能够在我请求的时候,帮我办两件事:

  1. 符不符合后台的要求:在请求的时候,对入参(发送给后台数据)进行相应的校验。
  2. 符不符合前台的要求:在请求回数据的时候,对出参(后台返回的数据)进行相应的校验。

然后,在补上一个特点

  1. 可配置

接下来开整吧☺。

配置方法

先贴出配置数据,然后我们再分析:

import BaseApiShape from './baseApiCheck'


let config = {
    fetchTestDataList: {
        reqParam: {
            type: {},//不写默认是对象
            p1: {
                type: "string",//要求是什么类型,传入类型名,否者直接取传入变量的类型
            },
            p2: {
                type: {},
            },
            p13: {
                type: "string",
            },
        },
        resParam: {
            p1: {
                type: [],
            },
            p2: {
                type: {},
            }
        }
    },
    fetchLightDetail: {
        resParam: {
            type: [],
            _item: {
                lightItems: {
                    type: [],
                    _item: {
                        exclude: {
                            type: "boolean",
                        }
                    },
                },
                aaaaa: {
                    type: {},
                }
            }
        }
    }
}

export default new BaseApiShape({ config })

分析:

上面的两个校验个体非常典型,完整的把我所有的校验功能都体现出来了:

  • fetchTestDataList
  • fetchLightDetail

大体结构是这样的:

    {
        reqParam: {
           ...
        },
        resParam: {
           ...
        }
    }

看名猜意reqParamresParam,分别是代表的入参(前=>后)和出参(后=>前)。

然后我们再继续深入看一下:

先介绍一下约定的规矩:

type:就是表示要求的参数类型,至于怎么传,(基本)都行,比如一个字符串,你是传“string”直接告诉我什么类型还是随便给我个字符串变量我内部取一下类型都行,就是很随意。

_item:表示一个数组中元素,只会在是数组类型下出现。

案例一:

    {
        // 分析点:A
        type: {},
        // 分析点:B
        p1: {
            type: "string", // 分析点:C
        },
        p2: {
            type: {},
        },
        p13: {
            type: "string",
        },
    }

ABC说明白ta:

  • A:type表示的是这整体是一个对象。
  • B:p1表示这个结构中要名字为p1的属性。
  • C:p1里面的type,就表示p1应该是什么类型。

然后再说一下案例二

案例二:

    {
        // 分析点:A
        type: [],
        // 分析点:B
        _item: {
             // 分析点:C
            lightItems: {
                type: [],
                _item: {
                    exclude: {
                        type: "boolean",
                    }
                },
            },
            aaaaa: {
                type: {},
            }
        }
    }

ABC说明白ta:

  • A:type表示的是这整体是一个数组,(注意奥,这是数组哦!)。
  • B:_item表示数组中的元素。
  • C:lightItems说明这还是一个数组,这就构成了一个嵌套数组,超级复杂,没关系,支持!(这就已经很深了,还深?那就应该和后台的哥们聊聊了。)

我们实战一下

fetchLightDetail为例

返回的参数如果是这样的:

[{
    lightName: "test_lightName_0",
    comment: "test_comment_0",
    lightItems: [
        { 
            lightItemId: 1, 
            lightType: 0, 
            baseSelect: 1,
            exclude: 0 
        }
    ]
}]

那么校验结果就是:

已经很暖心的把问题说出来了,够用了,以后再优雅吧,反正想法基本落地了。

贴一下没怎么润色的基类内部实现,没测透,估计有bug(应该是肯定的)

let typeArr = [
    "bigInt",
    "boolean",
    "string",
    "symbol",
    "undefined",
    "object",
    "number",
    "function",
    "array"
]
export default class BaseApiShape {
    constructor(props) {
        const { config } = props;
        this.config = config;
        this.checkReport = []
    }
    process(config) {
        for (let key in config) {
            this[key] = _.cloneDeep(config[key])
        }
    }

    checkReqParams({ apiName, params }) {
        if (apiName in this.config) {
            return this.check({
                required: this.config[apiName].reqParam,
                current: params
            })
        }
    }

    checkResParams({ apiName, params }) {
        if (apiName in this.config) {
            return this.check({
                required: this.config[apiName].resParam,
                current: params
            })
        }
    }
    checkArr({ required, current = [], logPrefix }) {
        current.forEach((item, index) => {
            this.checkObj({
                required: required,
                current: item,
                logPrefix: logPrefix + `[${index}]`
            })
        })
    }
    check({ required = {}, current }) {
        if (required.type === "array" || Array.isArray(required.type)) {
            if (Array.isArray(current)) {
                if (required._item) {
                    this.checkArr({
                        required: required._item,
                        current: current,
                        logPrefix: "整体"
                    })
                }
            } else {
                this.checkReport.push({ property: "整体", message: `要求数组!` })
            }
        } else {
            this.checkObj({ required, current })
        }
        return this.checkReport;
    }
    //如果上来是数组,实际上其实判断就是自己的内部的元素,那就是判断多个对象了
    checkObj({ required = {}, current = {}, logPrefix }) {
        for (let key in required) {
            let checkOption = required[key];
            if (Object.keys(checkOption).length) {
                const { isRequired = true, type } = checkOption;
                if (type === "array" || Array.isArray(type)) {
                    if (checkOption._item) {
                        this.checkArr({
                            required: checkOption._item,
                            current: current[key],
                            logPrefix: ` ${logPrefix + "->" + key}`
                        })
                    }
                    continue;
                }
                //校验-是否存在
                if (isRequired && !(key in current)) {
                    this.checkReport.push({ property: key, message: `参数中并没有指定要求的属性:${key}` })
                }
                //校验-类型
                if ((typeArr.indexOf(type) != -1) || typeof type in typeArr || Array.isArray(type)) {
                    let typeTemp;
                    if (typeArr.indexOf(type) != -1) {
                        typeTemp = type;
                    } else {
                        typeTemp = typeof type;
                    }
                    if (typeTemp !== typeof current[key]) {
                        this.checkReport.push({ property: key, message: `${logPrefix && ('数组' + logPrefix)}:参数中指定要求的属性类型不对,yes-${typeTemp}:no-${typeof current[key]}` })
                    }
                }
            }
        }
    }
}

结语

西为中用,古为今用,“难”的,“抽象”的知识,等了解之后就明白其实是很多“简单”组成的(我不相信有coder对此没感悟),你技术再强,你没有“心”,你顶多是个机器,你技术再弱,你有“心”,你也是个人,不过分自大,不妄自菲薄,慢慢学吧,定个方向往那努力,管ta成不成呢😁,无成有终。

集成了该功能的🌰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值