diff --git a/front/src/components/DocView/index.vue b/front/src/components/DocView/index.vue index c0a88c166b5eedab3816129a9e0de493b5df0f06..f5c83a6022b48080521fb9f74105f645ff53f0b0 100644 --- a/front/src/components/DocView/index.vue +++ b/front/src/components/DocView/index.vue @@ -18,12 +18,12 @@
- +
- +
@@ -65,7 +65,8 @@ size="small" effect="plain" class="copyBtn" - @click.stop="copyCurl(buildRequestUrl(hostConfig))">{{ $t('copy') + 'CURL' }} + @click.stop="copyCurl(buildRequestUrl(hostConfig))" + >{{ $t('copy') + 'CURL' }}
@@ -81,15 +82,15 @@

{{ $t('description') }}

-
+

ContentType{{ docInfo.contentType }}

@@ -189,7 +190,7 @@
{{ $t('updateRemark') }} -
+
-

+

@@ -244,7 +245,7 @@ import CodeGenDrawer from '@/components/CodeGenDrawer' import CopyText from '@/components/CopyText' import ExportUtil from '@/utils/export' import { generate } from 'json2interface' -import {get_effective_url, parse_root_array, StringBuilder} from '@/utils/common' +import { get_effective_url, parse_root_array, StringBuilder } from '@/utils/common' import { mavonEditor } from 'mavon-editor' export default { @@ -529,7 +530,7 @@ export default { // --location --globoff const str = new StringBuilder(`curl -L -g `) - const httpMethod = this.docInfo.httpMethod; + const httpMethod = this.docInfo.httpMethod if (httpMethod === 'POST' || httpMethod === 'PUT') { str.append(`-X ${httpMethod} `) } diff --git a/front/src/components/ParameterCompareTable/index.vue b/front/src/components/ParameterCompareTable/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..9c9765b6ef8ccbd298430c719bd94874e91a9750 --- /dev/null +++ b/front/src/components/ParameterCompareTable/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/front/src/utils/http.js b/front/src/utils/http.js index dbf9f8c5c88a6fd0245168a210ec51fb3cfe3da7..107a7b0ce2c460603cbb7bcc43d96c1fe9e31d11 100644 --- a/front/src/utils/http.js +++ b/front/src/utils/http.js @@ -20,7 +20,7 @@ const frontURL = getBrowserUrl() const serverUrl = process.env.VUE_APP_BASE_API || frontURL // 创建axios实例 -const client = axios.create({ +export const client = axios.create({ baseURL: serverUrl, // api 的 base_url timeout: 600000, // 请求超时时间,600秒 headers: { diff --git a/front/src/utils/i18n/languages/en-us.js b/front/src/utils/i18n/languages/en-us.js index 569be7a2b69c42d170886f4325f475cd53e6079f..b9d6def9bd28dfa7eb38d265be5820b79b57071b 100644 --- a/front/src/utils/i18n/languages/en-us.js +++ b/front/src/utils/i18n/languages/en-us.js @@ -562,6 +562,10 @@ export default { 'weComWebhookUrlPlacehoder': 'Input full url with token parameter', 'weComWebhookUrlTip': 'Push message to weCom group when doc is changed' }, + ModSetting: { + 'isMonitorSetting': 'Is Monitor', + 'isMonitorSettingTip': 'When monitoring is enabled, data will be pushed to the document push table for parameter difference comparison. otherwise, the registration document will be updated' + }, UserInfo: { 'bindDingDingTip': 'Use DingDing app scan the QR to bind account' }, @@ -569,7 +573,8 @@ export default { 'weComSetting': 'WeCom Setting', 'dingdingSetting': 'DingTalk Setting', 'swaggerSetting': 'Swagger Setting', - 'meterSphereSetting': 'MeterSphere Setting' + 'meterSphereSetting': 'MeterSphere Setting', + 'modSetting': 'Base Setting' }, DocChangelog: { confirmRestore: 'Restore this version?' diff --git a/front/src/utils/i18n/languages/zh-cn.js b/front/src/utils/i18n/languages/zh-cn.js index 05d9f5d4b08a630da64f45479abc69ff5774c6d3..5336855d170561d5fe1faba247ae4c1878398d85 100644 --- a/front/src/utils/i18n/languages/zh-cn.js +++ b/front/src/utils/i18n/languages/zh-cn.js @@ -565,6 +565,10 @@ export default { 'weComWebhookUrlPlacehoder': '输入完整带token参数的url', 'weComWebhookUrlTip': '当文档变更时推送消息到企业微信群。' }, + ModSetting: { + 'isMonitorSetting': '是否监控', + 'isMonitorSettingTip': '开启监控则推送数据入文档推送表,用于比对参数差异。否则更新登记文档' + }, UserInfo: { 'bindDingDingTip': '使用钉钉App扫一扫进行账号绑定' }, @@ -572,7 +576,8 @@ export default { 'weComSetting': '企业微信配置', 'dingdingSetting': '钉钉配置', 'swaggerSetting': 'Swagger设置', - 'meterSphereSetting': 'MeterSphere设置' + 'meterSphereSetting': 'MeterSphere设置', + 'modSetting': '基础配置' }, DocChangelog: { confirmRestore: '确认还原到此版本吗?' diff --git a/front/src/views/project/DocTable/index.vue b/front/src/views/project/DocTable/index.vue index 9416b6ff99d5e4b99dc3d18a9528d94ac10211a8..a9a96002916e18b46c098bfac60a0ff994a1a55b 100644 --- a/front/src/views/project/DocTable/index.vue +++ b/front/src/views/project/DocTable/index.vue @@ -75,6 +75,7 @@ :height="tableHeight" :row-height="30" border + :row-style="rowStyle" >

+
+ + + +
md @@ -233,6 +239,17 @@ :title="$t('unlock')" :command="() => { onDocUnLock(scope.row) }" /> + + + + + +
@@ -300,6 +317,7 @@ import SvgIcon from '@/components/SvgIcon' import TimeTooltip from '@/components/TimeTooltip' import DocExportDialog from '@/components/DocExportDialog' import PopoverUpdate from '@/components/PopoverUpdate' +import { client } from '@/utils/http' export default { name: 'DocTable', @@ -336,7 +354,9 @@ export default { status: null, filterEmptyFolder: null }, - token: '' + token: '', + docDiffMap: {}, // 文档比对差异map {key: docId, value: diffType} + docParamIgnoreMap: {} // 文档参数比对是否忽略map {key: docId, value: 1 | 0} } }, computed: { @@ -397,9 +417,22 @@ export default { } Object.assign(searchData, this.searchForm) this.post('/doc/list-v2', searchData, function(resp) { - this.tableData = this.convertTree(resp.data) - callback && callback.call(this) - this.loading = false + Promise.all([client.get('/doc/view/compare/doc/result', { moduleId: moduleId }), + client.get('/doc/view/ext/isIgnoreCompareParam/' + moduleId + '/list')]).then(resps => { + this.tableData = this.convertTree(resp.data) + callback && callback.call(this) + this.loading = false + // 组装文档比对差异map + this.docDiffMap = {} + resps[0].data.data.forEach(docDiff => { + this.docDiffMap[docDiff.docId] = docDiff.diffType + }) + // 组装是否忽略参数比对map + this.docParamIgnoreMap = {} + resps[1].data.data.forEach(docInfoExt => { + this.docParamIgnoreMap[docInfoExt.docId] = docInfoExt.docValue + }) + }) }) this.loadToken(moduleId) }, @@ -547,6 +580,12 @@ export default { this.loadTable() }) }, + onDocParamIgnore(row, status) { + this.get('/doc/monitor/ignore/status', { docId: row.id, moduleId: row.moduleId, status: status }, () => { + this.tipSuccess(this.$t('operateSuccess')) + this.loadTable() + }) + }, onDocUnLock(row) { this.post('/doc/unlock', { id: row.id }, () => { this.tipSuccess(this.$t('operateSuccess')) @@ -609,6 +648,15 @@ export default { }, onCopyPostmanUrl() { this.copyText(this.postmanUrl) + }, + rowStyle({ row, rowIndex }) { + if (this.docDiffMap[row.id]) { + if (this.docDiffMap[row.id] === 1) { + return { 'color': '#C0C4CC' } + } else if (this.docDiffMap[row.id] === 2) { + return { 'color': '#F56C6C' } + } + } } } } diff --git a/front/src/views/project/ModuleSetting/ModSetting/index.vue b/front/src/views/project/ModuleSetting/ModSetting/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..226f8f2862e853f5b0f4759107613bcb74ce614a --- /dev/null +++ b/front/src/views/project/ModuleSetting/ModSetting/index.vue @@ -0,0 +1,70 @@ + + diff --git a/front/src/views/project/ModuleSetting/index.vue b/front/src/views/project/ModuleSetting/index.vue index 5296fae8e911deac84e13c92322c507407944b66..160eae394a4c8d6f96fa2ebf7ebb72a62ca5ed04 100644 --- a/front/src/views/project/ModuleSetting/index.vue +++ b/front/src/views/project/ModuleSetting/index.vue @@ -31,6 +31,9 @@ + + +
@@ -42,10 +45,11 @@ import PopoverUpdate from '@/components/PopoverUpdate' import SwaggerSetting from '@/components/ModuleSetting/SwaggerSetting' import MeterSphereSetting from './MeterSphereSetting' import EnvSetting from './EnvSetting' +import ModSetting from './ModSetting' export default { name: 'ModuleSetting', - components: { MeterSphereSetting, WeComSetting, DingDingSetting, PopoverUpdate, SwaggerSetting, EnvSetting }, + components: { MeterSphereSetting, WeComSetting, DingDingSetting, PopoverUpdate, SwaggerSetting, EnvSetting, ModSetting }, props: { projectId: { type: String, diff --git a/front/src/views/view/CompareResult/index.vue b/front/src/views/view/CompareResult/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..17b0cc068dfdbfe934333b2191413f987791406e --- /dev/null +++ b/front/src/views/view/CompareResult/index.vue @@ -0,0 +1,224 @@ + + + + diff --git a/front/src/views/view/index.vue b/front/src/views/view/index.vue index 27f716f9d3c10bd2c5c48eee0aebc7adf6bf3104..79bf1eec9a58380ba85c022b1ca8b97f7ba70db3 100644 --- a/front/src/views/view/index.vue +++ b/front/src/views/view/index.vue @@ -2,21 +2,25 @@
- {{ $t('apiInfo') }} + {{ $t('apiInfo') }} - {{ $t('debugApi') }} + {{ $t('debugApi') }} - Mock + Mock + + 比对结果 + + - {{ $t('apiInfo') }} + {{ $t('apiInfo') }} @@ -31,15 +35,18 @@ import DubboView from '@/components/DubboView' import DocDebug from '@/components/DocDebug' import Mock from '@/components/Mock' import DocViewCustom from '@/components/DocViewCustom' +import CompareResult from './CompareResult' export default { - components: { DocView, DocDebug, Mock, DubboView, DocViewCustom }, + components: { DocView, DocDebug, Mock, DubboView, DocViewCustom, CompareResult }, data() { return { active: 'info', item: { type: 0 }, + compareModifyParam: { pathParams: {}, headerParams: {}, requestParams: {}, responseParams: {}, queryParams: {}}, // 文档比对差异字段 + compareNewParam: { pathParams: [], headerParams: [], requestParams: [], responseParams: [], queryParams: [] }, // 文档比对新增字段 infoItem: {}, debugItem: {}, mockItem: {} @@ -61,17 +68,61 @@ export default { if (docId) { this.$nextTick(() => { this.get('/doc/view/detail', { id: docId }, function(resp) { - const data = resp.data - this.setProjectId(data.projectId) - this.initDocInfoView(data) - this.item = data - this.selectTab('info') - this.setTitle(data.name) - if (this.item.type === this.getEnums().DOC_TYPE.DUBBO) { - this.$nextTick(() => { - this.$refs.docViewDubbo.setData(this.item) + this.get('/doc/view/compare/param/result', { id: docId }, function(compareResp) { + const data = resp.data + this.setProjectId(data.projectId) + this.initDocInfoView(data) + this.item = data + this.selectTab('info') + this.setTitle(data.name) + if (this.item.type === this.getEnums().DOC_TYPE.DUBBO) { + this.$nextTick(() => { + this.$refs.docViewDubbo.setData(this.item) + }) + } + const compareModifyData = compareResp.data.filter(item => item.diffType !== -1) + compareModifyData.forEach(item => { + switch (item.style) { + case 0: + this.compareModifyParam.pathParams[item.paramId] = item + break + case 1: + this.compareModifyParam.headerParams[item.paramId] = item + break + case 2: + this.compareModifyParam.requestParams[item.paramId] = item + break + case 3: + this.compareModifyParam.responseParams[item.paramId] = item + break + case 5: + this.compareModifyParam.queryParams[item.paramId] = item + break + default: + } + }) + const compareNewData = compareResp.data.filter(item => item.diffType === -1) + compareNewData.forEach(item => { + switch (item.style) { + case 0: + this.compareNewParam.pathParams.push(item) + break + case 1: + this.compareNewParam.headerParams.push(item) + break + case 2: + this.compareNewParam.requestParams.push(item) + break + case 3: + this.compareNewParam.responseParams.push(item) + break + case 5: + this.compareNewParam.queryParams.push(item) + break + default: + } }) - } + }) }) }) } else { diff --git a/mysql.sql b/mysql.sql index 66908b2f414192580faa49163a4702ecc5f7d057..59fe81d21305aa69e4e3aee2757e1594202f3779 100644 --- a/mysql.sql +++ b/mysql.sql @@ -731,6 +731,76 @@ CREATE TABLE `user_wecom_info` ( KEY `idx_userid` (`user_info_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +DROP TABLE IF EXISTS `doc_info_ext`; +CREATE TABLE `doc_info_ext` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `doc_id` varchar(64) NOT NULL DEFAULT '' COMMENT '文档唯一key', + `module_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '模块id,module.id', + `doc_prop` varchar(64) NOT NULL COMMENT '文档属性', + `doc_value` varchar(128) NOT NULL COMMENT '文档属性值', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_docid` (`doc_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档信息扩展表'; + +DROP TABLE IF EXISTS `doc_info_push`; +CREATE TABLE `doc_info_push` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `doc_key` varchar(64) NOT NULL DEFAULT '' COMMENT '文档唯一key', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '文档名称', + `url` varchar(256) NOT NULL DEFAULT '' COMMENT '访问URL', + `http_method` varchar(12) NOT NULL DEFAULT '' COMMENT 'http方法', + `content_type` varchar(128) NOT NULL DEFAULT '' COMMENT 'contentType', + `module_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '模块id,module.id', + `is_compare` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否比对 1:已经比对,0:未比对', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_moduleid` (`module_id`) USING BTREE, + KEY `idx_dockey` (`doc_key`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档信息推送表'; + +DROP TABLE IF EXISTS `doc_param_push`; +CREATE TABLE `doc_param_push` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL DEFAULT '' COMMENT '字段名称', + `type` varchar(64) NOT NULL DEFAULT 'String' COMMENT '字段类型', + `doc_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'doc_info.id', + `parent_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `style` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0:path, 1:header, 2:body参数,3:返回参数,4:错误码, 5:query参数', + `order_index` int(11) NOT NULL DEFAULT '0' COMMENT '排序索引', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_docid` (`doc_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档参数推送表'; + +DROP TABLE IF EXISTS `doc_info_diff`; +CREATE TABLE `doc_info_diff` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `doc_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'doc_info.id', + `diff_type` tinyint(4) NOT NULL COMMENT '差异类型: 1: 推送缺少文档 2: 文档比对差异', + `module_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '模块id,module.id', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_docid` (`doc_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档差异表'; + +DROP TABLE IF EXISTS `doc_param_diff`; +CREATE TABLE `doc_param_diff` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `param_id` bigint(20) unsigned COMMENT '根据diff_type决定doc_param.id 或 doc_param_push.id', + `doc_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'doc_info.id', + `style` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0:path, 1:header, 2:body参数,3:返回参数,4:错误码, 5:query参数', + `diff_type` tinyint(4) NOT NULL COMMENT '差异类型: 1: 推送参数缺少字段,-1: 推送参数多出字段, 2: 名称一致,类型不一致', + `order_index` int(11) NOT NULL DEFAULT '0' COMMENT '排序索引', + `diff_param_name` varchar(256) NOT NULL DEFAULT 'String' COMMENT '差异字段名称', + `diff_param_type` varchar(64) COMMENT '差异字段类型', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_docid` (`doc_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档参数差异表'; + + INSERT INTO `constant_info` (`id`, `project_id`, `module_id`, `doc_id`, `content`, `gmt_create`, `gmt_modified`) VALUES (1, 5, 0, 0, '

全局状态码

HTTP状态码说明
200OK
400Bad Request
401Unauthorized
403Forbidden
404Not Found
405Method Not Allowed
500Internal Server Error
502Bad Gateway
503Service Unavailable
504Gateway Timeout
', '2022-11-03 20:32:43', '2022-11-03 20:32:43'), (2, 0, 5, 0, '

商品中心错误码

  • 10001:商品不存在
  • 10002:分类不存在
  • 10003:商品已下架
  • 10004:商品已售罄
', '2022-11-03 20:33:53', '2022-11-03 20:33:53'); diff --git a/server/boot/src/main/resources/META-INF/torna.properties b/server/boot/src/main/resources/META-INF/torna.properties index b3f2cef1c0a77715f1800ac1c30d7d61cab3a39e..f0bf68ff46f9feb9a92099373815eed24367fb26 100644 --- a/server/boot/src/main/resources/META-INF/torna.properties +++ b/server/boot/src/main/resources/META-INF/torna.properties @@ -73,6 +73,8 @@ torna.view-config.response-hidden-columns=required,maxLength # \u521D\u59CB\u6392\u5E8F\u503C torna.view-config.init-order=10000 torna.view-config.compose-show-debug=true +# \u662F\u5426\u4FDD\u5B58\u6240\u6709\u5FEB\u7167 +torna.snapshot.save.all=false # \u65E5\u5FD7\u7B49\u7EA7 diff --git a/server/server-api/src/main/java/cn/torna/api/open/DocApi.java b/server/server-api/src/main/java/cn/torna/api/open/DocApi.java index 1ae8aa289530f127d24539256a32e1f813acf6b1..bad845dca77ea4950afc3a4648bcaaddec0445d2 100644 --- a/server/server-api/src/main/java/cn/torna/api/open/DocApi.java +++ b/server/server-api/src/main/java/cn/torna/api/open/DocApi.java @@ -37,14 +37,7 @@ import cn.torna.dao.entity.DocParam; import cn.torna.dao.entity.Module; import cn.torna.dao.entity.Project; import cn.torna.manager.tx.TornaTransactionManager; -import cn.torna.service.DocDiffRecordService; -import cn.torna.service.DocInfoService; -import cn.torna.service.DocViewService; -import cn.torna.service.MockConfigService; -import cn.torna.service.ModuleConfigService; -import cn.torna.service.ModuleEnvironmentService; -import cn.torna.service.ProjectService; -import cn.torna.service.UserMessageService; +import cn.torna.service.*; import cn.torna.service.dto.DocFolderCreateDTO; import cn.torna.service.dto.DocInfoDTO; import cn.torna.service.dto.DocMeta; @@ -115,6 +108,9 @@ public class DocApi { @Autowired private DocInfoService docInfoService; + @Autowired + private DocInfoPushService docInfoPushService; + @Autowired private ModuleConfigService moduleConfigService; @@ -156,19 +152,28 @@ public class DocApi { if (Boolean.parseBoolean(isPrint) || isPrintContent(module.getId())) { log.info("【PUSH】推送内容:{}", JSON.toJSONString(param)); } - // 允许有相同的目录 - String allowSameFolder = EnvironmentKeys.TORNA_PUSH_ALLOW_SAME_FOLDER.getValue(); - if (Boolean.parseBoolean(allowSameFolder)) { - this.mergeSameFolder(param); + // 获取该模块是否开启监控 + boolean isMonitor = moduleConfigService.getCommonConfigValue(module.getId(), "isMonitor", "0") + .equals("0") ? false : true; + if (isMonitor) { + log.info("===========开始更新推送文档信息==========="); + ThreadPoolUtil.execute(() -> doMonitorPush(param, context)); } else { - this.checkSameFolder(param); - } - String author = param.getAuthor(); - if (StringUtils.hasText(author)) { - ApiUser user = (ApiUser) context.getApiUser(); - user.setNickname(author); + log.info("===========开始更新平台文档信息==========="); + // 允许有相同的目录 + String allowSameFolder = EnvironmentKeys.TORNA_PUSH_ALLOW_SAME_FOLDER.getValue(); + if (Boolean.parseBoolean(allowSameFolder)) { + this.mergeSameFolder(param); + } else { + this.checkSameFolder(param); + } + String author = param.getAuthor(); + if (StringUtils.hasText(author)) { + ApiUser user = (ApiUser) context.getApiUser(); + user.setNickname(author); + } + ThreadPoolUtil.execute(() -> doPush(param, context)); } - ThreadPoolUtil.execute(() -> doPush(param, context)); } private void checkSameFolder(DocPushParam param) { @@ -218,6 +223,34 @@ public class DocApi { param.setApis(new ArrayList<>(folderItems.keySet())); } + /** + * 监控比对推送 + * + * @param param + * @param context + */ + private void doMonitorPush(DocPushParam param, RequestContext context) { + Module module = context.getModule(); + ThreadLocal docPushItemParamThreadLocal = new ThreadLocal<>(); + synchronized (lock) { + tornaTransactionManager.execute(() -> { + // 处理文档 + for (DocPushItemParam detailPushParam : param.getApis()) { + docPushItemParamThreadLocal.set(detailPushParam); + this.saveDocItemPush(detailPushParam, module.getId()); + } + log.info("完成模块【{}】推送文档更新,更新比对状态缓存", module.getName()); + ScheduleTask.MODULE_COMPARE_STATUS_CACHE.put(module.getId(), true); + return true; + }, e -> { + DocPushItemParam docPushItemParam = docPushItemParamThreadLocal.get(); + String paramInfo = JSON.toJSONString(docPushItemParam); + log.error("【PUSH_DOC】保存推送文档失败,模块名称:{},推送人:{},ip:{},token:{}, 文档信息:{}", module.getName(), + param.getAuthor(), context.getIp(), context.getToken(), paramInfo, e); + }); + } + } + private void doPush(DocPushParam param, RequestContext context) { Module module = context.getModule(); long moduleId = module.getId(); @@ -353,6 +386,23 @@ public class DocApi { } } + public void saveDocItemPush(DocPushItemParam param, long moduleId) { + if (Booleans.isTrue(param.getIsFolder())) { + List items = param.getItems(); + if (items != null) { + for (DocPushItemParam item : items) { + this.saveDocItemPush(item, moduleId); + } + } + } else { + DocInfoDTO docInfoDTO = buildDocInfoDTO(param); + docInfoDTO.setType(DocTypeEnum.HTTP.getType()); + formatUrl(docInfoDTO); + docInfoDTO.setModuleId(moduleId); + docInfoPushService.saveDocInfo(docInfoDTO); + } + } + public void pushDocItem(DocPushItemParam param, RequestContext context, Long parentId, PushContext pushContext) { User user = context.getApiUser(); long moduleId = context.getModuleId(); diff --git a/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoDiff.java b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoDiff.java new file mode 100644 index 0000000000000000000000000000000000000000..cf64a4d78d319714d4c83783b6161d724ef7f90d --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoDiff.java @@ -0,0 +1,37 @@ +package cn.torna.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 文档差异 + * + * @Author jyh + * @Date 2025/10/30 16:34 + * @Version 1.0 + **/ +@Table(name = "doc_info_diff", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocInfoDiff { + + /** 数据库字段:id */ + private Long id; + + /** doc_info.id, 数据库字段:doc_id */ + private Long docId; + + /** + * 模块id,module.id + */ + private long moduleId; + + /** 差异类型: 1: 推送缺少文档,2:文档比对差异 */ + private Byte diffType; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoExt.java b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoExt.java new file mode 100644 index 0000000000000000000000000000000000000000..f2c75411fbba11ca9d437f380d8d0991580afc3d --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoExt.java @@ -0,0 +1,41 @@ +package cn.torna.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 文档信息扩展表 + * + * @Author jyh + * @Date 2025/11/3 09:02 + * @Version 1.0 + **/ +@Table(name = "doc_info_ext", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocInfoExt { + + /** 数据库字段:id */ + private Long id; + + /** doc_info.id, 数据库字段:doc_id */ + private Long docId; + + /** 模块id,module.id */ + private long moduleId; + + /** 文档属性 */ + private String docProp; + + /** 文档属性值 */ + private String docValue; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; + + /** 数据库字段:gmt_modified */ + private LocalDateTime gmtModified; +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoPush.java b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoPush.java new file mode 100755 index 0000000000000000000000000000000000000000..dc4fd751d7814e3acc3d90bad8a50a04a8c9a0a3 --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/entity/DocInfoPush.java @@ -0,0 +1,45 @@ +package cn.torna.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:doc_info_push + * 备注:文档推送信息 + * + * @author jyh + */ +@Table(name = "doc_info_push", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocInfoPush { + + /** 数据库字段:id */ + private Long id; + + /** 唯一id,接口规则:md5(module_id:parent_id:url:http_method)。分类规则:md5(module_id:parent_id:name), 数据库字段:doc_key */ + private String docKey; + + /** 访问URL, 数据库字段:url */ + private String url; + + /** http方法, 数据库字段:http_method */ + private String httpMethod; + + /** contentType, 数据库字段:content_type */ + private String contentType; + + /** 模块id,module.id, 数据库字段:module_id */ + private Long moduleId; + + /** 是否比对 1:已经比对,0:未比对 */ + private Byte isCompare; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; + +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamDiff.java b/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamDiff.java new file mode 100644 index 0000000000000000000000000000000000000000..efac157abbd8cc9649a708b27ec10537fe80003b --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamDiff.java @@ -0,0 +1,47 @@ +package cn.torna.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 文档参数差异 + * + * @Author jyh + * @Date 2025/10/27 14:07 + * @Version 1.0 + **/ +@Table(name = "doc_param_diff", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocParamDiff { + + /** 数据库字段:id */ + private Long id; + + /** 根据diff_type决定doc_param.id 或 doc_param_tmp.id */ + private Long paramId; + + /** doc_info.id, 数据库字段:doc_id */ + private Long docId; + + /** 0:path, 1:header, 2:请求参数,3:返回参数,4:错误码, 数据库字段:style */ + private Byte style; + + /** 差异类型: 1: 临时参数缺少字段,-1: 临时参数多出字段, 2: 名称一致,类型不一致 */ + private Byte diffType; + + /** 排序索引, 数据库字段:order_index */ + private Integer orderIndex; + + /** 差异字段名称 */ + private String diffParamName; + + /** 差异字段类型 */ + private String diffParamType; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamPush.java b/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamPush.java new file mode 100755 index 0000000000000000000000000000000000000000..ccca732aae88365061bee9dd79b7d017fa2f6947 --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/entity/DocParamPush.java @@ -0,0 +1,45 @@ +package cn.torna.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:doc_param_push + * 备注:文档参数推送表 + * + * @author jyh + */ +@Table(name = "doc_param_push", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocParamPush { + + /** 数据库字段:id */ + private Long id; + + /** 字段名称, 数据库字段:name */ + private String name; + + /** 字段类型, 数据库字段:type */ + private String type; + + /** doc_info.id, 数据库字段:doc_id */ + private Long docId; + + /** 数据库字段:parent_id */ + private Long parentId; + + /** 0:path, 1:header, 2:请求参数,3:返回参数,4:错误码, 数据库字段:style */ + private Byte style; + + /** 排序索引, 数据库字段:order_index */ + private Integer orderIndex; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; + +} \ No newline at end of file diff --git a/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoDiffMapper.java b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoDiffMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..96ee87d8f3e33a5df58134fb99e544509897d8a9 --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoDiffMapper.java @@ -0,0 +1,12 @@ +package cn.torna.dao.mapper; + +import cn.torna.dao.entity.DocInfoDiff; +import com.gitee.fastmybatis.core.mapper.BaseMapper; + +/** + * @Author jyh + * @Date 2025/10/30 16:40 + * @Version 1.0 + **/ +public interface DocInfoDiffMapper extends BaseMapper { +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoExtMapper.java b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoExtMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..2c218b901c8ca00140ac6a0144ddf55ce5a8f49d --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoExtMapper.java @@ -0,0 +1,13 @@ +package cn.torna.dao.mapper; + +import cn.torna.dao.entity.DocInfoExt; +import com.gitee.fastmybatis.core.mapper.BaseMapper; + +/** + * + * @Author jyh + * @Date 2025/11/3 09:09 + * @Version 1.0 + **/ +public interface DocInfoExtMapper extends BaseMapper { +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoPushMapper.java b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoPushMapper.java new file mode 100755 index 0000000000000000000000000000000000000000..e4de8e18855678f8662de56754fd970dd2f77b6b --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocInfoPushMapper.java @@ -0,0 +1,11 @@ +package cn.torna.dao.mapper; + +import cn.torna.dao.entity.DocInfoPush; +import com.gitee.fastmybatis.core.mapper.BaseMapper; + +/** + * @author jyh + */ +public interface DocInfoPushMapper extends BaseMapper { + +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamDiffMapper.java b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamDiffMapper.java new file mode 100755 index 0000000000000000000000000000000000000000..3b6b520166e2ee6039f283f80066271dc3c4e56b --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamDiffMapper.java @@ -0,0 +1,11 @@ +package cn.torna.dao.mapper; + +import cn.torna.dao.entity.DocParamDiff; +import com.gitee.fastmybatis.core.mapper.BaseMapper; + +/** + * @author jyh + */ +public interface DocParamDiffMapper extends BaseMapper { + +} diff --git a/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamPushMapper.java b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamPushMapper.java new file mode 100755 index 0000000000000000000000000000000000000000..4179ad885ee70d59d9cf8ba8b24929a882b37e65 --- /dev/null +++ b/server/server-dao/src/main/java/cn/torna/dao/mapper/DocParamPushMapper.java @@ -0,0 +1,11 @@ +package cn.torna.dao.mapper; + +import cn.torna.dao.entity.DocParamPush; +import com.gitee.fastmybatis.core.mapper.BaseMapper; + +/** + * @author jyh + */ +public interface DocParamPushMapper extends BaseMapper { + +} diff --git a/server/server-service/src/main/java/cn/torna/service/DocDiffRecordService.java b/server/server-service/src/main/java/cn/torna/service/DocDiffRecordService.java index 25af52b408320b4f8715672b855f23b3703ce22c..280123d387b60a031b6c9b991b2824ade12a9021 100644 --- a/server/server-service/src/main/java/cn/torna/service/DocDiffRecordService.java +++ b/server/server-service/src/main/java/cn/torna/service/DocDiffRecordService.java @@ -62,17 +62,18 @@ public class DocDiffRecordService extends BaseLambdaService listDocDiff(Long docId) { - DocInfo docInfo = docInfoService.getById(docId); - String version = docInfo.getVersion(); - List diffRecordList; - // 如果有版本号,只能看截止到当前版本的记录 - if (StringUtils.hasText(version) && !"-".equals(version)) { - diffRecordList = listByField(DocDiffRecord::getDocId, docId); - } else { - // 可以查看所有变更记录 - String docKey = docInfoService.getDocKey(docId); - diffRecordList = listByField(DocDiffRecord::getDocKey, docKey); - } +// DocInfo docInfo = docInfoService.getById(docId); +// String version = docInfo.getVersion(); +// List diffRecordList; +// // 如果有版本号,只能看截止到当前版本的记录 +// if (StringUtils.hasText(version) && !"-".equals(version)) { +// diffRecordList = listByField(DocDiffRecord::getDocId, docId); +// } else { +// // 可以查看所有变更记录 +// String docKey = docInfoService.getDocKey(docId); +// diffRecordList = listByField(DocDiffRecord::getDocKey, docKey); +// } + List diffRecordList = listByField(DocDiffRecord::getDocId, docId); if (CollectionUtils.isEmpty(diffRecordList)) { return Collections.emptyList(); } diff --git a/server/server-service/src/main/java/cn/torna/service/DocInfoPushService.java b/server/server-service/src/main/java/cn/torna/service/DocInfoPushService.java new file mode 100644 index 0000000000000000000000000000000000000000..e1262fb1e2a01ff6e1e8fd9f3bdf9f84478b02b2 --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/DocInfoPushService.java @@ -0,0 +1,67 @@ +package cn.torna.service; + +import cn.torna.common.enums.ParamStyleEnum; +import cn.torna.common.util.CopyUtil; +import cn.torna.dao.entity.DocInfoPush; +import cn.torna.dao.mapper.DocInfoPushMapper; +import cn.torna.service.dto.DocInfoDTO; +import com.gitee.fastmybatis.core.support.BaseLambdaService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @Author jyh + * @Date 2025/10/22 14:15 + * @Version 1.0 + **/ +@Service +@Slf4j +public class DocInfoPushService extends BaseLambdaService { + + @Autowired + private DocParamPushService docParamPushService; + + /** + * 保存推送文档信息 + */ + @Transactional(rollbackFor = Exception.class) + public synchronized void saveDocInfo(DocInfoDTO docInfoDTO) { + // 根据doc_key删除原先信息 + String docKey = docInfoDTO.buildDocKey(); + docInfoDTO.setDocKey(docKey); + DocInfoPush docInfoPush = this.getByField(DocInfoPush::getDocKey, docKey); + if (docInfoPush != null) { + this.deleteByColumn(DocInfoPush::getDocKey, docKey); + docParamPushService.deleteParamByDocId(docInfoPush.getId()); + } + // 新增基本信息 + long docId = insertDocInfoPush(docInfoDTO); + // 新增参数 + this.saveParams(docId, docInfoDTO); + } + + private void saveParams(long docId, DocInfoDTO docInfoDTO) { + docParamPushService.saveParams(docId, docInfoDTO.getPathParams(), ParamStyleEnum.PATH); + docParamPushService.saveParams(docId, docInfoDTO.getHeaderParams(), ParamStyleEnum.HEADER); + docParamPushService.saveParams(docId, docInfoDTO.getQueryParams(), ParamStyleEnum.QUERY); + docParamPushService.saveParams(docId, docInfoDTO.getRequestParams(), ParamStyleEnum.REQUEST); + docParamPushService.saveParams(docId, docInfoDTO.getResponseParams(), ParamStyleEnum.RESPONSE); + docParamPushService.saveParams(docId, docInfoDTO.getErrorCodeParams(), ParamStyleEnum.ERROR_CODE); + } + + private long insertDocInfoPush(DocInfoDTO docInfoDTO) { + DocInfoPush docInfoPush = CopyUtil.copyBean(docInfoDTO, DocInfoPush::new); + this.getMapper().saveIgnoreNull(docInfoPush); + // 修复使用非MYSQL数据库插入数据id不返回问题 + if (docInfoPush.getId() == null) { + DocInfoPush one = getByField(DocInfoPush::getDocKey, docInfoPush.getDocKey()); + if (one != null) { + docInfoPush.setId(one.getId()); + } + } + return docInfoPush.getId(); + } + +} diff --git a/server/server-service/src/main/java/cn/torna/service/DocInfoService.java b/server/server-service/src/main/java/cn/torna/service/DocInfoService.java index 0a8040db947654323d8fc58b689e67c1d1fae572..b2cade9b8c5532cd303af634535bbcb661dbafc3 100755 --- a/server/server-service/src/main/java/cn/torna/service/DocInfoService.java +++ b/server/server-service/src/main/java/cn/torna/service/DocInfoService.java @@ -17,29 +17,27 @@ import cn.torna.common.util.IdGen; import cn.torna.common.util.Markdown2HtmlUtil; import cn.torna.common.util.TreeUtil; import cn.torna.dao.entity.DocInfo; +import cn.torna.dao.entity.DocInfoDiff; +import cn.torna.dao.entity.DocInfoExt; +import cn.torna.dao.entity.DocInfoPush; import cn.torna.dao.entity.DocParam; +import cn.torna.dao.entity.DocParamDiff; +import cn.torna.dao.entity.DocParamPush; import cn.torna.dao.entity.EnumInfo; import cn.torna.dao.entity.Module; import cn.torna.dao.entity.ModuleEnvironment; import cn.torna.dao.entity.ModuleEnvironmentParam; import cn.torna.dao.entity.UserDingtalkInfo; import cn.torna.dao.entity.UserWeComInfo; +import cn.torna.dao.mapper.DocInfoDiffMapper; +import cn.torna.dao.mapper.DocInfoExtMapper; import cn.torna.dao.mapper.DocInfoMapper; +import cn.torna.dao.mapper.DocInfoPushMapper; +import cn.torna.dao.mapper.DocParamDiffMapper; import cn.torna.dao.mapper.UserDingtalkInfoMapper; import cn.torna.dao.mapper.UserWeComInfoMapper; import cn.torna.manager.doc.DataType; -import cn.torna.service.dto.DocFolderCreateDTO; -import cn.torna.service.dto.DocInfoDTO; -import cn.torna.service.dto.DocItemCreateDTO; -import cn.torna.service.dto.DocListFormDTO; -import cn.torna.service.dto.DocMeta; -import cn.torna.service.dto.DocParamDTO; -import cn.torna.service.dto.DocRefDTO; -import cn.torna.service.dto.DubboInfoDTO; -import cn.torna.service.dto.EnumInfoDTO; -import cn.torna.service.dto.EnumItemDTO; -import cn.torna.service.dto.ModuleEnvironmentDTO; -import cn.torna.service.dto.UpdateDocFolderDTO; +import cn.torna.service.dto.*; import cn.torna.service.event.DocAddEvent; import cn.torna.service.event.DocUpdateEvent; import com.gitee.fastmybatis.core.PageInfo; @@ -68,6 +66,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -82,6 +81,9 @@ public class DocInfoService extends BaseLambdaService { @Autowired private DocParamService docParamService; + @Autowired + private DocParamPushService docParamPushService; + @Autowired private ModuleConfigService moduleConfigService; @@ -124,6 +126,17 @@ public class DocInfoService extends BaseLambdaService { @Autowired private MockConfigService mockConfigService; + @Autowired + private DocInfoPushMapper docInfoPushMapper; + + @Autowired + private DocParamDiffMapper docParamDiffMapper; + + @Autowired + private DocInfoDiffMapper docInfoDiffMapper; + + @Autowired + private DocInfoExtMapper docInfoExtMapper; /** * 查询模块下的所有文档 @@ -267,6 +280,34 @@ public class DocInfoService extends BaseLambdaService { return getDocDetail(docInfo); } + /** + * 返回参数比对结果 + * + * @param docId + * @return + */ + public List getParamCompareResult(long docId) { + Query query = Query.create().eq("doc_id", docId).orderByAsc("order_index"); + List docParamDiffList = docParamDiffMapper.list(query); + return docParamDiffList.stream().map(item -> CopyUtil.copyBean(item, DocParamDiffDTO::new)) + .collect(Collectors.toList()); + } + + /** + * 获取文档比对结果 + * + * @param moduleId + * @return + */ + public List getDocCompareResult(Long moduleId) { + List docInfoDiffList = docInfoDiffMapper.listByField(DocInfoDiff::getModuleId, moduleId); + return docInfoDiffList.stream().map(docInfoDiff -> { + DocInfoDiffDTO docInfoDiffDTO = new DocInfoDiffDTO(); + CopyUtil.copyProperties(docInfoDiff, docInfoDiffDTO); + return docInfoDiffDTO; + }).collect(Collectors.toList()); + } + /** * 返回文档详情 * @@ -992,4 +1033,237 @@ public class DocInfoService extends BaseLambdaService { return listByField(DocInfo::getParentId, parentId); } + /** + * 按模块比对文档 + * + * @param moduleId + */ + public void compareMonitorDoc(long moduleId) { + // 获取平台文档 + List docInfoList = this.listDocDetail(moduleId); + Map docInfoMap = docInfoList.stream().filter(item -> item.getIsFolder() == 0) + .collect(Collectors.toMap(item -> item.getDocKey(), + item -> item, (existing, replacement) -> existing)); + // 获取推送文档 + List docInfoPushList = docInfoPushMapper.listByField(DocInfoPush::getModuleId, moduleId); + Map docInfoPushMap = docInfoPushList.stream().collect(Collectors.toMap(item -> item.getDocKey(), + item -> item, (existing, replacement) -> existing)); + + // 获取文档参数忽略比对数据 + Query docInfoExtQuery = Query.create().eq("module_id", moduleId).eq("doc_prop", "isIgnoreCompareParam"); + List ignoreCompareParamDocIds = docInfoExtMapper.listUniqueValue(docInfoExtQuery, DocInfoExt::getDocId); + // 遍历比对文档 + docInfoMap.forEach((docKey, docInfoDTO) -> { + if (docInfoPushMap.get(docKey) != null) { + if (!ignoreCompareParamDocIds.contains(docInfoDTO.getId())) { // 未忽略参数比对 + DocInfoPush docInfoPush = docInfoPushMap.get(docKey); + log.info("开始比对文档【id:{},name:{},url:{},httpMethod:{}】", docInfoDTO.getId(), docInfoDTO.getName(), + docInfoDTO.getUrl(), docInfoDTO.getHttpMethod()); + // 获取平台文档参数 + List params = docParamService.listByField(DocParam::getDocId, docInfoDTO.getId()); + params.sort(Comparator.comparing(DocParam::getOrderIndex)); + Map> paramsMap = params.stream() + .collect(Collectors.groupingBy(DocParam::getStyle)); + // 获取推送文档参数 + List pushParams = docParamPushService.listByField(DocParamPush::getDocId, docInfoPush.getId()); + pushParams.sort(Comparator.comparing(DocParamPush::getOrderIndex)); + Map> pushParamsMap = pushParams.stream() + .collect(Collectors.groupingBy(DocParamPush::getStyle)); + // 按类型比对参数 + paramsMap.forEach((paramType, docParams) -> { + log.info("开始比对参数类型【{}】", paramType); + List pushDocParams = pushParamsMap.get(paramType) == null ? new ArrayList<>() : pushParamsMap.get(paramType); + List docParamDiffList = compareParam(docParams, pushDocParams); + if (docParamDiffList.size() > 0) { + log.info("【id:{},name:{},url:{},httpMethod:{}】参数类型【{}】有差异,更新比对结果", docInfoDTO.getId(), docInfoDTO.getName(), + docInfoDTO.getUrl(), docInfoDTO.getHttpMethod(), paramType); + docInfoDiffMapper.deleteByColumn(DocInfoDiff::getDocId, docInfoDTO.getId()); + saveDocInfoCompareResult(docInfoDTO, moduleId, (byte) 2); + } else { + log.debug("【id:{},name:{},url:{},httpMethod:{}】参数类型【{}】无差异,清除比对结果", docInfoDTO.getId(), docInfoDTO.getName(), + docInfoDTO.getUrl(), docInfoDTO.getHttpMethod(), paramType); + docInfoDiffMapper.deleteByColumn(DocInfoDiff::getDocId, docInfoDTO.getId()); + } + updateDocParamDiffByType(docParamDiffList, docInfoDTO.getId(), paramType); + }); + } + } else { + log.info("平台文档【id:{},name:{},url:{},httpMethod:{}】无推送内容!", docInfoDTO.getId(), docInfoDTO.getName(), + docInfoDTO.getUrl(), docInfoDTO.getHttpMethod()); + docInfoDiffMapper.deleteByColumn(DocInfoDiff::getDocId, docInfoDTO.getId()); + saveDocInfoCompareResult(docInfoDTO, moduleId, (byte) 1); + } + }); + log.info("完成模块【{}】比对,更新比对状态缓存", moduleId); + ScheduleTask.MODULE_COMPARE_STATUS_CACHE.put(moduleId, false); + } + + /** + * 是否忽略参数比较(应用于动态参数) + * 逻辑:status == false 直接删除条目,否则新增条目 docValue = 1 + * + * @param docId + * @param moduleId + * @param status + */ + @Transactional(rollbackFor = Exception.class) + public void ignoreCompareParam(Long docId, Long moduleId, boolean status) { + if (status) { + DocInfoExt docInfoExt = new DocInfoExt(); + docInfoExt.setDocId(docId); + docInfoExt.setModuleId(moduleId); + docInfoExt.setDocProp("isIgnoreCompareParam"); + docInfoExt.setDocValue("1"); + docInfoExtMapper.save(docInfoExt); + + log.info("平台文档【id:{}】开启禁止参数比对,清除之前比对结果!", docId); + docParamDiffMapper.deleteByColumn(DocParamDiff::getDocId, docId); + Query query = Query.create().eq("doc_id", docId).eq("diff_type", (byte) 2); + docInfoDiffMapper.deleteByQuery(query); + } else { + Query query = Query.create() + .eq("doc_prop", "isIgnoreCompareParam") + .eq("doc_id", docId) + .eq("module_id", moduleId); + docInfoExtMapper.deleteByQuery(query); + + } + } + + /** + * 列出该文档属性和模块ID下所有扩展属性 + * + * @param docProp + * @param moduleId + * @return 扩展属性列表 + */ + public List listDocExtByPropAndModuleId(String docProp, Long moduleId) { + Query query = Query.create().eq("doc_prop", docProp).eq("module_id", moduleId); + List docInfoExtList = docInfoExtMapper.list(query); + return docInfoExtList.stream().map(item -> { + DocInfoExtDTO docInfoExtDTO = new DocInfoExtDTO(); + CopyUtil.copyProperties(item, docInfoExtDTO); + return docInfoExtDTO; + }).collect(Collectors.toList()); + } + + private void saveDocInfoCompareResult(DocInfoDTO docInfoDTO, long moduleId, byte diffType) { + DocInfoDiff docInfoDiff = new DocInfoDiff(); + docInfoDiff.setDocId(docInfoDTO.getId()); + docInfoDiff.setDiffType(diffType); + docInfoDiff.setModuleId(moduleId); + docInfoDiffMapper.save(docInfoDiff); + } + + /** + * 保存比对结果列表 + * + * @param docParamDiffList + * @param docId + * @param paramType + */ + private void updateDocParamDiffByType(List docParamDiffList, long docId, Byte paramType) { + Query query = Query.create().eq("doc_id", docId).eq("style", paramType); + docParamDiffMapper.deleteByQuery(query); + docParamDiffList.forEach(docParamDiff -> { + docParamDiff.setDocId(docId); + docParamDiff.setStyle(paramType); + docParamDiffMapper.save(docParamDiff); + }); + } + + /** + * 比对平台参数与推送文档参数差异 + * + * @param docParams + * @param pushDocParams + * @return 比对结果列表 + */ + private List compareParam(List docParams, List pushDocParams) { + // DocParam -->> DocParamTmpDTO + List docParamDTOS = new ArrayList<>(); + for (DocParam item : docParams) { + DocParamPushDTO docParamPushDTO = new DocParamPushDTO(); + CopyUtil.copyProperties(item, docParamPushDTO); + docParamDTOS.add(docParamPushDTO); + } + // 组装全名称路径的参数映射 + Map paramNamePathMap = new HashMap<>(); + this.convertTree(docParamDTOS, 0L, null, paramNamePathMap); + Set paramNamePathSet = paramNamePathMap.keySet(); + // DocParamTmp -->> DocParamTmpDTO + List docParamPushDTOS = new ArrayList<>(); + for (DocParamPush item : pushDocParams) { + DocParamPushDTO docParamPushDTO = new DocParamPushDTO(); + CopyUtil.copyProperties(item, docParamPushDTO); + docParamPushDTOS.add(docParamPushDTO); + } + Map tmpParamNamePathMap = new HashMap<>(); + this.convertTree(docParamPushDTOS, 0L, null, tmpParamNamePathMap); + Set tmpParamNameSet = tmpParamNamePathMap.keySet(); + // 以平台文档维度比对参数 + Map nameDiffMap = new HashMap<>(); // key: 参数全路径名(包含父路径,'-'号分隔), value: DocParamDiff + paramNamePathSet.forEach(paramName -> { + if (tmpParamNameSet.contains(paramName)) { // 包含字段 + if (!paramNamePathMap.get(paramName).getType().equals(tmpParamNamePathMap.get(paramName).getType())) { // 类型不一致 + DocParamDiff docParamDiff = new DocParamDiff(); + docParamDiff.setParamId(paramNamePathMap.get(paramName).getId()); + docParamDiff.setDiffType((byte) 2); + docParamDiff.setDiffParamType(tmpParamNamePathMap.get(paramName).getType()); + docParamDiff.setOrderIndex(paramNamePathMap.get(paramName).getOrderIndex()); + nameDiffMap.put(paramName, docParamDiff); + } + } else { // 临时参数缺少字段 + DocParamDiff docParamDiff = new DocParamDiff(); + docParamDiff.setParamId(paramNamePathMap.get(paramName).getId()); + docParamDiff.setDiffType((byte) 1); + docParamDiff.setOrderIndex(paramNamePathMap.get(paramName).getOrderIndex()); + nameDiffMap.put(paramName, docParamDiff); + } + }); + // 以推送文档维度比对参数 + tmpParamNameSet.forEach(tmpParamName -> { + if (!paramNamePathSet.contains(tmpParamName)) { // 临时参数多出字段 + DocParamDiff docParamDiff = new DocParamDiff(); + docParamDiff.setDiffType((byte) -1); + docParamDiff.setDiffParamType(tmpParamNamePathMap.get(tmpParamName).getType()); + docParamDiff.setOrderIndex(tmpParamNamePathMap.get(tmpParamName).getOrderIndex()); + nameDiffMap.put(tmpParamName, docParamDiff); + } + }); + // 组装比对结果 + List docParamDiffList = new ArrayList<>(); + nameDiffMap.forEach((paramName, docParamDiff) -> { + docParamDiff.setDiffParamName(paramName); + docParamDiffList.add(docParamDiff); + }); + return docParamDiffList; + } + + /** + * list转换成tree + * + * @param list + * @param parentId 当前父节点id + * @param parentName 拼接父节点名称 + * @param paramNamePathMap 记录全路径名称->param映射 + * @return + */ + private List convertTree(List list, long parentId, String parentName, Map paramNamePathMap) { + if (list == null) { + return Collections.emptyList(); + } + List temp = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + DocParamPushDTO item = list.get(i); + if (Objects.equals(item.getParentId(), parentId)) { + String fullPath = (parentId == 0L ? item.getName() : parentName + "-" + item.getName()); + List children = convertTree(list, item.getId(), fullPath, paramNamePathMap); + paramNamePathMap.put(fullPath, item); + item.setChildren(children); + temp.add(item); + } + } + return temp; + } } diff --git a/server/server-service/src/main/java/cn/torna/service/DocParamPushService.java b/server/server-service/src/main/java/cn/torna/service/DocParamPushService.java new file mode 100644 index 0000000000000000000000000000000000000000..4640c35a74079fb95a722be7fe52ade81a2de919 --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/DocParamPushService.java @@ -0,0 +1,49 @@ +package cn.torna.service; + +import cn.torna.common.enums.ParamStyleEnum; +import cn.torna.dao.entity.DocParamPush; +import cn.torna.dao.mapper.DocParamPushMapper; +import cn.torna.service.dto.DocParamDTO; +import com.gitee.fastmybatis.core.support.BaseLambdaService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @Author jyh + * @Date 2025/10/22 16:59 + * @Version 1.0 + **/ +@Service +public class DocParamPushService extends BaseLambdaService { + + public void saveParams(long docId, List docParamDTOS, ParamStyleEnum paramStyleEnum) { + for (DocParamDTO docParamDTO : docParamDTOS) { + this.doSave(docParamDTO, 0L, docId, paramStyleEnum); + } + } + + public void deleteParamByDocId(long docId) { + this.deleteByColumn(DocParamPush::getDocId, docId); + } + + private void doSave(DocParamDTO docParamDTO, long parentId, long docId, ParamStyleEnum paramStyleEnum) { + DocParamPush docParamPush = new DocParamPush(); + docParamDTO.setParentId(parentId); + docParamPush.setName(docParamDTO.getName()); + docParamPush.setType(docParamDTO.getType()); + docParamPush.setDocId(docId); + docParamPush.setParentId(parentId); + docParamPush.setStyle(paramStyleEnum.getStyle()); + docParamPush.setOrderIndex(docParamDTO.getOrderIndex()); + this.save(docParamPush); + + List children = docParamDTO.getChildren(); + if (children != null) { + Long pid = docParamPush.getId(); + for (DocParamDTO child : children) { + this.doSave(child, pid, docId, paramStyleEnum); + } + } + } +} diff --git a/server/server-service/src/main/java/cn/torna/service/DocSnapshotService.java b/server/server-service/src/main/java/cn/torna/service/DocSnapshotService.java index d2d93ef059a918ca21ef66013b6b07443f4b23b6..c861f54b78211ab2aa62558f63de6279cbaf135a 100644 --- a/server/server-service/src/main/java/cn/torna/service/DocSnapshotService.java +++ b/server/server-service/src/main/java/cn/torna/service/DocSnapshotService.java @@ -16,6 +16,7 @@ import com.gitee.fastmybatis.core.support.BaseLambdaService; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -32,6 +33,9 @@ import java.util.stream.Collectors; @Slf4j public class DocSnapshotService extends BaseLambdaService { + @Value("${torna.snapshot.save.all:false}") + private boolean saveAllSnapshot; + @Autowired private DocInfoService docInfoService; @@ -73,10 +77,12 @@ public class DocSnapshotService extends BaseLambdaService MODULE_COMPARE_STATUS_CACHE = new ConcurrentHashMap<>(); + @Autowired private DocDiffRecordService docDiffRecordService; + @Autowired + private DocInfoService docInfoService; + + @Autowired + private ModuleConfigMapper moduleConfigMapper; @Scheduled(initialDelay = 1000, fixedDelay = 15 * 1000) public void saveDocDiff() { @@ -31,5 +51,21 @@ public class ScheduleTask { } } - + /** + * 比对文档差异 + */ + @Scheduled(initialDelay = 300 * 1000, fixedDelay = 300 * 1000) + public void compareDocDiff() { + Query query = Query.create() + .eq("type", ModuleConfigTypeEnum.COMMON) + .eq("config_key", "isMonitor") + .eq("config_value", "1"); + List compareModuleIdList = moduleConfigMapper.listUniqueValue(query, ModuleConfig::getModuleId); + compareModuleIdList.forEach(moduleId -> { + if (MODULE_COMPARE_STATUS_CACHE.getOrDefault(moduleId, false)) { + log.info("==========开始比对模块【{}】推送文档=====", moduleId); + docInfoService.compareMonitorDoc(moduleId); + } + }); + } } diff --git a/server/server-service/src/main/java/cn/torna/service/dto/DocInfoDiffDTO.java b/server/server-service/src/main/java/cn/torna/service/dto/DocInfoDiffDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..9673f714b2cf40a98916a5b385d135ecad201ce4 --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/dto/DocInfoDiffDTO.java @@ -0,0 +1,37 @@ +package cn.torna.service.dto; + +import cn.torna.common.support.IdCodec; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * + * @Author jyh + * @Date 2025/10/30 16:36 + * @Version 1.0 + **/ +@Data +public class DocInfoDiffDTO { + + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long id; + + /** + * doc_info.id, 数据库字段:doc_id + */ + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long docId; + + /** + * 模块id,module.id + */ + private long moduleId; + + /** 差异类型: 1: 推送缺少文档,2:文档比对差异 */ + private Byte diffType; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; +} diff --git a/server/server-service/src/main/java/cn/torna/service/dto/DocInfoExtDTO.java b/server/server-service/src/main/java/cn/torna/service/dto/DocInfoExtDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..50b632623114661dfb8ad2457b933850e744726f --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/dto/DocInfoExtDTO.java @@ -0,0 +1,41 @@ +package cn.torna.service.dto; + +import cn.torna.common.support.IdCodec; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * + * @Author jyh + * @Date 2025/11/3 09:07 + * @Version 1.0 + **/ +@Data +public class DocInfoExtDTO { + + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long id; + + /** + * doc_info.id, 数据库字段:doc_id + */ + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long docId; + + /** 模块id,module.id */ + private long moduleId; + + /** 文档属性 */ + private String docProp; + + /** 文档属性值 */ + private String docValue; + + /** 数据库字段:gmt_create */ + private LocalDateTime gmtCreate; + + /** 数据库字段:gmt_modified */ + private LocalDateTime gmtModified; +} diff --git a/server/server-service/src/main/java/cn/torna/service/dto/DocParamDiffDTO.java b/server/server-service/src/main/java/cn/torna/service/dto/DocParamDiffDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..227ca6f6fe99b8fc0c2005462d12ccbd50533493 --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/dto/DocParamDiffDTO.java @@ -0,0 +1,59 @@ +package cn.torna.service.dto; + +import cn.torna.common.support.IdCodec; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @Author jyh + * @Date 2025/10/24 14:53 + * @Version 1.0 + **/ +@Data +public class DocParamDiffDTO { + + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long id; + + /** + * 根据diff_type决定doc_param.id 或 doc_param_tmp.id + */ + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long paramId; + + /** + * doc_info.id, 数据库字段:doc_id + */ + private Long docId; + + /** + * 0:path, 1:header, 2:请求参数,3:返回参数,4:错误码, 数据库字段:style + */ + private Byte style; + + /** + * 差异类型: 1: 临时参数缺少字段,-1: 临时参数多出字段, 2: 名称一致,类型不一致 + */ + private Byte diffType; + + /** 排序索引, 数据库字段:order_index */ + private Integer orderIndex; + + /** + * 差异字段名称 + */ + private String diffParamName; + + /** + * 差异字段类型 + */ + private String diffParamType; + + /** + * 数据库字段:gmt_create + */ + private LocalDateTime gmtCreate; + +} diff --git a/server/server-service/src/main/java/cn/torna/service/dto/DocParamPushDTO.java b/server/server-service/src/main/java/cn/torna/service/dto/DocParamPushDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..4b25235af5ed42bbbd9fe186376f1bf6a775b320 --- /dev/null +++ b/server/server-service/src/main/java/cn/torna/service/dto/DocParamPushDTO.java @@ -0,0 +1,59 @@ +package cn.torna.service.dto; + +import cn.torna.common.annotation.Diff; +import cn.torna.common.bean.TreeAware; +import cn.torna.common.enums.PositionType; +import cn.torna.common.support.IdCodec; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * @Author jyh + * @Date 2025/10/24 14:53 + * @Version 1.0 + **/ +@Data +public class DocParamPushDTO implements TreeAware { + + @JSONField(serializeUsing = IdCodec.class, deserializeUsing = IdCodec.class) + private Long id; + + /** + * 字段名称, 数据库字段:name + */ + private String name; + + /** + * 字段类型, 数据库字段:type + */ + private String type; + + /** + * doc_info.id, 数据库字段:doc_id + */ + private Long docId; + + /** 排序索引, 数据库字段:order_index */ + private Integer orderIndex; + + /** + * 父节点, 数据库字段:parent_id + */ + private Long parentId; + + /** + * 0:header, 1:请求参数,2:返回参数,3:错误码, 数据库字段:style + */ + private Byte style; + + /** + * 数据库字段:gmt_create + */ + private LocalDateTime gmtCreate; + + private List children; + +} diff --git a/server/server-web/src/main/java/cn/torna/web/controller/doc/DocController.java b/server/server-web/src/main/java/cn/torna/web/controller/doc/DocController.java index 18496c10ceaaebb6eba147ae33285a0dbd8913ba..4759a4460e15e3938fa78b476ddab00784ee5da4 100644 --- a/server/server-web/src/main/java/cn/torna/web/controller/doc/DocController.java +++ b/server/server-web/src/main/java/cn/torna/web/controller/doc/DocController.java @@ -10,6 +10,7 @@ import cn.torna.common.enums.ParamStyleEnum; import cn.torna.common.exception.BizException; import cn.torna.common.util.CopyUtil; import cn.torna.dao.entity.DocInfo; +import cn.torna.dao.entity.DocInfoExt; import cn.torna.dao.entity.ModuleEnvironment; import cn.torna.dao.entity.ModuleEnvironmentParam; import cn.torna.service.DocInfoService; @@ -38,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -382,4 +384,29 @@ public class DocController { return Result.ok(); } + /** + * 比对文档 + * + * @param moduleId + * @return 比对结果 + */ + @GetMapping("monitor/compare") + public Result compareMonitorDoc(@HashId Long moduleId) { + docInfoService.compareMonitorDoc(moduleId); + return Result.ok(); + } + + /** + * 是否忽略参数比较(应用于动态参数) + * + * @param docId + * @param moduleId + * @param status 1:是,0:否 + * @return + */ + @GetMapping("monitor/ignore/status") + public Result ignoreCompareParam(@HashId Long docId, @HashId Long moduleId, boolean status) { + docInfoService.ignoreCompareParam(docId, moduleId, status); + return Result.ok(); + } } diff --git a/server/server-web/src/main/java/cn/torna/web/controller/doc/ViewController.java b/server/server-web/src/main/java/cn/torna/web/controller/doc/ViewController.java index a3022914106a9a2a5271dce700e2157eadb7762b..3298957a4f1229ebff804df19b8074d2db4698ef 100644 --- a/server/server-web/src/main/java/cn/torna/web/controller/doc/ViewController.java +++ b/server/server-web/src/main/java/cn/torna/web/controller/doc/ViewController.java @@ -5,6 +5,7 @@ import cn.torna.common.bean.Result; import cn.torna.common.bean.User; import cn.torna.common.exception.BizException; import cn.torna.common.util.CopyUtil; +import cn.torna.common.util.IdUtil; import cn.torna.dao.entity.Project; import cn.torna.dao.entity.Space; import cn.torna.dao.entity.SpaceUser; @@ -13,6 +14,9 @@ import cn.torna.service.DocViewService; import cn.torna.service.ProjectService; import cn.torna.service.SpaceService; import cn.torna.service.dto.DocInfoDTO; +import cn.torna.service.dto.DocInfoDiffDTO; +import cn.torna.service.dto.DocInfoExtDTO; +import cn.torna.service.dto.DocParamDiffDTO; import cn.torna.service.dto.ProjectDTO; import cn.torna.service.dto.SpaceProjectDTO; import cn.torna.service.dto.TreeDTO; @@ -21,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -116,5 +121,41 @@ public class ViewController { return Result.ok(docInfoDTO); } + /** + * 参数比对结果 + * + * @param id 主键 + * @return 比对结果 + */ + @GetMapping("compare/param/result") + public Result> paramCompareResult(@HashId Long id) { + List compareResultList = docInfoService.getParamCompareResult(id); + return Result.ok(compareResultList); + } + + /** + * 文档比对结果 + * + * @param moduleId 主键 + * @return 比对结果 + */ + @GetMapping("compare/doc/result") + public Result> docCompareResult(@HashId Long moduleId) { + List compareResultList = docInfoService.getDocCompareResult(moduleId); + return Result.ok(compareResultList); + } + + /** + * 列出该文档属性和模块ID下所有扩展属性 + * + * @param docProp + * @param moduleId + * @return 扩展属性列表 + */ + @GetMapping("ext/{docProp}/{moduleId}/list") + public Result> listDocExtByPropAndModuleId(@PathVariable("docProp") String docProp, + @PathVariable("moduleId") String moduleId) { + return Result.ok(docInfoService.listDocExtByPropAndModuleId(docProp, IdUtil.decode(moduleId))); + } }