Browse Source

服务访问表创建

master
wenlele 1 year ago
parent
commit
3bcb297675
  1. 76
      api/app/lib/controllers/latestMetadata/index.js
  2. 5
      api/app/lib/index.js
  3. 89
      api/app/lib/models/restful_api.js
  4. 53
      api/app/lib/models/restful_api_record.js
  5. 4
      api/app/lib/routes/latestMetadata/index.js
  6. 65
      scripts/0.0.9/01_alter_t_restful_api.sql
  7. 1
      web/client/src/sections/dataQuality/containers/documentLibrary.js
  8. 12
      web/client/src/sections/metadataManagement/actions/businessMetadata.js
  9. 89
      web/client/src/sections/metadataManagement/components/releaseModal.js
  10. 2
      web/client/src/utils/webapi.js

76
api/app/lib/controllers/latestMetadata/index.js

@ -529,29 +529,29 @@ async function postMetadataResourceApplications (ctx) {
try {
const { resourceName, applyBy, resourceType, resourceId } = ctx.request.body;
if (!resourceName || !applyBy || !resourceType || !resourceId) {
ctx.status = 400;
ctx.body = { message: '参数不全,请重新申请资源' }
} else {
const models = ctx.fs.dc.models;
const postOne = await models.ResourceConsumption.findOne({
where: { applyBy: applyBy, resourceName: resourceName, resourceId, resourceType, approve_remarks: null }
});
if (postOne) {
ctx.status = 400;
ctx.body = { message: '参数不全,请重新申请资源' }
} else {
const models = ctx.fs.dc.models;
const postOne = await models.ResourceConsumption.findOne({
where: { applyBy: applyBy, resourceName: resourceName, resourceId, resourceType, approve_remarks: null }
});
if (postOne) {
ctx.status = 400;
ctx.body = { message: '该用户已申请过该元数据资源' }
} else {
await models.ResourceConsumption.create({ applyAt: moment(), approveState: '审批中', ...ctx.request.body });
ctx.body = { message: '申请资源成功' }
ctx.status = 200;
}
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "申请资源失败"
}
}
ctx.body = { message: '该用户已申请过该元数据资源' }
} else {
await models.ResourceConsumption.create({ applyAt: moment(), approveState: '审批中', ...ctx.request.body });
ctx.body = { message: '申请资源成功' }
ctx.status = 200;
}
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "申请资源失败"
}
}
}
//获取元数据资源申请记录
@ -827,6 +827,35 @@ async function listStructuredData (ctx) {
}
}
}
//发布REST服务
async function publishingServices (ctx) {
let message = "发布REST服务失败"
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
const { method, url } = data
const postOne = await models.MetadataRestapi.findOne({ where: { method: method, url: url } });
if (postOne) {
message = '路由和请求方式重复'
throw ''
}
await models.MetadataRestapi.create(data)
ctx.body = { message: '发布REST服务成功' }
ctx.status = 200;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": message
}
}
}
module.exports = {
getResourceCatalog,
postResourceCatalog,
@ -850,5 +879,6 @@ module.exports = {
postMetadataRestapis,
putMetadataRestapis,
delMetadataRestapis,
listStructuredData
listStructuredData,
publishingServices
}

5
api/app/lib/index.js

@ -56,7 +56,7 @@ module.exports.models = function (dc) {
const {
DataSource, AcquisitionTask, Adapter, User, MetadataDatabase, MetadataFile, MetadataRestapi, AcquisitionLog, ResourceCatalog,
BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi,ResourceConsumption,BusinessRule,StandardDoc
BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi,ResourceConsumption,BusinessRule,StandardDoc,RestfulApi,RestfulApiRecord
} = dc.models;
AcquisitionTask.belongsTo(DataSource, { foreignKey: 'dataSourceId', targetKey: 'id' });
@ -92,4 +92,7 @@ module.exports.models = function (dc) {
BusinessRule.belongsTo(StandardDoc, { foreignKey: 'ruleBasis', targetKey: 'id' });
StandardDoc.hasMany(BusinessRule, { foreignKey: 'ruleBasis', targetKey: 'id' });
RestfulApi.belongsTo(RestfulApiRecord, { foreignKey: 'restServiceId', targetKey: 'id' });
RestfulApiRecord.belongsTo(RestfulApi, { foreignKey: 'restServiceId', targetKey: 'id' });
};

89
api/app/lib/models/restful_api.js

@ -0,0 +1,89 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const RestfulApi = sequelize.define("restfulApi", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_restful_api_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "接口名称",
primaryKey: false,
field: "name",
autoIncrement: false
},
url: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "接口路由",
primaryKey: false,
field: "url",
autoIncrement: false
},
method: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "请求方法",
primaryKey: false,
field: "method",
autoIncrement: false
},
table: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "数据库表名称",
primaryKey: false,
field: "table",
autoIncrement: false
},
conditions: {
type: DataTypes.JSONB,
allowNull: true,
defaultValue: null,
comment: "数据库表查询条件",
primaryKey: false,
field: "conditions",
autoIncrement: false
},
enabled: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null,
comment: "是否已启用",
primaryKey: false,
field: "enabled",
autoIncrement: false
},
fields: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: '数据库表字段',
primaryKey: false,
field: "fields",
autoIncrement: false
}
}, {
tableName: "t_restful_api",
comment: "",
indexes: []
});
dc.models.RestfulApi = RestfulApi;
return RestfulApi;
};

53
api/app/lib/models/restful_api_record.js

@ -0,0 +1,53 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const RestfulApiRecord = sequelize.define("restfulApiRecord", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "唯一标识",
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_restful_api_record_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "接口名称",
primaryKey: false,
field: "name",
autoIncrement: false
},
visitTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "访问时间",
primaryKey: false,
field: "visit_time",
autoIncrement: false
},
token: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "令牌",
primaryKey: false,
field: "token",
autoIncrement: false
},
}, {
tableName: "t_restful_api_record",
comment: "",
indexes: []
});
dc.models.RestfulApiRecord = RestfulApiRecord;
return RestfulApiRecord;
};

4
api/app/lib/routes/latestMetadata/index.js

@ -71,4 +71,8 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/listStructuredData'] = { content: '获取对表的库与字段信息', visible: true };
router.get('/listStructuredData', latestMetadata.listStructuredData);
app.fs.api.logAttr['POST/publishing/services'] = { content: '发布REST服务', visible: true };
router.post('/publishing/services', latestMetadata.publishingServices);
};

65
scripts/0.0.9/01_alter_t_restful_api.sql

@ -0,0 +1,65 @@
create table t_restful_api
(
id serial not null,
"table" varchar(255) not null,
fields varchar(255) not null,
conditions jsonb not null,
name varchar(255) not null,
method varchar(10) not null,
url varchar(255) not null,
enabled boolean default true not null
);
comment on table t_restful_api is 'REST服务';
comment on column t_restful_api.id is 'ID唯一标识';
comment on column t_restful_api."table" is '数据库表名称';
comment on column t_restful_api.fields is '数据库表字段';
comment on column t_restful_api.conditions is '数据库表查询条件';
comment on column t_restful_api.name is '接口名称';
comment on column t_restful_api.method is '请求方法';
comment on column t_restful_api.url is '接口路由';
comment on column t_restful_api.enabled is '是否已启用';
create unique index t_restful_api_id_uindex
on t_restful_api (id);
alter table t_restful_api
add constraint t_restful_api_pk
primary key (id);
create table t_restful_api_record
(
id serial not null,
rest_service_id int not null,
visit_time timestamp not null,
token varchar(255) not null
);
comment on table t_restful_api_record is 'REST服务访问记录';
comment on column t_restful_api_record.rest_service_id is 'rest服务id';
comment on column t_restful_api_record.visit_time is '访问时间';
comment on column t_restful_api_record.token is '令牌';
create unique index t_restful_api_record_id_uindex
on t_restful_api_record (id);
alter table t_restful_api_record
add constraint t_restful_api_record_pk
primary key (id);

1
web/client/src/sections/dataQuality/containers/documentLibrary.js

@ -148,7 +148,6 @@ function Approve ({ loading, clientHeight, actions, dispatch, }) {
}
})
console.log(fileUrl);
packBulk({ fileUrl: fileUrl })
} else {
if (folderId.current.length > 0) {

12
web/client/src/sections/metadataManagement/actions/businessMetadata.js

@ -163,3 +163,15 @@ export function listStructuredData (query = {}) {
reducer: {}
});
}
export function publishingServices (data={}) {
return dispatch => basicAction({
type: 'post',
data: data,
dispatch: dispatch,
actionType: 'POST_PUBLISHING_SERVICES',
url: ApiTable.publishingServices,
msg: { option: '发布REST服务' },
reducer: {}
});
}

89
web/client/src/sections/metadataManagement/components/releaseModal.js

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Modal, Input, Form, Tabs, Avatar, Checkbox, Button, Select } from 'antd';
import { Modal, Input, Form, Tabs, Avatar, Checkbox, Button, Select, message } from 'antd';
import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
const { TabPane } = Tabs;
const { TextArea } = Input;
@ -14,7 +14,10 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
const [database, setDatabase] = useState({})
const [fieldValue, setFiedValue] = useState([])
const [indeterminate, setIndeterminate] = useState(false)
const [fromData, setFromData] = useState([{ value1: '', value2: '', value3: '' }])
const [fromData, setFromData] = useState([{ field: '', condition: '', value: '' }])
const [interfaceName, setInterfaceName] = useState('')
const [interfaceUrl, setInterfaceUrl] = useState('')
const [interfaceMode, setInterfaceMode] = useState('GET')
const [sql, setSql] = useState()
const [form] = Form.useForm();
useEffect(() => {
@ -37,7 +40,6 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
{ value: '<', label: '<' },
{ value: '>=', label: '>=' },
{ value: '<=', label: '<=' },
{ value: 'BETWEEN', label: 'BETWEEN' },
{ value: 'LIKE', label: 'LIKE' },
{ value: 'IN', label: 'IN' }]
@ -100,37 +102,37 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
{fromData?.map((f, index) => {
return <div key={'key' + index} style={{ display: 'flex', marginBottom: 16 }}>
<Select
style={{ width: 200, marginRight: 10 }} value={f.value1 || null}
style={{ width: 200, marginRight: 10 }} value={f.field || null}
placeholder="请选择字段"
onChange={v => {
fromData?.splice(index, 1, { value1: v, value2: f.value2, value3: f.value3 })
fromData?.splice(index, 1, { field: v, condition: f.condition, value: f.value })
setFromData([...fromData])
}}
options={fieldValue?.map(d => ({ value: d, label: d })) || []}
allowClear
/>
<Select
style={{ width: 200, marginRight: 10 }} value={f.value2 || null}
style={{ width: 200, marginRight: 10 }} value={f.condition || null}
placeholder="请选择条件"
onChange={v => {
fromData?.splice(index, 1, { value1: f.value1, value2: v, value3: f.value3 })
fromData?.splice(index, 1, { field: f.field, condition: v, value: f.value })
setFromData([...fromData])
}}
options={operator}
allowClear
/>
<Input
style={{ width: 200, marginRight: 10 }} value={f.value3}
style={{ width: 200, marginRight: 10 }} value={f.value}
placeholder="请输入值"
onChange={v => {
fromData?.splice(index, 1, { value1: f.value1, value2: f.value2, value3: v?.target?.value })
fromData?.splice(index, 1, { field: f.field, condition: f.condition, value: v?.target?.value })
setFromData([...fromData])
}}
allowClear
/>
{index == 0 ?
<PlusCircleOutlined style={{ fontSize: 20, marginTop: 6 }} onClick={() => {
fromData?.splice(index, 0, { value1: '', value2: '', value3: '' })
fromData?.splice(index, 0, { field: '', condition: '', value: '' })
setFromData([...fromData])
}} />
: <MinusCircleOutlined style={{ fontSize: 20, marginTop: 6 }} onClick={() => {
@ -143,31 +145,43 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
</TabPane>
<TabPane key="SQL">
<div style={{ fontSize: 20, marginBottom: 20, fontWeight: 600, display: 'flex', justifyContent: 'space-between' }}>预览SQL<Button onClick={() => {
console.log(editData);
console.log(database);
console.log(fromData);
let whereOption = []
fromData.map(s => {
whereOption.push(`${editData?.code}.${s.value1} ${s.value2} ${s.value3}`);
})
let sqlData = `SELECT * FROM ${editData?.code} ${fromData.length ? 'WHERE ' + whereOption.join(' AND ') : ''}`
setSql(sqlData)
}}>生成SQL</Button></div>
<div style={{ fontSize: 20, marginBottom: 20, fontWeight: 600, display: 'flex', justifyContent: 'space-between' }}>预览SQL<Button
onClick={() => {
let whereOption = []
let integrity = true
fromData.map(u => {
if (integrity) {
if (u.field && u.condition && u.value) {
whereOption.push(`${editData?.code}.${u.field} ${u.condition} ${"'" + (u.condition == 'LIKE' ? ('%' + u.value + '%') : u.value) + "'"}`);
} else if (!u.field && !u.condition && !u.value) {
} else {
integrity = false
}
}
})
if (integrity) {
let sqlData = `SELECT * FROM ${editData?.code} ${whereOption?.length ? 'WHERE ' + whereOption.join(' AND ') : ''}`
setSql(sqlData)
} else {
message.warning({
duration: 1,
content: 'sql配置条件不满足',
});
}
}}>生成SQL</Button></div>
<TextArea value={sql} autoSize={{ minRows: 5 }} disabled={true} />
</TabPane>
<TabPane key="release">
<div style={{ fontSize: 20, marginBottom: 10, fontWeight: 600 }}>服务发布</div>
<div style={{ display: 'flex' }}>接口名称<Input style={{ width: 180 }} placeholder='请输入' /></div>
<div style={{ display: 'flex' }}>接口名称<Input style={{ width: 180 }} placeholder='请输入' onChange={e => {
setInterfaceName(e.target.value)
}} /></div>
<div style={{ display: 'flex', margin: '10px 0 10px 30px' }}>URL <Select
style={{ width: 200, marginRight: 10 }}
onChange={v => {
setInterfaceMode(v)
}}
defaultValue={"GET"}
options={[{ value: "GET", label: "GET" },
@ -176,7 +190,9 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
{ value: "DEL", label: "DEL" },]}
allowClear
/>
<Input style={{ width: 180, marginLeft: 20 }} placeholder='请输入' />
<Input style={{ width: 180, marginLeft: 20 }} placeholder='请输入' onClick={() => {
setInterfaceUrl(e.target.value)
}} />
</div>
<div style={{ display: 'flex', marginLeft: 20 }}>返回值
<div style={{ display: 'flex', width: 310, flexDirection: 'column', alignItems: 'center' }}>
@ -219,6 +235,25 @@ const ReleaseModal = ({ actions, dispatch, onConfirm, onCancel, editData = {} })
}}>下一步</Button> : ""}
{activeKey == 'release' ? <Button type="primary" style={{ marginLeft: 10 }} onClick={() => {
if (!interfaceName || !interfaceMode || !interfaceUrl) {
message.warning({
duration: 1,
content: '缺少发布参数',
});
} else if (!sql) {
message.warning({
duration: 1,
content: '缺少SQL语句,请生成SQL',
});
}
dispatch(metadataManagement.publishingServices({})).then(res => {
if (res.success) {
}
})
}}>完成 </Button> : ""}
<Button style={{ marginLeft: 10 }} onClick={() => onCancel()}>取消</Button>
</div>

2
web/client/src/utils/webapi.js

@ -23,6 +23,8 @@ export const ApiTable = {
putResourceCatalog: 'resource-catalog/{id}',
delResourceCatalog: 'resource-catalog/{id}',
listStructuredData: 'listStructuredData',
publishingServices:'publishing/services',
//最新元数据-元数据列表查询
getMetadataDatabases: 'metadata/databases',
getMetadataFiles: 'metadata/files',

Loading…
Cancel
Save