前言
随着项目增多,且多数为项目定制化接口。避免前端重复封装,从 Swagger 入手,将 url 自动转 js 方法直接用。特别节约时间和资源~
Swagger 简介
Swagger 一款 RESTFUL
接口的、基于 YAML、JSON
语言的文档在线自动生成、代码自动生成的工具。
主要工具:
-
Swagger 编辑器 :基于浏览器的编辑器,可以在其中编写
OpenAPI
规范,并实时预览API
文档。 -
Swagger UI
:将OpenAPI
规范呈现为交互式API
文档,并可以在浏览器中尝试API
调用。 -
Swagger Codegen
:根据OpenAPI
规范,生成服务器存根和客户端代码库。
实现
目录结构
bash
.
├─ js
| ├─MySwagger.js # 主文件,1.url转js接口;2.接口查询界面解析接口;
| └─ swagger
| ├─ axios.js # axios 对象及 包装后的 get,post,put,delete 等方法
| └─ utils.js # 工具文件,导入 axios.js,设置外部传入的 axios,url 解析为js方法名,定义 promise 等
└─ components
└─MySwaggerApi.vue # 组件,转换后的文件查看
主文件说明
MySwagger.js
中有两类方法
initSwaggerApi
:用于生成 Js 接口;initSwaggerDoc
:用于界面显示接口信息;
文件 swagger/axios.js 和 swagger/utils.js 将在最后贴出
js
import Utils from "./swagger/utils"
import axios from "axios"
// 请求法类型
const REQUEST_METHODS = [ 'get', 'post', 'put', 'delete', 'patch'];
export default class MySwagger {
/**
* 初始化 Swagger Javascript Api 接口
* @param {Object} axios,axios对象,注意已带 token
* @param {Object} cfg,swagger 相关信息 {ignorePaths, jsonUrl, ignorePaths}
* @param {Function} callback 回调函数
* @returns
*/
static initSwaggerApi(myaxios, cfg, callback) {
// swagger/axios.js 文件中的方法,设置外部传入的 axios 对象
Utils.setHttp(myaxios);
// 解析 swagger json 对象
axios.get(cfg.jsonUrl).then(res => {
// paths 列表
const paths = res.data.paths;
for(const path in paths) {
const methods = paths[path];
for(const method in methods) {
if(!REQUEST_METHODS.includes(method)) {
continue;
}
// swagger/axios.js 文件中的方法,url 转换为 js 方法名
const apiMethodName = Utils.restUrlToMethodName(method, path, cfg.ignorePaths);
// 注意是 this,直接放在 类上,为静态方法。api 方法对象集合
this[apiMethodName] = function(odt, odt2, odt3) {
// swagger/axios.js 文件中的方法,定义 promise
return Utils.definePromise(path, method, odt, odt2, odt3)
};
}
}
callback(true)
console.log('Swagger API 自动转js方法成功!');
}).catch(err => {
callback(false)
console.error('Swagger API 自动转js方法失败', err);
})
}
/**
* 初始化 Swagger Javascript Api 文档信息
* @param {Object} cfg swagger 相关信息 {jsonUrl, ignorePaths}
* @param {Function} callback 成功回调 (data) 信息
* @param {Function} errorback 失败回调 (e) 错误信息
* @returns
*/
static initSwaggerDoc(cfg, callback, errorback) {
let objs = {}
// 解析 swagger json 对象
axios.get(cfg.jsonUrl).then(res => {
const data = res.data;
// 判断 swagger 版本
const version = (data.swagger || data.openapi).split('.')[0];
// paths 列表
const paths = data.paths;
// swagger v2
if(version==='2') {
for(const path in paths) {
const methods = paths[path];
for(const method in methods) {
// get/post 对象下
if(!REQUEST_METHODS.includes(method)) {
continue;
}
// swagger/axios.js 文件中的方法,url 转换为 js 方法名
const apiMethodName = Utils.restUrlToMethodName(method, path, cfg.ignorePaths);
const params = methods[method];
const tag = params.tags[0];
if(!objs[tag]) {
objs[tag] = {
tag,
description: tag,
paths: []
}
}
objs[tag].paths.push({
path,
apiMethodName,
method,
summary: params.summary,// 接口描述
parameters: params.parameters,// 请求参数
version
})
}
}
}
else {// swagger v3,配置管理
// 先解析 tag
data.tags.forEach(t => {
objs[t.name] = {
tag: t.name,
description: `${t.name} ${t.description}`,
paths: []
}
});
// console.log('objs: ', objs);
for(const path in paths) {
const methods = paths[path];
for(const method in methods) {
// get/post 对象下
if(!REQUEST_METHODS.includes(method)) {
continue;
}
// swagger/axios.js 文件中的方法,url 转换为 js 方法名
const apiMethodName = Utils.restUrlToMethodName(method, path, cfg.ignorePaths);
const params = methods[method];
const tag = params.tags[0];
// console.log('objs[tag]', objs[tag], tag);
// 要判断,有的 tags 与 path 不一致
objs[tag]?.paths.push({
path,
apiMethodName,
method,
summary: params.summary,// 接口描述
parameters: params.parameters,// 请求参数
version
})
}
}
}
callback(objs)
}).catch(e => {
errorback(e)
})
}
}
功能使用
Swagger 解析配置
js
const SWAGGER_CFG = {
// 过滤不必要的路径
ignorePaths: ['v1/', 'myweb/'],
// swagger json 文件
jsonUrl: 'http://127.0.0.1:8000/swagger/v1/swagger.json',
// swagger 接口文件地址
docUrl: 'http://127.0.0.1:8000/swagger/index.html'
}
生成 Js 接口
- 代码第
13 - 20
行,创建axios
对象,外部传入更灵活,可设置token
等信息; - 代码第
13 - 20
行,初始化js
接口到MySwagger
类上;
vue
<template>
<div>
<h1>swagger测试</h1>
</div>
</template>
<script>
import axios from 'axios'
import MySwagger from '../js/index'
export default {
mounted() {
// 外部传入更灵活,可设置 token 等信息
const myAxios = axios.create({
// 基地址
baseURL: `${window.location.protocol}//${IP}:${PORT}/`,
// 请求头带 token
headers:{
'Authorization': '111ssss22s2s2s2s2s2s22s22'
}
})
// 初始化 js 接口到 MySwagger 类上
MySwagger 对象上.initSwaggerApi(myAxios, SWAGGER_CFG, (data) => {
console.log(`初始化成功:${data}`);
}, (err) => {
console.log(`初始化失败:${err}`);
})
window.MySwagger = MySwagger;
}
}
</script>
为方便查看 MySwagger 挂在 window。但不推荐...控制台输出可看到接口都挂到类上
接口界面
- 封装成组件 Vue
<MySwaggerApi>
; - 代码第
4
行,可打开原始 Swagger 接口地址; - 代码第
78 - 98
行,可复制接口; - 代码第
104 - 111
行,调接口MySwagger.initSwaggerDoc
获取列表;
vue
<template>
<div class="my-swagger-api__main">
<div class="header">
<el-link icon="el-icon-view" @click="openLink">Swagger 接口地址</el-link>
<el-input v-model="searchKey" placeholder="路径搜索" @input="searchPath">
<el-button icon="el-icon-search" ></el-button>
</el-input>
</div>
<div class="main">
<div class="row" v-for="item of tableData" :key="item.tag">
<div class="first-title">{{item.description}}</div>
<el-table class="api-list-table" :data="item.paths" border style="width: 100%" :row-class-name="tableRowClassName" @cell-click="onCopyText" >
<el-table-column type="expand" @click="copyHandle( 'icon-' + item.font_class)">
<template #default="props">
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="请求方法:">
<el-tag size="medium" :type="tagTypes[props.row.method]">
{{ props.row.method }}
</el-tag>
</el-form-item>
<el-form-item label="JS方法名:" >
<el-tag size="mini" type="info">{{ props.row.apiMethodName }}</el-tag>
<span></span>
</el-form-item>
<el-form-item label="描述:">
<span>{{ props.row.summary }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column prop="path" label="路径"> </el-table-column>
<el-table-column prop="apiMethodName" label="JS方法名" class-name="api-method-name-cell"> </el-table-column>
<el-table-column prop="summary" label="描述"> </el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import MySwagger from '../js/MySwagger'
export default {
props: {
config: {
type: Object,
default() {
return {
// ignorePaths: ['v1/'],
// jsonUrl: 'http://127.0.0.1:7008/swagger/v1/swagger.json',// 解析的 json 路径
// docUrl: 'http://127.0.0.1:7008/swagger/index.html' // 官方接口列表
}
}
}
},
data() {
return {
tableData: {},//API_LIST,
tagTypes: {
post: 'success',
get: '',
put:'info',
delete: 'danger',
patch: 'warning'
},
searchKey: '',
}
},
methods: {
tableRowClassName({ row, rowIndex }) {
// return `${row.method}-row`;
},
// 打开
openLink() {
window.open(this.config.docUrl, '_blank')
},
// 拷贝
onCopyText(row, column, cell, event) {
console.log(row, column, cell, event);
if(column.property!=='apiMethodName') {
return
}
// 获取输入框的内容
const copyText = row.apiMethodName;
let copy = (e) => {
e.preventDefault()
e.clipboardData.setData('text/plain', copyText)
// 提示用户复制成功
this.$message({
type: 'success',
message: `${column.label} 复制成功`
})
document.removeEventListener('copy', copy)
}
document.addEventListener('copy', copy)
document.execCommand('copy')
},
// 查询
searchPath(val) {
console.log(this.searchKey, val);
}
},
mounted() {
// 初始化
MySwagger.initSwaggerDoc(this.config, (data=>{
this.tableData = data;
console.log('this.tableData',this.tableData);
}),(e)=>{
console.error('获取错误',e);
});
},
};
</script>
<style lang="scss" scoped>
.my-swagger-api__main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
.header {
padding: 50px 50px 20px 50px;
display: flex;
justify-content: space-between;
width: 100%;
.el-input {
width: 350px;
}
}
}
.main {
position: absolute;
top: 100px;
left: 50px;
right: 50px;
bottom: 50px;
overflow: auto;
.row {
margin-bottom: 20px;
}
}
.first-title {
font-size: 17px;
font-weight: 600;
height: 45px;
line-height: 45px;
padding-left: 5px;
border-left: 4px solid #0d64d4;
color: #333;
background:#f0f8ff;
}
.demo-table-expand {
margin-left: 50px;
background:#fafdff;
:deep(.el-form-item) {
display: block;
.el-form-item__label {
width: 90px;
}
}
}
.api-list-table {
:deep(.api-method-name-cell) {
cursor: pointer;
}
}
</style>
<style lang="scss">
.get-row {
background: rgba(97, 175, 254, .5) !important;
}
.post-row {
background: rgba(73, 204, 144, .5) !important;
}
</style>
界面组件的使用
vue
<template>
<my-swagger-api :config="config"/>
</template>
<script>
import MySwaggerApi from '../components/MySwaggerApi.vue';
export default {
components: {
MySwaggerApi
},
data() {
return {
config: SWAGGER_CFG
}
}
}
</script>
效果
使用
此处只列出特殊的。
Post
{urlParams}
- Post 方式,同时存在 Parameters 和 RequestBody
- 第一个参数:对象类型,RequestBody。如果没参数,就传 null;
- 第二个参数,对象类型,Parameters。注意 key 与下图一致;
js
// 第一个参数 RequestBody
const requestBody = {
ip: '127.0.0.1',
port: '6163'
};
// 第二个参数 Parameters
const parameters = {
dataSourceName: 'pg123'
};
MySwagger.postConfSpatDatasourceEdit(requestBody, parameters);
url + Params
/v1/gms/conf/set-default-slice-cache-db
- Post 方式,同时存在 Parameters 和 RequestBody
- 第一个参数:对象类型,RequestBody。如果没参数,就传 null;
- 第二个参数,对象类型,Parameters。注意 key 与下图一致;
js
// 第二个参数 Parameters
const parameters = {
id: 'pg123'
};
MySwagger.postConfSetDefaultSliceCacheDb(null, parameters).then(res=>{
console.log(res);
}).catch(err => {
console.log(err);
})
Get
{urlParams}
- Get 方式,在 URL 上赋值参数
- 第一个参数,对象类型,Parameters。注意 key 与下图一致;
js
// 第一个参数是对象类型,且 key 与下图一致
const parameters = {
dataSourceName: 'pg123'
}
// 请求
MySwagger.getConfSpatDatasourceInfo(parameters).then(res=>{
console.log(res);
}).catch(err => {
console.error(err);
})
url + Params
/v1/gms/conf/spat/gdb-list
- Get 方式,在 URL 上赋值参数
- 第一个参数,对象类型,Parameters。注意 key 与下图一致;
js
// 第一个参数是对象类型,且 key 与下图一致
const parameters = {
gdbp: 'pg123'
}
MySwagger.getConfSpatGdbList(parameters).then(res=>{
console.log(res);
}).catch(err => {
console.log(err);
})
最后
swagger/axios.js
js
// 导入封装好的axios实例
let service;
export default {
// axios实例,外部传入,用于外部设置 token
setHttp(axios) {
service = axios;
},
/**
* get 请求
* @param {String} url 必填,请求地址
* @param {Object} params 选填,与请求一起发送的 url 参数
* @param {Object} options 选填,其它配置参数
*/
get(url, params, options = {}) {
// 是否解析带参数的url
url = parseParamsUrl(url, params);
// 参数
const config = {
method: 'get',
url: encodeURI(url)
}
// 是否有 params
if (params) config.params = params
// 其它参数
for (let key in options) {
config[key] = options[key]
}
// console.log('get',config);
return service(config)
},
/**
* post 请求
* @param {String} url 必填,请求地址
* @param {Object} data 选填,请求主体数据
* @param {Object} params 选填,与请求一起发送的 url 参数
* @param {Object} options 选填,其它配置参数
*/
post(url, data, params, options = {}) {
// 是否解析带参数的url
url = parseParamsUrl(url, params);
// 参数
const config = {
method: 'post',
url: encodeURI(url)
}
// 是否有 params
if (params) config.params = params
// 是否有 data
if (data) config.data = data
// 其它参数
for (let key in options) {
config[key] = options[key]
}
// console.log('post',config);
return service(config)
},
put(url, params) {
const config = {
method: 'put',
url: encodeURI(url)
}
if (params) config.params = params
return service(config)
},
delete(url, params) {
const config = {
method: 'delete',
url: encodeURI(url)
}
if (params) config.params = params
return service(config)
}
}
/**
* 解析 url 带参数
* @param {String} url 必填,请求地址
* @param {Object} params 选填,与请求一起发送的 url 参数
*/
function parseParamsUrl(url, params) {
if(params && Object.keys(params) && Object.keys(params).length > 0) {
Object.keys(params).forEach(key => {
// 对应的参数
const reg = `{${key}}`;
if(url.indexOf(reg) > -1) {
url = url.replace(reg, params[key])
}
})
}
// console.log('url2', url);
return url;
}
swagger/utils.js
js
import http from "./axios.js"
export default {
// axios实例,外部传入,用于外部设置 token
setHttp(axios) {
http.setHttp(axios)
},
/**
* url 转方法名
* @param {*} method:get, post, put, delete, patch
* @param {*} url
* @returns
*/
restUrlToMethodName(method, url, ignorePaths) {
// 把 -或者 . 也换成 /
url = url.replace(/-|\./g, '/');
// 去除不要的
ignorePaths.forEach(p=>{
url = url.replace(p, '');
})
// 移除路径中的斜杠和参数占位符
const paths = url.split('/');
const methodName = paths.reduce((x,y)=>{
// 去掉 {},首字符大写
const part = y.replace(/\{.*?\}/g, '');
// 首字符大写
const part2 = part.charAt(0).toUpperCase() + part.slice(1);
return x + part2;
}, '')
return method + methodName;
},
/**
* 定义 promise
* @param {String} url
* @param {String} method:get, post, put, delete, patch
* @param {Object} odt,如果方法是 get,此参数为 params。如果是 post,为 RequestBody
* @param {Object} odt2,如果方法是 get,此参数为 options。如果是 post,为 params
* @param {Object} odt3,如果方法是 post,为 params
* @returns
*/
definePromise(url, method, odt, odt2, odt3) {
let promise;
switch(method) {
case 'get': {
promise = new Promise(function(resolve, reject) {
http.get(url, odt, odt2).then(function(resp) {
const d = resp.data;
(d && d.succ) ? resolve(d.obj||d): reject(d);
}).catch(e=>{
reject(e)
})
})
break;
}
case 'post': {
promise = new Promise(function(resolve, reject) {
http.post(url, odt, odt2, odt3).then(function(resp) {
const d = resp.data;
(d && d.succ) ? resolve(d): reject(d);
})
})
break;
}
}
return promise;
},
}