!12 apimanager模块添加routelist

Merge pull request !12 from UCT/master
This commit is contained in:
JaguarJack 2021-11-12 09:45:22 +00:00 committed by Gitee
commit 120cd494c6
14 changed files with 1106 additions and 326 deletions

View File

@ -14,8 +14,9 @@ apimanager 模块是一个用于API管理、测试的模块。
6. 基于catchadmin开发模块安装简单使用便捷支持模块数据导入导出。
7. 开源开放易于二次开发测试用例可共享形成产品API知识库。
8. 支持私有化部署、云原生部署。
9. 可视化管理系统路由列表并可与API测试工具结合可视化测试系统接口。
演示地址:www.uctoo.com 控制台使用demo帐号登录
演示地址:demo.uctoo.com 控制台使用demo帐号登录
模块使用界面截图:
<table>
<tr>
@ -39,6 +40,9 @@ apimanager 模块是一个用于API管理、测试的模块。
<tr>
<td><img src="https://gitee.com/UCT_admin/materials/raw/master/uctoo_apitester/images/apirun.png"></td>
</tr>
<tr>
<td><img src="https://gitee.com/UCT_admin/materials/raw/master/uctoo_apitester/images/routelist.png"></td>
</tr>
</table>
## 产品架构
@ -70,8 +74,10 @@ apimanager 模块是一个用于API管理、测试的模块。
## 使用手册
1. 可以通过API管理->API分类功能增删改查API分类。
2. 可以通过API环境变量功能增删改查用户环境变量。环境变量的key值以{{key}}方式定义在API测试用例中对应的{{key}}值将替换为环境变量的value值。每个用户可以创建多组环境变量可以切换当前选中的环境变量组。
3. 可以通过API列表功能增删改查API测试用例。api_url、header、body、query、auth字段支持环境变量。
3. 可以通过API列表功能增删改查API测试用例。api_url、header、body、query、auth字段支持环境变量。新增API测试用例时标识字段请与路由列表name字段保持一致以便API测试用例与路由一一对应快速检索。
4. 可以对已添加的API测试用例执行测试操作在API测试界面可以对api_url、header、body、query、auth等字段进行自定义编辑。发送按钮可以实际执行API测试用例获得接口返回值。
5. 可以使用路由列表->同步至数据库功能,将系统内所有路由信息保存至数据库,以便可视化管理和测试。与 php think route:list -m 命令相同效果。
6. 可以使用路由列表->API测试功能以路由name字段为请求参数跳转至API测试列表页面以便快速查询出对应的API测试用例进行API测试。需更新前端vue项目layout/mixin/formOperate.js文件修复了页面初始化传参bug
具体请参考 https://www.kancloud.cn/doc_uctoo/manual

View File

@ -1,128 +1,71 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="queryParam.env_name"
placeholder="环境名称"
clearable
class="filter-item form-search-input"
/>
<el-button
class="filter-item search"
icon="el-icon-search"
@click="handleSearch"
>
<el-input v-model="queryParam.env_name" placeholder="环境名称" clearable class="filter-item form-search-input" />
<el-button class="filter-item search" icon="el-icon-search" @click="handleSearch">
搜索
</el-button>
<el-button
class="filter-item"
icon="el-icon-refresh"
@click="handleRefresh"
>
<el-button class="filter-item" icon="el-icon-refresh" @click="handleRefresh">
重置
</el-button>
<el-button
class="filter-item fr"
type="primary"
icon="el-icon-plus"
@click="handleCreateEnv"
>
<el-button class="filter-item fr" type="primary" icon="el-icon-plus" @click="handleCreateEnv">
新增
</el-button>
</div>
<el-table
ref="multipleTable"
:data="data"
tooltip-effect="dark"
style="width: 100%"
border
fit
@selection-change="handleSelectMulti"
>
<el-table ref="multipleTable" :data="data" tooltip-effect="dark" style="width: 100%" border fit @selection-change="handleSelectMulti">
<el-table-column type="selection" width="55" />
<el-table-column prop="env_name" label="环境名称" />
<el-table-column prop="selected" label="当前环境" />
<el-table-column prop="env_name" label="环境名称" />
<el-table-column prop="selected" label="当前环境" />
<el-table-column prop="creator" label="创建人" />
<el-table-column prop="created_at" label="创建时间" />
<el-table-column prop="updated_at" label="更新时间" />
<el-table-column label="操作" fixed="right" width="300">
<template slot-scope="module">
<el-button
type="primary"
icon="el-icon-refresh"
@click="selectAPIenv(module.row.id)"
>切换</el-button
>
<el-button
type="primary"
icon="el-icon-edit"
@click="handleUpdate(module.row)"
/>
<el-button
type="danger"
icon="el-icon-delete"
@click="handleDelete(module.row.id)"
/>
<el-button type="primary" icon="el-icon-refresh" @click="selectAPIenv(module.row.id)" >切换</el-button>
<el-button type="primary" icon="el-icon-edit" @click="handleUpdate(module.row)" />
<el-button type="danger" icon="el-icon-delete" @click="handleDelete(module.row.id)" />
</template>
</el-table-column>
</el-table>
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="paginate.current"
hide-on-single-page
:page-sizes="paginate.sizes"
:page-size="paginate.limit"
:layout="paginate.layout"
:total="paginate.total"
/>
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="paginate.current"
hide-on-single-page
:page-sizes="paginate.sizes"
:page-size="paginate.limit"
:layout="paginate.layout"
:total="paginate.total"/>
<!----------------------------------- 编辑 ---------------------------------------------->
<el-dialog
:close-on-click-modal="false"
:title="title"
:visible.sync="formVisible"
@close="handleCancel"
>
<el-form
label-position="top"
:ref="formName"
:model="formFieldsData"
:rules="rules"
>
<el-form-item
label="env_name"
:label-width="formLabelWidth"
prop="env_name"
>
<el-input
v-model="formFieldsData.env_name"
placeholder="请输入环境名称"
autocomplete="off"
clearable
/>
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="formVisible" @close="handleCancel">
<el-form label-position="top" :ref="formName" :model="formFieldsData" :rules="rules">
<el-form-item label="env_name" :label-width="formLabelWidth" prop="env_name">
<el-input v-model="formFieldsData.env_name" placeholder="请输入环境名称" autocomplete="off" clearable />
</el-form-item>
<el-form-item
label="env_json"
:label-width="formLabelWidth"
prop="env_json"
>
<!-- <el-form-item label="appid" :label-width="formLabelWidth" prop="appid">
<el-input v-model="formFieldsData.appid" placeholder="请输入appid" autocomplete="off" clearable />
</el-form-item>
<el-form-item label="project_id" :label-width="formLabelWidth" prop="project_id">
<el-input v-model="formFieldsData.project_id" placeholder="请输入project_id" autocomplete="off" clearable />
</el-form-item> -->
<el-form-item label="env_json" :label-width="formLabelWidth" prop="env_json">
<avue-crud
ref="crudJSON"
:option="tableOption"
:data="jsonTableData"
@row-update="addUpdateJSON"
@row-del="rowDelJSON"
@row-save="rowSaveJSON"
>
<template slot-scope="{ row, index }" slot="menu">
<el-button
type="text"
size="small"
@click="rowCellJSON(row, index)"
>{{ row.$cellEdit ? "自定义保存" : "自定义修改" }}</el-button
>
</template>
ref="crudJSON"
:option="tableOption"
:data="jsonTableData"
@row-update="addUpdateJSON"
@row-del="rowDelJSON"
@row-save="rowSaveJSON"
>
<template slot-scope="{ row, index }" slot="menu">
<el-button
type="text"
size="small"
@click="rowCellJSON(row, index)"
>{{ row.$cellEdit ? "自定义保存" : "自定义修改" }}</el-button
>
</template>
</avue-crud>
</el-form-item>
</el-form>
@ -134,137 +77,141 @@
</div>
</template>
<script>
import formOperate from "@/layout/mixin/formOperate";
export default {
name: "apimanager_apienv",
mixins: [formOperate],
data() {
return {
formName: "apiEnv",
formLabelWidth: "120px",
//
refreshRoute: true,
//
queryParam: {
env_name: ""
},
formVisible: false,
formFieldsData: {
env_name: "",
env_json: ""
},
url: "apiTesterUserenv",
//
rules: {
env_name: [{ required: true, message: "请输入环境名称" }],
env_json: [{ required: true, message: "请输入环境变量" }]
},
jsonTableData: [],
tableOption: {
refreshBtn: false,
addBtn: false,
editBtn: false,
addRowBtn: true,
cancelBtn: false,
border: true,
columnBtn: false,
column: [
{
label: "Key",
prop: "key",
cell: true,
rules: [
{
required: true,
message: "Key值示例:{{KeyName}}",
trigger: "blur"
}
]
},
{
label: "Value",
prop: "value",
cell: true,
rules: [
{
required: true,
message: "请输入Value值",
trigger: "blur"
}
]
}
]
import formOperate from '@/layout/mixin/formOperate'
import { parseTime } from '@/utils'
export default {
name:'apimanager_apienv',
mixins: [formOperate],
data() {
return {
formName: 'apiEnv',
formLabelWidth: '120px',
//
refreshRoute: true,
//
queryParam: {
env_name: '',
},
formVisible: false,
formFieldsData: {
env_name: '',
env_json: ''
},
url: 'apiTesterUserenv',
//
rules: {
env_name: [
{ required: true, message: '请输入环境名称' }
],
env_json: [
{ required: true, message: '请输入环境变量' }
]
},
jsonTableData:[],
tableOption: {
refreshBtn:false,
addBtn: false,
editBtn: false,
addRowBtn: true,
cancelBtn: false,
border: true,
column: [
{
label: "Key",
prop: "key",
cell: true,
rules: [
{
required: true,
message: "Key值示例:{{KeyName}}",
trigger: "blur"
}
]
},
{
label: "Value",
prop: "value",
cell: true,
rules: [
{
required: true,
message: "请输入Value值",
trigger: "blur"
}
]
}
]
},
}
};
},
watch: {
formFieldsData: {
deep: true,
handler(data) {
if (data.env_json) {
let obj = this.JsonToObject(data.env_json);
let arr = Object.entries(obj).map(item => {
},
watch:{
formFieldsData:{
deep:true,
handler(data){
if(data.env_json){
let obj = this.JsonToObject(data.env_json)
let arr = Object.entries(obj).map(item => {
return { key: item[0], value: item[1], $cellEdit: false };
});
this.jsonTableData = arr;
}
}
}
}
},
methods: {
handleCreateEnv() {
this.jsonTableData = [];
this.handleCreate();
},
selectAPIenv(id) {
this.$http.get("apiTesterUserenv/selectAPIenv/" + id).then(response => {
this.$message.success(response.message);
this.handleRefresh();
});
},
// ApiBaseInfo Json Object
JsonToObject(json) {
if (json) {
let flag = /\'/.test(json);
if (flag) {
return JSON.parse(json.replace(/\'/gi, '"'));
methods: {
handleCreateEnv(){
this.jsonTableData = []
this.handleCreate()
},
selectAPIenv(id) {
this.$http.get( 'apiTesterUserenv/selectAPIenv/' + id).then(response => {
this.$message.success(response.message)
this.handleRefresh()
})
},
// ApiBaseInfo Json Object
JsonToObject(json) {
if (json) {
let flag = /\'/.test(json);
if (flag) {
return JSON.parse(json.replace(/\'/gi, '"'));
} else {
return JSON.parse(json);
}
} else {
return JSON.parse(json);
return null;
}
},
// JSON
rowCellJSON(row, index) {
this.$refs.crudJSON.rowCell(row, index);
},
// JSON
addUpdateJSON(form, index, done, loading) {
loading();
done();
},
// JSON
rowSaveJSON(form, done) {
done();
this.formFieldsData.env_json = this.handlerJson(this.jsonTableData);
},
// JSON
rowDelJSON(form, index, done) {
this.jsonTableData.splice(index, 1);
this.formFieldsData.env_json = this.handlerJson(this.jsonTableData);
},
handlerJson(arrData){
let cache = {};
arrData.forEach(item => {
cache[item.key] = item.value;
});
if(Object.keys(cache).length){
return JSON.stringify(cache);
}else{
return null;
}
} else {
return null;
}
},
// JSON
rowCellJSON(row, index) {
this.$refs.crudJSON.rowCell(row, index);
},
// JSON
addUpdateJSON(form, index, done, loading) {
loading();
done();
},
// JSON
rowSaveJSON(form, done) {
done();
this.formFieldsData.env_json = this.handlerJson(this.jsonTableData);
},
// JSON
rowDelJSON(form, index, done) {
this.jsonTableData.splice(index, 1);
this.formFieldsData.env_json = this.handlerJson(this.jsonTableData);
},
handlerJson(arrData) {
let cache = {};
arrData.forEach(item => {
cache[item.key] = item.value;
});
if (Object.keys(cache).length) {
return JSON.stringify(cache);
} else {
return null;
}
}
}
};
</script>

View File

@ -329,11 +329,11 @@ export default {
this.apiBaseInfo = response.data;
this.apiBaseInfo.body = this.apiBaseInfo.body.replace(/'/g, '"');
if (this.apiBaseInfo.body) {
let resstr = this.apiBaseInfo.body
.replace(/\\/g, "")
.replace(/"{/g, "{")
.replace(/}"/g, "}");
this.rawJson = JSON.parse(resstr);
// let resstr = this.apiBaseInfo.body
// .replace(/\\/g, "")
// .replace(/"{/g, "{")
// .replace(/}"/g, "}");
this.rawJson = JSON.parse(this.apiBaseInfo.body);
} else {
this.rawJson = {};
}

View File

@ -239,13 +239,13 @@
/>
</el-form-item>
<el-form-item
label="标识"
label="标识路由name"
:label-width="formLabelWidth"
prop="api_name"
>
<el-input
v-model="formFieldsData.api_name"
placeholder="请输入英文唯一标识"
placeholder="请输入英文唯一标识请与路由name字段一致"
autocomplete="off"
clearable
/>

View File

@ -0,0 +1,159 @@
<template>
<div class="app-container">
<el-form ref="form" :model="queryParam" :inline="true">
<el-form-item prop="rule" label="rule" :label-width="formLabelWidth">
<el-input v-model="queryParam.rule" placeholder="rule" type="input"></el-input>
</el-form-item><el-form-item prop="route" label="route" :label-width="formLabelWidth">
<el-input v-model="queryParam.route" placeholder="route" type="input"></el-input>
</el-form-item><el-form-item prop="method" label="method" :label-width="formLabelWidth">
<el-input v-model="queryParam.method" placeholder="method" type="input"></el-input>
</el-form-item><el-form-item prop="name" label="name" :label-width="formLabelWidth">
<el-input v-model="queryParam.name" placeholder="name" type="input"></el-input>
</el-form-item><el-form-item prop="domain" label="domain" :label-width="formLabelWidth">
<el-input v-model="queryParam.domain" placeholder="domain" type="input"></el-input>
</el-form-item><el-form-item prop="option" label="option" :label-width="formLabelWidth">
<el-input v-model="queryParam.option" placeholder="option" type="input"></el-input>
</el-form-item><el-form-item prop="pattern" label="pattern" :label-width="formLabelWidth">
<el-input v-model="queryParam.pattern" placeholder="pattern" type="input"></el-input>
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" type="primary" @click="handleSearch">
查询
</el-button>
</el-form-item>
</el-form>
<el-divider content-position="center"></el-divider>
<div class="filter-container">
<el-row>
<el-col :span="12">
<div class="grid-content">
<el-button class="filter-item" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
<!-- <el-button class="filter-item" type="primary" icon="el-icon-plus" @click="handleCreate()">添加</el-button> -->
<el-button type="primary" class="filter-item" icon="el-icon-refresh" @click="sync">
同步至数据库
</el-button>
<el-button v-if="this.selectedIds.length" size="small" class="filter-item mb-5" type="danger" icon="el-icon-delete" @click="handleMultiDelete">批量删除</el-button>
<el-button @click="clearFilter">清除所有过滤器</el-button>
</div>
</el-col>
<el-col :span="12">
<el-button icon="el-icon-info" circle @click="templateVersion" style="float: right; padding: 3px 0"></el-button>
<el-dropdown @command="handleTableCommand" style="float: right; padding: 3px 0">
<span class="el-dropdown-link">
<i class="el-icon-more el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="a">配置表格</el-dropdown-item>
<el-dropdown-item command="b" divided>移除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div class="grid-content">
<el-dropdown @command="handleDropdownCommand" style="float: right; padding: 3px 0">
<span class="el-dropdown-link">
<i class="el-icon-menu el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-checkbox-group v-model="checkList">
<el-dropdown-item command="a"><el-checkbox label="rule"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="route"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="method"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="name"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="domain"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="option"></el-checkbox></el-dropdown-item><el-dropdown-item command="a"><el-checkbox label="pattern"></el-checkbox></el-dropdown-item>
</el-checkbox-group>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-col>
</el-row>
</div>
<el-table ref="multipleTable" :data="data" tooltip-effect="dark" style="width: 100%" fit @selection-change="handleSelectMulti">
<el-table-column type="selection" width="55" v-if="true"></el-table-column>
<el-table-column prop="rule" label="rule" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="route" label="route" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="method" label="method" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="name" label="name" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="domain" label="domain" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="option" label="option" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="pattern" label="pattern" sortable="true" v-if="true"></el-table-column>
<el-table-column prop="creator" label="创建人" v-if="true"></el-table-column>
<el-table-column prop="created_at" label="创建时间" v-if="true"></el-table-column>
<el-table-column prop="updated_at" label="更新时间" v-if="true"></el-table-column>
<el-table-column label="操作" v-if="true" fixed="right">
<template slot-scope="module">
<el-button type="primary" icon="el-icon-stopwatch" @click="testApi(module.row.name)">API测试</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="paginate.current" hide-on-single-page :page-sizes="paginate.sizes" :page-size="paginate.limit" :layout="paginate.layout" :total="paginate.total"></el-pagination>
<!----------------------------------- 新增/编辑 ---------------------------------------------->
<el-drawer ref="drawer" size="60%" :title="drawerTitle" :visible.sync="formVisible" :before-close="handleClose" direction="rtl" @close="handleCancel">
<div class="demo-drawer__content">
<el-form :ref="formName" :model="formFieldsData" :rules="rules">
<el-form-item prop="rule" label="rule" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.rule" placeholder="rule" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="route" label="route" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.route" placeholder="route" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="method" label="method" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.method" placeholder="method" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="name" label="name" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.name" placeholder="name" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="domain" label="domain" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.domain" placeholder="domain" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="option" label="option" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.option" placeholder="option" autocomplete="off" clearable type="input"></el-input>
</el-form-item><el-form-item prop="pattern" label="pattern" :label-width="formLabelWidth">
<el-input v-model="formFieldsData.pattern" placeholder="pattern" autocomplete="off" clearable type="input"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="drawer__footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">{{ loading ? '提交中 ...' : '确 定' }}</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import formOperate from '@/layout/mixin/formOperate'
export default {
name:'apimanager_routeList',
mixins: [formOperate],
data() {
return {
url: 'routeList',
formName: 'route_list',
formLabelWidth: '120px',
//
queryParam: {
rule:'',route:'',method:'',name:'',domain:'',option:'',pattern:'',
},
formVisible: false,
formFieldsData: {
rule:'',route:'',method:'',name:'',domain:'',option:'',pattern:'',
},
loading: false,
checkList: [],
search: '',
drawerTitle: 'route_list',
form: {rule:'',route:'',method:'',name:'',domain:'',option:'',pattern:'', },
timer: null,
//
rules: {
}
}
},
mounted() {},
methods: {
testApi(name) {
let api_name = name.replace(/\\/g,"\\\\") //
this.$router.push({ path: "/apitester", query: { api_name} });
},
sync() {
this.$http.post('apimanager/routelist/sync').then(res => {
this.$message.success(res.message)
this.handleRefresh()
})
},
},
}
</script>

View File

@ -4,5 +4,6 @@ export default {
// api测试
apitester: () => import('@/views/apimanager/apitester'),
apirun: () => import('@/views/apimanager/apirun'),
apienv: () => import('@/views/apimanager/apienv')
apienv: () => import('@/views/apimanager/apienv'),
apimanager_routeList: () => import('@/views/apimanager/route_list/route_list'),
}

View File

@ -0,0 +1,93 @@
<?php
namespace catchAdmin\apimanager\controller;
use catcher\base\CatchRequest as Request;
use catcher\CatchResponse;
use catcher\base\CatchController;
use catchAdmin\apimanager\repository\RouteListRepository as RouteListModel;
use think\Response;
class RouteList extends CatchController
{
protected $routeListModel;
/**
*
* @time 2021/11/11 17:47
* @param RouteListModel $routeListModel
* @return mixed
*/
public function __construct(RouteListModel $routeListModel)
{
$this->routeListModel = $routeListModel;
}
/**
*
* @time 2021/11/11 17:47
* @return Response
*/
public function index() : Response
{
return CatchResponse::paginate($this->routeListModel->getList());
}
/**
*
* @time 2021/11/11 17:47
* @param Request $request
* @return Response
*/
public function save(Request $request) : Response
{
return CatchResponse::success($this->routeListModel->storeBy($request->post()));
}
/**
*
* @time 2021/11/11 17:47
* @param $id
* @return Response
*/
public function read($id) : Response
{
return CatchResponse::success($this->routeListModel->findBy($id));
}
/**
*
* @time 2021/11/11 17:47
* @param $id
* @param Request $request
* @return Response
*/
public function update($id, Request $request) : Response
{
return CatchResponse::success($this->routeListModel->updateBy($id, $request->post()));
}
/**
*
* @time 2021/11/11 17:47
* @param $id
* @return Response
*/
public function delete($id) : Response
{
return CatchResponse::success($this->routeListModel->deleteBy($id));
}
/**
* 同步
*
* @time 2021/11/11 17:47
* @return \think\response\Json
* @throws \Exception
*/
public function sync()
{
return CatchResponse::success($this->routeListModel->sync());
}
}

View File

@ -0,0 +1,48 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
use Phinx\Db\Adapter\MysqlAdapter;
class RouteList extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('route_list', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '路由表' ,'id' => 'id' ,'primary_key' => ['id']]);
$table->addColumn('rule', 'string', ['limit' => 128,'null' => true,'signed' => true,'comment' => 'rule',])
->addColumn('route', 'string', ['limit' => 256,'null' => true,'signed' => true,'comment' => 'route',])
->addColumn('method', 'string', ['limit' => 16,'null' => true,'signed' => true,'comment' => 'method',])
->addColumn('name', 'string', ['limit' => 256,'null' => true,'signed' => true,'comment' => 'name',])
->addColumn('domain', 'string', ['limit' => 128,'null' => true,'signed' => true,'comment' => 'domain',])
->addColumn('option', 'string', ['limit' => 256,'null' => true,'signed' => true,'comment' => 'option',])
->addColumn('pattern', 'string', ['limit' => 128,'null' => true,'signed' => true,'comment' => 'pattern',])
->addColumn('title', 'string', ['limit' => 128,'null' => true,'signed' => true,'comment' => 'title',])
->addColumn('created_at', 'integer', ['limit' => MysqlAdapter::INT_REGULAR,'null' => false,'default' => 0,'signed' => false,'comment' => '创建时间',])
->addColumn('updated_at', 'integer', ['limit' => MysqlAdapter::INT_REGULAR,'null' => false,'default' => 0,'signed' => false,'comment' => '更新时间',])
->addColumn('deleted_at', 'integer', ['limit' => MysqlAdapter::INT_REGULAR,'null' => false,'default' => 0,'signed' => false,'comment' => '软删除字段',])
->addColumn('creator_id', 'integer', ['limit' => MysqlAdapter::INT_REGULAR,'null' => false,'default' => 0,'signed' => false,'comment' => '创建人ID',])
->addIndex(['name'], ['unique' => true,'name' => 'route_list_name'])
->create();
}
}

View File

@ -9,7 +9,6 @@
// | Author: UCToo <contact@uctoo.com>
// +----------------------------------------------------------------------
use think\facade\Log;
use think\migration\Seeder;
class ApimanagerMenusSeed extends Seeder
@ -24,7 +23,6 @@ class ApimanagerMenusSeed extends Seeder
*/
public function run()
{
Log::write("ApimanagerMenusSeed",'debug');
\catcher\Utils::importTreeData($this->getPermissions(), 'permissions', 'parent_id');
}
@ -33,7 +31,7 @@ class ApimanagerMenusSeed extends Seeder
return array (
0 =>
array (
'id' => 136,
'id' => 143,
'permission_name' => 'API管理',
'parent_id' => 0,
'level' => '',
@ -47,97 +45,430 @@ class ApimanagerMenusSeed extends Seeder
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 10,
'created_at' => 1622926698,
'updated_at' => 1622959419,
'sort' => 1,
'created_at' => 1621425807,
'updated_at' => 1621427128,
'deleted_at' => 0,
),
1 =>
array (
'id' => 144,
'permission_name' => 'API分类',
'parent_id' => 143,
'level' => '',
'route' => '/apicategory',
'icon' => 'el-icon-s-grid',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory',
'component' => 'apicategory',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 10,
'created_at' => 1621413029,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
2 =>
array (
'id' => 145,
'permission_name' => 'API测试列表',
'parent_id' => 143,
'level' => '',
'route' => '/apitester',
'icon' => 'el-icon-stopwatch',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester',
'component' => 'apitester',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621479275,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
3 =>
array (
'id' => 146,
'permission_name' => '列表',
'parent_id' => 145,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester@index',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621778966,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
4 =>
array (
'id' => 147,
'permission_name' => '创建',
'parent_id' => 145,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester@save',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779011,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
5 =>
array (
'id' => 148,
'permission_name' => '更新',
'parent_id' => 145,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester@update',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779033,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
6 =>
array (
'id' => 149,
'permission_name' => '读取',
'parent_id' => 145,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester@read',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779051,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
7 =>
array (
'id' => 150,
'permission_name' => '删除',
'parent_id' => 145,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester@delete',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779083,
'updated_at' => 1624010086,
'deleted_at' => 0,
),
8 =>
array (
'id' => 151,
'permission_name' => '列表',
'parent_id' => 144,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory@index',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779121,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
9 =>
array (
'id' => 152,
'permission_name' => '创建',
'parent_id' => 144,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory@save',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779137,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
10 =>
array (
'id' => 153,
'permission_name' => '更新',
'parent_id' => 144,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory@update',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779154,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
11 =>
array (
'id' => 154,
'permission_name' => '读取',
'parent_id' => 144,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory@read',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779171,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
12 =>
array (
'id' => 155,
'permission_name' => '删除',
'parent_id' => 144,
'level' => '',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory@delete',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1621779186,
'updated_at' => 1624010103,
'deleted_at' => 0,
),
13 =>
array (
'id' => 156,
'permission_name' => 'API运行',
'parent_id' => 143,
'level' => '',
'route' => '/apimanager/apirun',
'icon' => 'el-icon-position',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apirun',
'component' => 'apirun',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 2,
'sort' => 1,
'created_at' => 1621798022,
'updated_at' => 1621831249,
'deleted_at' => 0,
),
14 =>
array (
'id' => 161,
'permission_name' => 'API环境变量',
'parent_id' => 143,
'level' => '',
'route' => '/apienv',
'icon' => 'el-icon-setting',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apienv',
'component' => 'apienv',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 1,
'created_at' => 1622176953,
'updated_at' => 1622177106,
'deleted_at' => 0,
),
15 =>
array (
'id' => 281,
'permission_name' => '路由列表',
'parent_id' => 143,
'level' => '143',
'route' => '/apimanager/routeList/curd',
'icon' => 'el-icon-link',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList',
'component' => 'apimanager_routeList',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 0,
'created_at' => 1636624116,
'updated_at' => 1636689266,
'deleted_at' => 0,
),
16 =>
array (
'id' => 282,
'permission_name' => '列表',
'parent_id' => 281,
'level' => '143-281',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList@index',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1636624117,
'updated_at' => 1636689266,
'deleted_at' => 0,
),
17 =>
array (
'id' => 283,
'permission_name' => '保存',
'parent_id' => 281,
'level' => '143-281',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList@save',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1636624117,
'updated_at' => 1636689266,
'deleted_at' => 0,
),
18 =>
array (
'id' => 284,
'permission_name' => '读取',
'parent_id' => 281,
'level' => '143-281',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList@read',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1636624118,
'updated_at' => 1636689266,
'deleted_at' => 0,
),
19 =>
array (
'id' => 285,
'permission_name' => '更新',
'parent_id' => 281,
'level' => '143-281',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList@update',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1636624118,
'updated_at' => 1636689266,
'deleted_at' => 0,
),
20 =>
array (
'id' => 286,
'permission_name' => '删除',
'parent_id' => 281,
'level' => '143-281',
'route' => '',
'icon' => '',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'routeList@delete',
'component' => '',
'redirect' => '',
'keepalive' => 1,
'type' => 2,
'hidden' => 1,
'sort' => 1,
'created_at' => 1636624119,
'updated_at' => 1636689266,
'deleted_at' => 0,
'children' =>
array (
0 =>
array (
'id' => 137,
'permission_name' => 'API分类',
'parent_id' => 136,
'level' => '',
'route' => '/apicategory',
'icon' => 'el-icon-s-grid',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apicategory',
'component' => 'apicategory',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 10,
'created_at' => 1622928640,
'updated_at' => 1622959419,
'deleted_at' => 0,
),
1 =>
array (
'id' => 138,
'permission_name' => 'API环境变量',
'parent_id' => 136,
'level' => '',
'route' => '/apienv',
'icon' => 'el-icon-setting',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apienv',
'component' => 'apienv',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 9,
'created_at' => 1622930243,
'updated_at' => 1622959419,
'deleted_at' => 0,
),
2 =>
array (
'id' => 140,
'permission_name' => 'API测试列表',
'parent_id' => 136,
'level' => '',
'route' => '/apitester',
'icon' => 'el-icon-stopwatch',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apitester',
'component' => 'apitester',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 1,
'sort' => 8,
'created_at' => 1622951640,
'updated_at' => 1622959419,
'deleted_at' => 0,
),
3 =>
array (
'id' => 141,
'permission_name' => 'API运行',
'parent_id' => 136,
'level' => '',
'route' => '/apimanager/apirun',
'icon' => 'el-icon-position',
'module' => 'apimanager',
'creator_id' => 1,
'permission_mark' => 'apirun',
'component' => 'apirun',
'redirect' => '',
'keepalive' => 1,
'type' => 1,
'hidden' => 2,
'sort' => 1,
'created_at' => 1622951894,
'updated_at' => 1622959419,
'deleted_at' => 0,
),
),
),
);
}

View File

@ -0,0 +1,66 @@
<?php
// +----------------------------------------------------------------------
// | UCToo [ Universal Convergence Technology ]
// +----------------------------------------------------------------------
// | Copyright (c) 2014-2021 https://www.uctoo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: UCToo <contact@uctoo.com>
// +----------------------------------------------------------------------
namespace catchAdmin\apimanager\model;
use catchAdmin\apimanager\model\search\RouteListSearch;
use catcher\base\CatchModel as Model;
/**
*
* @property int $id
* @property string $rule
* @property string $route
* @property string $method
* @property string $name
* @property string $domain
* @property string $option
* @property string $pattern
* @property string $title
* @property int $created_at
* @property int $updated_at
* @property int $deleted_at
* @property int $creator_id
*/
class RouteList extends Model
{
use RouteListSearch;
public $field = [
//
'id',
//
'rule',
//
'route',
//
'method',
//
'name',
//
'domain',
//
'option',
//
'pattern',
//
'title',
// 创建时间
'created_at',
// 更新时间
'updated_at',
// 软删除字段
'deleted_at',
// 创建人ID
'creator_id',
];
public $name = 'route_list';
}

View File

@ -0,0 +1,50 @@
<?php
// +----------------------------------------------------------------------
// | UCToo [ Universal Convergence Technology ]
// +----------------------------------------------------------------------
// | Copyright (c) 2014-2021 https://www.uctoo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: UCToo <contact@uctoo.com>
// +----------------------------------------------------------------------
namespace catchAdmin\apimanager\model\search;
use catchAdmin\apimanager\model\ApiCategory;
trait RouteListSearch
{
public function searchRuleAttr($query, $value, $data)
{
return $query->whereLike('rule', $value);
}
public function searchRouteAttr($query, $value, $data)
{
return $query->whereLike('route', $value);
}
public function searchMethodAttr($query, $value, $data)
{
return $query->whereLike('method', $value);
}
public function searchNameAttr($query, $value, $data)
{
return $query->whereLike('name', $value);
}
public function searchDomainAttr($query, $value, $data)
{
return $query->whereLike('domain', $value);
}
public function searchOptionAttr($query, $value, $data)
{
return $query->whereLike('option', $value);
}
public function searchPatternAttr($query, $value, $data)
{
return $query->whereLike('pattern', $value);
}
}

View File

@ -0,0 +1,66 @@
<?php
// +----------------------------------------------------------------------
// | UCToo [ Universal Convergence Technology ]
// +----------------------------------------------------------------------
// | Copyright (c) 2014-2021 https://www.uctoo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: UCToo <contact@uctoo.com>
// +----------------------------------------------------------------------
namespace catchAdmin\apimanager\repository;
use catchAdmin\permissions\middleware\PermissionsMiddleware;
use catchAdmin\apimanager\model\RouteList;
use catcher\base\CatchRepository;
use catcher\exceptions\FailedException;
use think\facade\Console;
use think\facade\Log;
use think\facade\Db;
class RouteListRepository extends CatchRepository
{
protected $routeList;
public function __construct(RouteList $routeList)
{
$this->routeList = $routeList;
}
protected function model()
{
return $this->routeList;
}
public function all()
{
$routeList = $this->routeList->select();
return $routeList->toArray();
}
/**
* 同步
*
* @time 2020年06月26日
* @throws \Exception
* @return bool
*/
public function sync()
{
DB::table('route_list')->delete(true);
Console::call('route:list', ['-m']); //没用也不是从命令生成的route_list文件读的数据就是想执行一下命令
$routeList = app()->route->getRuleList();
$rows = [];
foreach ($routeList as $item) {
$item['route'] = $item['route'] instanceof \Closure ? '<Closure>' : $item['route'];
$item['option'] = json_encode($item['option']);
$item['pattern'] = json_encode($item['pattern']);
$rows[] = $item;
}
$res = $this->routeList->saveAll($rows);
return true;
}
}

View File

@ -23,4 +23,7 @@ $router->group(function () use ($router){
$router->get('apiTesterUserenv/selectAPIenv/<id>', '\catchAdmin\apimanager\controller\ApiTesterUserenv@selectAPIenv');
// apiTesterLog路由
$router->resource('apiTesterLog', '\catchAdmin\apimanager\controller\ApiTesterLog');
// routeList 路由
$router->resource('routeList', catchAdmin\apimanager\controller\RouteList::class);
$router->post('apimanager/routelist/sync', 'catchAdmin\apimanager\controller\RouteList@sync');
})->middleware('auth');

10
public/index.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>catchadmin app server</title>
</head>
<body>
catchadmin app server V2.0.0
</body>
</html>