Compare commits

...

253 Commits

Author SHA1 Message Date
wanjia 4ada9fc829 fix(dms):社区动态总数去除评论数 2026-03-26 09:38:47 +08:00
wanjia e8bdc8fc92 feat(dms):excel导入校企成果,单位及团队名称导入补充 2026-03-05 09:15:30 +08:00
wanjia 3a0f0d84c5 feat(dms):excel导入校企成果,图片为空时结果处理 2026-03-05 08:40:40 +08:00
wanjia 569eaa14ff feat(dms):excel导入校企成果,默认状态设置 2026-03-03 16:58:38 +08:00
wanjia 33e77ed05b feat(dms):excel导入校企成果,支持图片
(cherry picked from commit 89cf31f69a)
2026-03-03 15:53:58 +08:00
otto 7c785d4743 Merge pull request 'feat(数据管理体系模块): 修复hanlp版本错误导致的异常问题' (#1043) from otto/microservices:dev_monitoring into dev_monitoring 2026-01-27 16:02:48 +08:00
欧涛 c890870e09 feat(数据管理体系模块): 修复hanlp版本错误导致的异常问题 2026-01-27 16:06:54 +08:00
欧涛 07a70f12ce Merge branch 'master' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2026-01-26 14:45:01 +08:00
otto 5daaa3d54a Merge pull request '合并主干分支代码' (#1042) from otto/microservices:dev_monitoring into dev_monitoring 2026-01-26 14:36:31 +08:00
欧涛 e87484b1e1 Merge branch 'master' of code.gitlink.org.cn:otto/microservices into dev_monitoring
# Conflicts:
#	microservices-modules/microservices-modules-dms/pom.xml
#	microservices-modules/microservices-modules-pms/src/main/java/com/microservices/pms/utils/PmsGitLinkRequestUrl.java
2026-01-26 14:40:20 +08:00
欧涛 c22addc4e5 feat(特色专区功能完善): 初始化专区项目聚合统计增加延时(批量并发进行可能导致数据池崩溃) 2026-01-23 16:25:20 +08:00
otto 43d2ce4231 Merge pull request '初始化专区项目聚合统计增加延时(批量并发进行可能导致数据池崩溃)' (#1041) from otto/microservices:master into master 2026-01-23 16:22:27 +08:00
欧涛 fca7e4e502 feat(特色专区功能完善): 增加检测专区在gitlink是否存在对应项目机制
1. 查询专区列表时,若专区对应项目不存在则返回null(页面展示时则不提供项目仓库跳转链接)
2. 用户点击初始化时先检查数据库CmsProject表中是否存在项目记录,若存在则检查该项目对应的forge项目是否存在,若均存在则提示用户不能重复初始化专区对应项目
3. 若CmsProject表中存在项目记录但该项目对应的forge项目不存在则调用Forge接口创建Forge项目
4. 若CmsProject表中不存在项目记录,则调用创建专区项目接口,完成数据库和forge端的同步创建
5. 数据库及forge端创建完成后,在forge端项目中创建文章对应的所有文件(但是文章内容只能为空),同时创建完成后更新数据库中文章的sha值
2026-01-23 11:41:46 +08:00
欧涛 e92c412c4e feat(特色专区-栏目优化): 栏目支持上传图片 2026-01-22 16:40:58 +08:00
otto f94a885908 Merge pull request 'feat(特色专区-栏目优化): 栏目支持上传图片' (#1040) from otto/microservices:master into master 2026-01-22 16:39:15 +08:00
欧涛 5776ddd748 feat(特色专区-栏目优化): 栏目支持上传图片
针对公开的文章接口数据库查询文章列表时,若文章未设置头图则将栏目头图设置文文章头图。
2026-01-21 16:28:15 +08:00
otto 661709efaf Merge pull request '针对公开的文章接口数据库查询文章列表时,若文章未设置头图则将栏目头图设置文文章头图。' (#1039) from otto/microservices:master into master 2026-01-21 16:23:59 +08:00
欧涛 7760f5afb3 feat(特色专区-栏目优化): 栏目支持上传图片 2026-01-21 14:57:59 +08:00
otto 575170a801 Merge pull request 'feat(特色专区-栏目优化): 栏目支持上传图片' (#1038) from otto/microservices:master into master 2026-01-21 14:53:39 +08:00
otto be80d64dd5 Merge pull request 'feat(特色专区-栏目优化): 栏目支持上传图片' (#1037) from otto/microservices:master into master 2026-01-21 14:50:19 +08:00
欧涛 6f1068f082 feat(特色专区-栏目优化): 栏目支持上传图片 2026-01-21 14:38:53 +08:00
欧涛 3589a9b51a feat(特色专区-栏目重命名): 优化栏目重命名功能
主要变更:
- 新增CustomExecutorFactory线程池工厂,支持异步并发处理
- 重构asyncMoveFileToNewDir方法,直接在Git仓库中移动文章到新栏目
- 优化EntryDto类,新增JSON解析方法增强异常处理
- 移除冗余的栏目锁定检查,简化重命名逻辑
- 优化用户提示信息,提升交互体验

技术改进:
- 使用CountDownLatch+线程池实现高效并发文章移动
- 优化Git操作流程,减少不必要的创建删除操作
- 统一代码格式规范,提升可维护性
2026-01-21 11:31:31 +08:00
欧涛 e9e3852364 fix(项目管理-离职逻辑): 撤销离职相关修改 2026-01-19 17:31:55 +08:00
otto 1aabdcf752 Merge pull request 'fix(项目管理-离职逻辑): 撤销离职相关修改' (#1036) from otto/microservices:master into master 2026-01-19 17:27:49 +08:00
欧涛 44f0bfb4c8 feat(项目管理-离职逻辑): 原始需求中已离职用户为标注提示
返回用户信息时,根据用户的标签,判断是否为离职用户,并给出相应的提示信息。
2026-01-19 17:25:08 +08:00
otto ab58222217 Merge pull request 'fix(项目管理-离职逻辑): 原始需求中已离职用户为标注提示' (#1035) from otto/microservices:master into master 2026-01-19 17:21:24 +08:00
欧涛 a3ccf62c10 feat(项目管理-离职逻辑): 原始需求中已离职用户为标注提示
返回用户信息时,根据用户的标签,判断是否为离职用户,并给出相应的提示信息。
2026-01-19 17:13:12 +08:00
wanjia9506 a4e92ca636 Merge pull request 'bug fix' (#1034) from wanjia9506/microservices:master into master 2026-01-16 09:33:23 +08:00
wanjia9506 8bfd8c6e1a Merge pull request 'dms-慢查询优化' (#1033) from wanjia9506/microservices:master into master 2026-01-12 11:14:48 +08:00
otto 3e4c801cbd Merge pull request 'feat(特色专区-软件工厂定制化): 专区资源支持根据下载数排序' (#1032) from otto/microservices:master into master 2025-12-30 17:08:01 +08:00
欧涛 c76b6efa20 Merge branch 'master' of code.gitlink.org.cn:otto/microservices into dev_zone_rjgc 2025-12-29 10:25:06 +08:00
欧涛 0d833872b6 feat(特色专区-软件工厂定制化): 专区资源支持根据下载数排序 2025-12-25 19:12:11 +08:00
otto 4782b8c0c0 Merge pull request '软件工厂专区' (#1028) from wanjia9506/microservices:dev_zone_rjgc into dev_zone_rjgc 2025-12-25 17:08:57 +08:00
wanjia e792599fc3 Merge branch 'master' of https://gitlink.org.cn/Gitlink/microservices into dev_zone_rjgc 2025-12-25 17:01:29 +08:00
wanjia9506 2e9c3d0f9d Merge pull request '专区资源支持更新时间字段排序' (#1027) from wanjia9506/microservices:master into master 2025-12-12 13:51:37 +08:00
wanjia9506 b7e68454ac Merge pull request '专区资源拓展数据字段' (#1026) from wanjia9506/microservices:master into master 2025-12-11 11:13:44 +08:00
wanjia9506 e3921d833b Merge pull request '专区资源支持标签搜索' (#1025) from wanjia9506/microservices:master into master 2025-12-10 09:02:55 +08:00
wanjia9506 723825064c Merge pull request '专区资源支持标签' (#1023) from wanjia9506/microservices:master into master 2025-12-09 13:59:15 +08:00
otto 02791388bd Merge pull request '修复新建deployment的同时新建service,但是删除时,未删除service的问题' (#1021) from otto/microservices:dev_monitoring into dev_monitoring 2025-11-24 10:16:32 +08:00
OTTO 5d14d5e56a fix(运维监控): 修复新建deployment的同时新建service,但是删除时,未删除service的问题
1. 问题分析:
在正常情况下,支持重复创建应用,即使Service已经创建也支持通过创建操作直接更新原Service,但是部分特殊情况下Service需要暴露指定端口,此时若重复创建会出现端口重复的问题;
2. 处理方案:
删除应用时同步删除对应Service;

关联Issue:https://pm.osredm.com/osredm/projects/27/bug/4152
2025-11-14 17:06:52 +08:00
OTTO e3ddab5ceb fix(运维监控): 优化日志打印逻辑 2025-11-14 15:07:16 +08:00
otto 587a2707a7 Merge pull request 'fix(数据管理体系): 修复打包时丢失依赖的问题' (#1019) from otto/microservices:dev_monitoring into dev_monitoring 2025-11-12 09:59:00 +08:00
OTTO 992c17b4c8 fix(数据管理体系): 修复打包时丢失依赖的问题 2025-11-12 09:57:26 +08:00
OTTO 0aa96e853a Merge branch 'master' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-11-12 09:36:08 +08:00
OTTO 34ab56eb8a fix(运维监控): 新增K8S异常事件站内信通知功能
修复发送用户存在多人时用户列表转换错误的问题
2025-11-07 10:22:23 +08:00
otto bdbd2da88d Merge pull request '修复发送用户存在多人时用户列表转换错误的问题' (#1018) from otto/microservices:dev_monitoring into dev_monitoring 2025-11-07 10:20:14 +08:00
otto 30f8ce8184 Merge pull request '新增K8S异常事件站内信通知功能' (#1017) from otto/microservices:dev_monitoring into dev_monitoring 2025-11-06 17:19:48 +08:00
OTTO 7fbfbdaf8d feat(运维监控): 新增K8S异常事件站内信通知功能
1. 实现K8S Warning类型异常事件自动监控和站内信通知
2. 添加定时任务,每10秒执行一次事件检测
3. 集成ES查询和站内信发送能力
4. 仅处理Warning类型K8S事件(排除异常原因为"Unhealthy"的事件)
5. 处理最近300秒内且未发送通知的事件(通过sendNotification字段判断)
2025-11-06 17:17:15 +08:00
OTTO 93ed7838e6 feat(运维监控): 新增K8S异常事件站内信通知功能
1. 实现K8S Warning类型异常事件自动监控和站内信通知
2. 添加定时任务,每10秒执行一次事件检测
3. 集成ES查询和站内信发送能力
4. 仅处理Warning类型K8S事件(排除异常原因为"Unhealthy"的事件)
5. 处理最近300秒内且未发送通知的事件(通过sendNotification字段判断)
2025-11-06 17:12:31 +08:00
otto 68988926b9 Merge pull request 'fix(项目管理): 修复项目管理社区管理Forge接口同步更新的问题' (#1015) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-22 10:21:10 +08:00
OTTO 5485d39894 Merge branch 'dev_monitoring' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-10-22 09:53:27 +08:00
OTTO 2516ba038e fix(项目管理): 修复项目管理社区管理Forge接口同步更新的问题 2025-10-22 09:46:58 +08:00
OTTO e80d7f8a6f fix(项目管理): 修复项目管理社区管理Forge接口同步更新的问题 2025-10-22 09:45:01 +08:00
OTTO bb9b4f05da feat(运维监控): 运维监控服务Nacos相关配置支持动态更新 2025-10-17 15:39:02 +08:00
otto 4bbf3c91e2 Merge pull request 'feat(运维监控): 运维监控服务Nacos相关配置支持动态更新' (#1010) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-17 15:37:19 +08:00
OTTO 00bde1ad9a fix(运维监控): 基于K8S事件开发系统关键活动日志
获取系统资源占用时根据发生时间降序排序
2025-10-13 16:45:50 +08:00
otto 092d566de4 Merge pull request '获取系统资源占用时根据发生时间降序排序' (#1009) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-13 16:44:08 +08:00
OTTO 27c5e64e1a fix(运维监控): 基于K8S事件开发系统关键活动日志
修复关键操作和异常事件分页总数异常的问题
2025-10-13 11:20:58 +08:00
otto 71f4e4bc8f Merge pull request '修复关键操作和异常事件分页总数异常的问题' (#1008) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-13 11:19:14 +08:00
OTTO 8c8e13a5e1 feat(运维监控): 基于K8S事件开发系统关键活动日志
完成获取K8S事件关键活动操作列表接口获取K8S事异常结束事件原因列表接口和获取K8S资源对象列表接口开发
2025-10-11 16:54:30 +08:00
otto 82c74b9184 Merge pull request 'feat(运维监控): 基于K8S事件开发系统关键活动日志' (#1007) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-11 16:52:50 +08:00
OTTO 290fc64c88 feat(运维监控): 基于K8S事件开发系统关键活动日志
完成获取K8S事件异常结束列表接口和资源占用列表接口开发
2025-10-11 16:16:28 +08:00
OTTO b0aede3f93 feat(运维监控): 基于K8S事件开发系统关键活动日志
完成获取K8S事件关键活动列表接口开发
2025-10-11 09:33:47 +08:00
otto 902f961469 Merge pull request '解决dms依赖问题' (#1006) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-09 08:46:57 +08:00
OTTO e904d854f6 Merge branch 'feat_chievements_dev' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-10-09 08:41:37 +08:00
OTTO 3dbdef7ab8 Merge branch 'master' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-10-08 16:19:58 +08:00
otto 072e85de82 Merge pull request '同步数据管理服务最新代码' (#1005) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-08 16:18:45 +08:00
OTTO 8f48c1030a feat(运维监控): 扩大并发线程池线程数 2025-10-08 15:56:55 +08:00
otto 9380081443 Merge pull request 'feat(运维监控): 扩大并发线程池线程数' (#1004) from otto/microservices:dev_monitoring into dev_monitoring 2025-10-08 15:55:07 +08:00
OTTO 0646a3b773 feat(运维监控): 【微服务编排】微服务编排创建应用时支持svc、hpa、config等相关资源创建 2025-09-22 14:38:40 +08:00
otto 43e75440d5 Merge pull request '【微服务编排】微服务编排创建应用时支持svc、hpa、config等相关资源创建' (#1003) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-22 14:38:07 +08:00
OTTO 0ca345d200 feat(运维监控): 【微服务编排】微服务编排创建应用时支持svc、hpa、config等相关资源创建 2025-09-22 14:28:30 +08:00
OTTO 83c1457c50 Merge branch 'master' of code.gitlink.org.cn:otto/microservices into dev_monitoring 2025-09-22 09:59:13 +08:00
otto 3f208de48a Merge pull request 'feat(社区版项目管理)' (#1002) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-22 09:58:19 +08:00
wanjia bce79534bd fix(系统用户):查询最新用户信息
(cherry picked from commit b426441d19)
2025-09-15 16:57:58 +08:00
OTTO f402ec1748 fix(微服务平台): 用户输入环境变量格式异常时返回友好的中文提示信息 2025-09-10 09:53:39 +08:00
otto 7de259d088 Merge pull request 'fix(微服务平台): 用户输入环境变量格式异常时返回友好的中文提示信息' (#997) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-10 09:52:48 +08:00
OTTO 1c9bc630d8 fix(微服务平台): 修复集群状态监控服务名称未显示
当应用未设置中文别名时使用标识替代
2025-09-10 09:34:32 +08:00
otto c13748d494 Merge pull request 'refact(微服务平台): 生产环境所有服务端口统一使用8080,简化配置成本' (#996) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-08 16:46:41 +08:00
OTTO 69e789d856 refact(微服务平台): 生产环境所有服务端口统一使用8080,简化配置成本 2025-09-08 16:43:18 +08:00
otto 02f215c2e4 Merge pull request 'refact(微服务平台): 生产环境所有服务端口统一使用8080,简化配置成本' (#995) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-08 16:23:47 +08:00
OTTO d1076e48d5 refact(微服务平台): 生产环境所有服务端口统一使用8080,简化配置成本 2025-09-08 15:40:07 +08:00
OTTO b25411e35c fix(微服务平台): 微服务编排根据别名进行查询无法查询出结果 2025-09-08 15:13:12 +08:00
OTTO 1f3d65fd39 fix(微服务平台): 微服务编排根据别名进行查询无法查询出结果 2025-09-08 08:38:26 +08:00
otto 3f155f2270 Merge pull request 'fix(微服务平台): 微服务编排根据别名进行查询无法查询出结果' (#994) from otto/microservices:dev_monitoring into dev_monitoring 2025-09-08 08:37:50 +08:00
otto ee77bdb8a1 Merge pull request 'feat(微服务系统管理): 优化微服务系统菜单屏蔽逻辑' (#987) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-29 15:10:04 +08:00
OTTO 631f6752a6 feat(微服务系统管理): 优化微服务系统菜单屏蔽逻辑
1. 屏蔽菜单逻辑优化:根据屏蔽菜单值,检索该菜单的所有子菜单,将该菜单以及子菜单全部进行屏蔽
2. 屏蔽菜单参数支持动态刷新机制
2025-08-29 15:07:09 +08:00
OTTO 39a8dde543 fix(运维监控模块): 【微服务流量控制】“Blocked by Sentinel (flow limiting)”提示更改
设置Sentinel统一异常处理器,分别处理限流规则、熔断规则、热点参数限流规则、授权规则、系统规则的异常信息提示
2025-08-21 16:36:33 +08:00
otto 0f0a639bf4 Merge pull request '【微服务流量控制】“Blocked by Sentinel (flow limiting)”提示更改' (#986) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-21 16:36:29 +08:00
otto cf2c5cffa3 Merge pull request 'fix(运维监控模块): 镜像拉取失败时容器状态未显示为报错状态' (#985) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-21 15:29:31 +08:00
OTTO b89c235df3 fix(运维监控模块): 镜像拉取失败时容器状态未显示为报错状态
在rancher中镜像拉取失败等操作均视为可预期的情况,所以不会设置为报错状态,所以在代码逻辑中进行控制,对已知的非报错情况设置error为false,其余情况均设置为true
2025-08-21 15:29:16 +08:00
OTTO 2d5d43b37e fix(运维监控模块): 微服务应用多次点击重启后无反应
需要更新k8s应用的metadataTemplate字段才可使应用发生重启操作
2025-08-21 14:57:30 +08:00
otto 32b58df5d1 Merge pull request 'fix(运维监控模块): 微服务应用多次点击重启后无反应' (#984) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-21 14:57:02 +08:00
OTTO 555ef0fc80 feat(运维监控模块): 定义部分镜像名称展示 2025-08-13 11:40:50 +08:00
otto 46cbaccf64 Merge pull request 'feat(运维监控模块): 定义部分镜像名称展示' (#982) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-13 11:40:08 +08:00
OTTO ebf1dc53cf feat(运维监控模块): 【微服务可视化面板】可视化面板缺少堆栈、创建时间字段,容器端口按合同需改为发布端口
增加堆栈、创建时间字段

关联Issue:https://pm.osredm.com/osredm/projects/27/task/3707
2025-08-01 16:29:26 +08:00
otto e6dadf2ccd Merge pull request '【可视化面板】增加堆栈、创建时间字段' (#979) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-01 16:28:51 +08:00
OTTO 71cf4fb6fc feat(运维监控模块): 【微服务运行状态监控】完成获取容器CPU、内存、硬盘读写、网络收发情况接口开发
获取容器CPU使用率、内存使用情况时根据pod名称分组计算
2025-08-01 15:05:21 +08:00
otto eed4b9d9eb Merge pull request '获取容器CPU使用率、内存使用情况时根据pod名称分组计算' (#978) from otto/microservices:dev_monitoring into dev_monitoring 2025-08-01 15:04:55 +08:00
OTTO cad9ea1247 feat(运维监控模块): 【微服务运行状态监控】完成获取容器CPU、内存、硬盘读写、网络收发情况接口开发
由于数据量太大展示效果不好,调整为查询最近半小时的数据
2025-08-01 09:45:58 +08:00
OTTO 02e369849d feat(运维监控模块): 【微服务网关管理】支持网关限流
调用Sentinel接口获取流控应用列表,获取appType为11(代表为网关)的应用,构建相关跳转参数对象进行返回
2025-07-31 09:29:30 +08:00
otto f7e9d9350e Merge pull request '【微服务网关管理】支持网关限流' (#977) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-31 09:28:55 +08:00
OTTO 356b85fd35 feat(运维监控模块): 【微服务编排】增加一个搜索功能,支持根据名称和标识进行搜索 2025-07-31 09:03:35 +08:00
otto 19a32e2f7c Merge pull request '【微服务编排】增加一个搜索功能,支持根据名称和标识进行搜索' (#976) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-31 09:03:10 +08:00
otto b2f9921664 fix(运维监控模块): 获取链路追踪应用列表时指标值保留两位小数 2025-07-30 15:25:50 +08:00
otto 2567386baa Merge pull request 'fix(运维监控模块): 获取链路追踪应用列表时指标值保留两位小数' (#975) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-30 15:25:13 +08:00
OTTO 8e10fee65f feat(运维监控模块): 【微服务运行状态监控】完成获取容器CPU、内存、硬盘读写、网络收发情况接口开发
新增获取容器 CPU、内存、磁盘读写、网络收发等资源使用情况的接口:
1. 调用Rancher接口获取命名空间列表
2. 若用户未传入统计频率设置默认统计频率为10s
3. 设置Prometheus查询时间范围为最近一小时
4. 根据CPU、内存、硬盘读写、网络收发设置Prometheus查询语句
5. 提取Prometheus查询结果中container_label_io_kubernetes_pod_name作为Pod名称,提取数值作为指标值
2025-07-30 15:06:47 +08:00
otto 310b423ec9 Merge pull request '【微服务运行状态监控】完成获取容器CPU、内存、硬盘读写、网络收发情况接口开发' (#974) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-30 15:06:23 +08:00
OTTO 705823ec3f feat(运维监控模块): 【微服务运行状态监控】完成获取容器启停状态接口开发
1. 调用Prometheus接口查询容器状态为“Running”的个数以及容器总数量
2. 调用Prometheus接口查询容器的创建时间,调用Rancher接口查询容器元数据和状态数据,筛选出不为“running”状态的所有容器,并获取该容器状态为False的condition,获取condition的lastTransitionTime作为该容器的停止时间
2025-07-30 10:58:31 +08:00
OTTO 172e0ee92e Merge remote-tracking branch 'origin/dev_monitoring' into dev_monitoring 2025-07-30 10:32:15 +08:00
OTTO 726ece8af9 fix(运维监控模块): 网关获取Rancher Token失败
修复方法:将超时时间延长至3s
2025-07-30 10:32:02 +08:00
OTTO 82f180d31c fix(运维监控模块): 网关获取Rancher Token失败
修复方法呢:将超时时间延长至3s
2025-07-30 10:31:46 +08:00
otto e91ee14d63 Merge pull request 'fix(运维监控模块): 网关获取Rancher Token失败' (#968) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-30 10:31:27 +08:00
OTTO 10a1b3f5cf fix(运维监控模块): 微服务启停操作
1. 【微服务编排】微服务停止后,状态应一并变化
2. 【微服务编排】微服务操作暂停但页面pod状态无变化
2025-07-29 13:45:12 +08:00
otto 7f22a2c6ec Merge pull request 'fix(运维监控模块): 微服务启停操作' (#965) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-29 13:44:34 +08:00
OTTO 9c76980bdc fix(运维监控模块): 【微服务链路追踪】提供链路追踪列表页面,可在左侧菜单中直接进入链路追踪功能
修复当搜索筛选后排序序号异常的问题
2025-07-29 10:22:05 +08:00
otto a861d69061 Merge pull request '修复当搜索筛选后排序序号异常的问题' (#964) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-29 10:21:40 +08:00
OTTO 53274c3b0e fix(运维监控模块): 【微服务链路追踪】提供链路追踪列表页面,可在左侧菜单中直接进入链路追踪功能
修复当查询条件为空导致报错的问题
2025-07-29 10:02:58 +08:00
otto 32ded69b77 Merge pull request '修复当查询条件为空导致报错的问题' (#963) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-29 10:02:20 +08:00
otto 36e9d84dd8 Merge pull request '【微服务链路追踪】提供链路追踪列表页面' (#962) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-29 09:51:42 +08:00
OTTO 4e0d281bf7 feat(运维监控模块): 【微服务链路追踪】提供链路追踪列表页面,可在左侧菜单中直接进入链路追踪功能
实现对应用名称的模糊搜索
2025-07-29 09:50:42 +08:00
OTTO fbcbe63ad4 feat(运维监控模块): 【微服务链路追踪】提供链路追踪列表页面,可在左侧菜单中直接进入链路追踪功能
1. 把“获取链路追踪应用列表”和“获取应用链路追踪关键指标”拆成两步:
• 第 1 步:根据关键字(应用名)调用 SkyWalking searchServices 接口拿到 Service 列表。
• 第 2 步:将上一步返回的 Service id 与 name 组装成批量 GraphQL 查询,统一一次性拉取 CPM、SLA、Avg Response Time 三项指标。
2. 第 2 步的批量查询通过动态拼装 GraphQL 变量与函数名完成,避免 N+1 次请求。
3. 对 SkyWalking 返回的指标值做“按分钟维度求平均”后落到 LinkTrackingApplication 对象。
2025-07-29 09:36:50 +08:00
otto 48e0510bb3 Merge pull request '【网关异常处理】完成获取网关异常处理记录相关查询接口开发' (#959) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-28 11:40:21 +08:00
OTTO 3726e4b294 feat(运维监控模块): 【网关异常处理】完成获取网关异常处理记录相关查询接口开发
1. 完成获取存在网关异常处理记录的日期列表接口开发:扫描日志目录,提取所有 error*.log 对应的日期并倒序返回
2. 完成获取网关异常处理记录接口开发:
- 按日期读取对应 error 日志,过滤包含“异常处理”的行
- 日志文件缺失或 IO 异常时统一抛出 ServiceException,附带具体日期信息
2025-07-28 11:38:39 +08:00
OTTO 335d05d2c3 feat(运维监控模块): 【网关异常处理】完成获取网关异常处理记录相关查询接口开发
1. 完成获取存在网关异常处理记录的日期列表接口开发:扫描日志目录,提取所有 error*.log 对应的日期并倒序返回
2. 完成获取网关异常处理记录接口开发:
- 按日期读取对应 error 日志,过滤包含“异常处理”的行
- 日志文件缺失或 IO 异常时统一抛出 ServiceException,附带具体日期信息
2025-07-28 11:20:44 +08:00
OTTO fa4ee4526b feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量接口
统一环境变量各类型返回字段
2025-07-25 17:06:45 +08:00
otto 405b2e07c1 Merge pull request '【微服务管理】支持服务网关管理' (#958) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-25 17:06:28 +08:00
OTTO c6fd9a1c9d feat(运维监控模块): 【微服务管理】支持服务网关管理(基于网关的配置文件来实现)
新增更新网关路由配置接口、新增更新网关安全认证配置接口、新增更新网关白名单配置接口:
1. 调用Nacos接口获取最新的网关配置,并将配置内容转换为JSONObject对象
2. 基于用户传入的更新参数,更新网关配置对象
3. 调用Nacos配置更新接口,更新网关配置
2025-07-25 16:10:40 +08:00
OTTO bfbaa3491d feat(运维监控模块): 【微服务管理】支持服务网关管理(基于网关的配置文件来实现)
新增获取微服务网关白名单配置接口:
1. 调用Nacos接口以yaml文本形式获取网关配置内容
2. 将yaml文本转换为JSONObject对象
3. 从JSONObject对象中提取网关白名单配置信息
2025-07-25 15:09:10 +08:00
OTTO c97b64f3bc feat(运维监控模块): 【微服务管理】支持服务网关管理(基于网关的配置文件来实现)
新增获取微服务网关安全认证配置接口:
1. 调用Nacos接口以yaml文本形式获取网关配置内容
2. 将yaml文本转换为JSONObject对象
3. 从JSONObject对象中提取网关安全认证配置信息
2025-07-25 15:00:46 +08:00
OTTO 83c8d5fe3e feat(运维监控模块): 【微服务管理】支持服务网关管理(基于网关的配置文件来实现)
新增获取微服务网关路由配置接口:
1. 调用Nacos接口以yaml文本形式获取网关配置内容
2. 将yaml文本转换为JSONObject对象
3. 从JSONObject对象中提取网关路由配置信息
2025-07-25 15:00:35 +08:00
OTTO 7cff154598 refactor(运维监控模块): 优化获取应用功能列表接口
获取网关管理功能相关参数时,直接从Nacos中获取
2025-07-25 14:10:59 +08:00
OTTO d0fcf1fbce refactor(运维监控模块): 统一处理Rancher接口异常信息
优化方法命名
2025-07-25 10:12:29 +08:00
otto 3cf7ced976 Merge pull request '统一处理Rancher接口异常信息' (#956) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-25 10:12:21 +08:00
OTTO ddb1a06acc refactor(运维监控模块): 统一处理Rancher接口异常信息
1. 将所有Rancher远程调用接口的响应调整为Response
2. 在监控运维服务中对Rancher接口响应进行统一处理(携带操作方法名称作为入参)
- 当Rancher远程接口响应Body为空时抛出异常:[xx操作]时发生错误,请联系系统管理员
- 读取Body内容并转换为JSONObject类型,
- 当响应状态码为2xx时,若调用方需要JSONObject类型则直接返回,若需要Boolean类型则返回true
- 当响应状态码不是为2xx时,则解析body对象,读取message字段作为异常信息返回给用户
2025-07-25 09:25:00 +08:00
OTTO 0c94e636e1 feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量接口
统一环境变量各类型返回字段
2025-07-24 17:04:43 +08:00
otto 0f2d37babe Merge pull request '统一环境变量各类型返回字段' (#955) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-24 17:04:16 +08:00
OTTO 6e72d6f762 feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量可选参数接口
新增资源相关环境变量的可选参数
2025-07-24 15:12:59 +08:00
otto d330e7a56d Merge pull request '新增资源相关环境变量的可选参数' (#953) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-24 15:12:29 +08:00
OTTO 29dbc84989 feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量类型接口 2025-07-24 14:48:54 +08:00
otto f055d0f28e Merge pull request '【编排环境变量管理】新增获取应用环境变量类型接口' (#952) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-24 14:48:48 +08:00
OTTO 20ce451a9a feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量可选参数接口
新增资源相关环境变量的可选参数
2025-07-24 14:40:55 +08:00
otto 629822dcb9 Merge pull request '新增资源相关环境变量的可选参数' (#951) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-24 14:40:45 +08:00
otto e7ca96f280 Merge pull request '支持编排环境变量管理' (#949) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-24 14:35:29 +08:00
OTTO f9956c305e feat(运维监控模块): 【编排环境变量管理】新增更新应用环境变量接口
1. 根据 EnvType 区分 KEY_VALUE_PAIR/RESOURCE/CONFIG_MAP_KEY/SECRET_KEY/POD_FIELD/SECRET/CONFIGMAP 七类环境变量
2. 构建符合 K8s spec 的 env 与 envFrom 数组,并写回第一个容器
3. 统一校验应用存在性及 spec.template.spec 路径,异常时抛出 ServiceException
4. 通过 Rancher API 完整更新应用配置,成功返回 true,失败统一处理错误信息
2025-07-24 14:31:07 +08:00
OTTO d13b38e980 feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量可选参数接口
1. 调用getK8sConfigmapByNamespace获取命名空间下的所有 ConfigMap和Secret数据;
2. 遍历ConfigMap/Secret数据,提取每个ConfigMap/Secret 的名称和键值集合;
3. 构建K8sApplicationEnvOptionalParameter对象并设置类型为 CONFIGMAP或Secret;
2025-07-24 10:48:25 +08:00
OTTO 45c84d17b6 feat(运维监控模块): 【编排环境变量管理】新增获取应用环境变量接口
将 Kubernetes 容器定义中的环境变量(env)与环境变量来源(envFrom)的 JSON 片段统一转换成内部模型 K8sApplicationEnv:
1. env:
- 区分五种情形:直接键值对、resourceFieldRef 资源字段引用、configMapKeyRef 指定键、secretKeyRef 指定键以及 fieldRef Pod 字段引用;
- 分别设置对应的 Type 枚举值(KEY_VALUE_PAIR、RESOURCE、CONFIG_MAP_KEY、SECRET_KEY、POD_FIELD),同时把引用信息封装到 valueObj 中;若入参为 null 或结构无法识别则返回 null。
2. envFrom:
- 处理整个 ConfigMap 或整个 Secret 的批量引入场景;
- 通过 configMapRef 或 secretRef 节点提取名称,并把 prefix(可能为空)作为 key,类型设为 CONFIGMAP 或 SECRET,同样把名称写入 valueObj;无法识别或入参为 null 时也返回 null。
2025-07-24 09:16:29 +08:00
OTTO 71ec75c3e5 fix(运维监控模块): 【微服务大屏】微服务POD运行状态增加状态描述
增加Unknown状态

关联Issue:https://pm.osredm.com/osredm/projects/27/task/3441
2025-07-23 14:26:19 +08:00
otto 0d0b0072c3 Merge pull request 'fix(运维监控模块): 【微服务大屏】微服务POD运行状态增加状态描述' (#947) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-23 14:25:44 +08:00
otto 8f84d425c8 Merge pull request '完善微服务模块定制化编排功能' (#946) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-23 14:12:38 +08:00
OTTO 1cb6c3e758 feat(运维监控模块): 【微服务管理】微服务模块定制化编排
1. 新增删除应用接口
2. 抽取Rancher错误响应的处理逻辑,创建公共处理方法
3. 将所有“Pod”展示调整为容器,简化用户理解
2025-07-23 14:11:42 +08:00
OTTO 17a911c54e feat(运维监控模块): 【微服务管理】微服务模块定制化编排
新增编辑应用Yaml文件接口:
- 根据传入的 kind 获取对应的应用类型枚举;
- 调用 remoteRancherService.editK8sApplication 发送编辑请求;
- 如果响应成功(状态码 200~299),返回 true;
- 否则读取响应体,尝试解析为 JSON,若包含 message 字段则抛出带有该错误信息的异常;
- 若读取响应体过程中发生 IO 异常,也抛出异常;
- 默认返回 false 表示编辑失败。
2025-07-23 11:33:30 +08:00
OTTO adc86b04c9 feat(运维监控模块): 【微服务管理】微服务模块定制化编排
新增获取K8S应用Yaml接口(用于Yaml文件下载和编辑应用Yaml时使用):
通过Yaml转换器将JSONObject转换为Yaml格式返回
2025-07-23 10:51:31 +08:00
OTTO cd19bd94cc Merge remote-tracking branch 'origin/dev_monitoring' into dev_monitoring 2025-07-22 17:31:42 +08:00
otto dc814cffff fix(运维监控模块): 完善接口:获取微服务应用功能列表
修复当应用没有标签时无法获取启停功能的bug
2025-07-22 17:31:15 +08:00
OTTO 92bfd2c7f2 fix(运维监控模块): 完善接口:获取微服务应用功能列表
修复当应用没有标签时无法获取启停功能的bug
2025-07-22 16:54:46 +08:00
otto 6a71629784 Merge pull request '修复当应用没有标签时无法获取启停功能的bug' (#945) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-22 16:54:13 +08:00
otto 1e016300c7 Merge pull request '支持编排定义与创建' (#944) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-22 16:46:22 +08:00
OTTO fdf61667f7 feat(运维监控模块): 【微服务模块定制化编排】支持编排定义与创建
1. 根据 kind 获取对应的应用类型(如Deployment、DaemonSet等)。
2. 调用 RemoteRancherService 的 createK8sApplication 方法发送创建请求,调用创建方法时将请求的Content-Type设置为application/yaml。
3. 如果响应成功(状态码 200~299),返回 true。
4. 如果失败,读取响应体中的错误信息并抛出 ServiceException。
5. 若解析响应失败,捕获 IOException 并抛出相应异常。
2025-07-22 16:38:03 +08:00
OTTO 22f350510c feat(运维监控模块): 【微服务模块定制化编排】支持编排定义与创建
构建K8S应用类型枚举,作为编排创建时的类型选择
2025-07-22 15:03:49 +08:00
OTTO 2566f119d3 Merge remote-tracking branch 'origin/dev_monitoring' into dev_monitoring 2025-07-22 09:49:50 +08:00
otto 19718b6123 Merge pull request 'pod列表根据命名空间排序,相同命名空间根据名称排序' (#943) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-22 09:49:42 +08:00
OTTO 3fa2daac28 fix(运维监控模块): 【微服务可视化面板】支持”所属命名空间“排序
pod列表根据命名空间排序,相同命名空间根据名称排序

关联issue:https://pm.osredm.com/osredm/projects/27/bug/3668
2025-07-22 09:49:26 +08:00
OTTO 2d304cfae0 fix(运维监控模块): 【微服务可视化面板】支持”所属命名空间“排序
pod列表根据命名空间排序,相同命名空间根据名称排序
2025-07-22 09:49:06 +08:00
otto 1f2f48c120 Merge pull request '根据应用状态和副本数判断是否允许启停操作' (#942) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-22 09:26:18 +08:00
OTTO 19e298b67a feat(运维监控模块): 完善接口:获取微服务应用功能列表
根据应用状态和副本数判断是否允许启停操作:
1. 启动:如果副本数为 0时;
2. 停止、强制停止、重启:如果副本数大于 0时;
3. 暂停:当前编排状态不为暂停时;
4. 继续:当前编排状态为暂停时。
2025-07-22 09:20:24 +08:00
otto f70681d783 Merge pull request '支持微服务启停管理' (#941) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-21 16:59:02 +08:00
OTTO 8abb5a38bd feat(运维监控模块): 支持微服务启停管理
1. 校验操作类型:根据传入的 operationKey 获取操作类型,若不支持则抛异常。
2. 获取应用详情:调用 RemoteRancherService 获取应用的详细信息。
3. 清理无用字段:去除 JSON 数据中不必要的字段,避免更新冲突。
4. 根据操作类型执行对应逻辑:
- START:设置副本数为1,启动应用。
- STOP:设置副本数为0,停止应用。
- FORCE_STOP:先停止应用,再强制删除处于 Terminating 状态的 Pod。
- RESTART:通过修改 deployment 的注解触发重启。
- PAUSED / RESUME:设置 paused 字段为 true 或 false,控制应用暂停或恢复。
5. 调用远程服务更新应用配置,并返回操作是否成功。
2025-07-21 16:58:00 +08:00
OTTO 9eb10143f7 feat(运维监控模块): 增加微服务监控相关注释 2025-07-21 10:37:06 +08:00
otto 9865c8bad9 Merge pull request '修复基于containerId获取Pod相关监控状态时(cadvisor)部分情况无法获取数据的问题' (#937) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-21 09:13:39 +08:00
OTTO bc92fb8419 fix(运维监控模块): 修复基于containerId获取Pod相关监控状态时(cadvisor)部分情况无法获取数据的问题
调整为通过podName获取pod容器监控数据
2025-07-21 09:13:09 +08:00
OTTO a25048faad fix(运维监控模块): 修复获取数据时数据排序经常变动导致用户体验差的问题
1. 对获取命名空间列表接口内所有列表内容进行排序
2. 对获取pod列表接口列表内容进行排序
2025-07-18 14:41:43 +08:00
otto 8c068c4b79 Merge pull request '修复获取数据时数据排序经常变动导致用户体验差的问题' (#936) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-18 14:41:26 +08:00
OTTO 735d7fd9e9 feat(运维监控模块): 支持服务网关管理
网关异常处理:
格式化网关异常处理日志
2025-07-18 14:19:15 +08:00
OTTO 705a9241b6 feat(运维监控模块): 完善接口:获取可视化面板Pod列表
补充Pod镜像以及副本数
2025-07-17 15:04:45 +08:00
otto 03b7c170e4 Merge pull request '完善接口:获取可视化面板Pod列表 补充Pod镜像以及副本数' (#935) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-17 15:04:23 +08:00
OTTO 0c0f27810e feat(运维监控模块): 完善接口:获取微服务应用功能列表
功能列表中增加启停选项:启动、停止、强制停止仅支持Deployment类型和Statefulsets类型应用
2025-07-17 14:14:13 +08:00
otto 4fd5c1cc4d Merge pull request '新增Pod删除接口' (#934) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-17 09:21:44 +08:00
OTTO 65a5b5d081 feat(运维监控模块): 新增Pod删除接口
1. 构建一个 deletePodBody JSON 对象,若 isForce 为 true,则设置强制删除参数。
2. 调用 RemoteRancherService.deletePod 发送删除请求,传入命名空间、Pod 名称、删除策略和用户身份令牌。
3. 判断响应状态码是否在 200-300 之间,若是则返回 true,表示删除成功。
2025-07-17 09:20:03 +08:00
otto e97b972f31 Merge pull request '新增获取微服务应用详情接口' (#933) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-16 10:35:25 +08:00
OTTO 828901d322 feat(运维监控模块): 新增获取微服务应用详情接口 2025-07-16 10:32:46 +08:00
OTTO 33185a1d0c feat(运维监控模块): 微服务管理相关接口迁移至微服务管理控制器中 2025-07-16 09:14:19 +08:00
otto 29b6b2bbae Merge pull request '微服务管理相关接口迁移至微服务管理控制器中' (#932) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-16 09:14:07 +08:00
OTTO 2883029641 feat(运维监控模块): 新增获取微服务运行状态监控数据接口
标注监控数据单位
2025-07-15 16:28:56 +08:00
otto 5eed69522c Merge pull request '标注监控数据单位' (#931) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-15 16:28:28 +08:00
otto c5d99153b8 Merge pull request '新增获取微服务运行状态监控数据接口' (#930) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-15 16:24:57 +08:00
OTTO 6f4a10f1cb feat(运维监控模块): 新增获取微服务运行状态监控数据接口
1. 设置参数:若未指定统计步长 step,则默认为10秒;设定查询时间范围为当前时间往前推1小时。
2. 并发查询:使用线程池并发执行6个监控指标的查询任务(CPU、内存、磁盘读写、网络收发)。
3. PromQL 查询:每个任务调用 Prometheus 接口执行对应的 PromQL 查询表达式,获取原始数据。
4. 数据转换:将原始 JSON 响应通过 getMetricCommonDataList 转换为统一的数据结构 List<MetricCommonData>。
5. 结果聚合:将各指标数据设置到 k8sPodContainerMetric 对象中,并等待所有任务完成后返回该对象。
6. 异常处理:若等待过程中发生中断异常,则记录日志并抛出业务异常。
2025-07-15 16:24:12 +08:00
otto c6f58972a6 Merge pull request '新增获取可视化面板POD列表接口' (#929) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-15 11:28:25 +08:00
OTTO 53795b1b88 feat(运维监控模块): 新增获取可视化面板POD列表接口
1. 获取微服务相关的命名空间列表;
2. 使用多线程并发获取各命名空间下的Pod列表,提升查询效率;
3. 补充Pod的详细信息设置,包括容器所属权(serviceAccountName)、容器端口列表(ports)以及存活时间(survivalTime)。
2025-07-15 11:19:39 +08:00
OTTO 1f653894cd feat(运维监控模块): 新增获取可视化面板POD列表接口
1. 获取微服务相关的命名空间列表;
2. 使用多线程并发获取各命名空间下的Pod列表,提升查询效率;
3. 补充Pod的详细信息设置,包括容器所属权(serviceAccountName)、容器端口列表(ports)以及存活时间(survivalTime)。
2025-07-15 11:15:35 +08:00
otto 5af3df62ee Merge pull request '修复获取功能应用列表报空指针异常的问题' (#928) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-14 17:31:18 +08:00
OTTO fde9893840 fix(运维监控模块): 修复获取功能应用列表报空指针异常的问题
调用Sentinel接口时,有概率返回结果中不包含success属性,所以在进行请求是否成功判断时,需要先判断是否存在该属性
2025-07-14 17:31:02 +08:00
otto e44f63a4c6 Merge pull request '修复获取集群内存使用率时数据异常的问题' (#927) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-11 17:21:06 +08:00
OTTO 6d47364cca fix(运维监控模块): 修复获取集群内存使用率时数据异常的问题
问题定位:当前集群内存使用率为pod容器内存使用大小/集群内存大小,但是kube_pod_container_resource_requests统计时会将pod预申请资源也计算进来,导致数据异常巨大(测试弹性伸缩时将cms服务内存申请设置为100000Gi)
修复方案:调整内存使用率计算方式,仅统计已正常运行在节点上的 pod 容器的内存使用大小,从而准确计算集群内存使用率,确保数据的准确性和可靠性,满足集群资源监控的实际需求。
2025-07-11 17:20:16 +08:00
OTTO 128a107f8f feat(运维监控模块): 完善微服务模块定制化编排功能
修复当POD未运行时重启次数为空的情况
2025-07-03 15:39:13 +08:00
otto 65aaa7df5b Merge pull request '修复当POD未运行时重启次数为空的情况' (#926) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-03 15:39:01 +08:00
OTTO b1da0ba006 feat(运维监控模块): 完善接口:获取微服务应用功能列表
获取应用网关管理功能时同步获取配置所属Group属性
2025-07-02 15:19:18 +08:00
otto ad3625a823 Merge pull request '完善微服务模块定制化编排功能' (#923) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-01 16:12:13 +08:00
OTTO 115a3cbe12 feat(运维监控模块): 完善微服务模块定制化编排功能
从Rancher获取应用POD的运行阶段,根据阶段来识别POD是否正在运行,当POD非运行阶段时,微服务大屏上获取POD运行指标调整为展示POD运行状态
2025-07-01 16:12:06 +08:00
OTTO c9d08ef3d5 feat(运维监控模块): 完善微服务模块定制化编排功能
1. 从Rancher获取应用POD的运行状态
2. 根据POD在Rancher中的状态值、是否错误转换为可读性更高的对象
2025-07-01 10:17:40 +08:00
otto 3f795fadb5 Merge pull request '完善微服务模块定制化编排功能' (#922) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-01 10:17:33 +08:00
OTTO 672476ba53 feat(运维监控模块): 新增接口:获取微服务应用功能列表 2025-07-01 09:05:58 +08:00
otto 1f55a7835e Merge pull request '完善接口:获取微服务应用功能列表' (#921) from otto/microservices:dev_monitoring into dev_monitoring 2025-07-01 09:05:52 +08:00
otto 0b16dbf195 Merge pull request 'feat(运维监控模块): 完善微服务模块定制化编排功能' (#920) from otto/microservices:dev_monitoring into dev_monitoring 2025-06-30 19:08:05 +08:00
OTTO 8111f6db61 feat(运维监控模块): 完善微服务模块定制化编排功能
计算应用创建时间和当前时间的差值按天、时、分、秒的方式进行返回,计算逻辑参考:
例如现在时间2025年6月30日12:00:00,提供的时间参数为2025年6月30日11:59:59,那么返回结果为1 秒钟;提供的时间参数为2025年6月30日10:30:00,那么返回结果为1.5 小时 ;提供的时间参数为2025年6月25日12:00:00,那么返回结果为5 天;
2025-06-30 18:52:10 +08:00
OTTO ae073b68bc feat(运维监控模块): 完善微服务模块定制化编排功能
获取应用的重启次数:通过Rancher接口获取应用下每个Pod容器的重启次数之和
2025-06-30 17:01:28 +08:00
OTTO 14072b1dff feat(运维监控模块): 完善微服务模块定制化编排功能
解析应用的Docker镜像并返回
2025-06-30 16:34:34 +08:00
OTTO 12eecde9c4 fix(运维监控):修复应用Pod列表找不到的问题
调整 podSelector 的匹配规则,不再仅依赖选择器的 Key 部分,而是保存并使用 POD 的完整选择器字段(如 "app=nginx")。在获取应用的 Labels 列表时,将该完整选择器通过 = 拆分为 Key 和 Value,再分别与 POD 的 Labels 进行精确匹配,从而提升选择器匹配的准确性与灵活性。
2025-06-30 16:33:19 +08:00
OTTO 66e1b9aca6 refactor(运维监控): 优化获取命名空间列表接口性能
1. 异步调用获取 K8S 集群中 Daemonsets、Deployment、Statefulsets 类型的应用列表及命名空间列表,通过 CountDownLatch 协调异步任务,统一汇总为应用基础数据;
2. 并发获取各命名空间下的 Pod 列表,并异步建立 Pod 与对应应用的关联关系,同样通过 CountDownLatch 汇聚响应结果,最终整合为完整的应用-命名空间-POD 映射数据返回。

关联任务:https://pm.osredm.com/osredm/projects/27/task
2025-06-30 14:15:23 +08:00
OTTO 93e56ed555 feat(运维监控模块): 完善微服务模块定制化编排功能
1. 从Rancher获取应用的运行状态
2. 根据应用在Rancher中的状态值、是否错误转换为可读性更高的对象
2025-06-30 11:48:17 +08:00
otto 13d5e7aa46 Merge pull request 'feat(运维监控模块): 新增接口:获取微服务应用功能列表' (#919) from otto/microservices:dev_monitoring into dev_monitoring 2025-06-26 11:13:44 +08:00
OTTO dc9a537feb feat(运维监控模块): 完善Prometheus指标注释 2025-06-23 20:25:53 +08:00
OTTO 3d5d6bfc6e Merge branch 'master' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-06-23 13:57:49 +08:00
OTTO bac9eeda5f feat(运维监控模块): 新增接口:获取微服务应用功能列表
这段代码为每个应用添加“启停管理”功能,设置对应的功能键和名称,并将命名空间和应用名称作为参数存入variables,最后将该功能对象加入功能列表中。
2025-06-19 19:49:27 +08:00
OTTO 18f89d1b8e feat(运维监控模块): 新增接口:获取微服务应用功能列表
1. REGISTRATION_CENTER:调用 Nacos 接口获取服务列表,匹配服务名与标签值,构造注册中心功能对象。
2. GATEWAY_MANAGEMENT:模糊匹配 Nacos 配置中的 dataId,若包含标签值则构造网关管理功能对象。
2025-06-19 19:02:12 +08:00
OTTO c3b5dcafba feat(运维监控模块): 新增接口:获取微服务应用功能列表
1. 调用网关远程接口获取 Sentinel 中注册的应用列表;
2. 检查返回结果是否有效且包含数据;
3. 遍历应用列表,匹配当前 Kubernetes 应用标签值(`k8sLabelValue`)与 Sentinel 中的 `app` 名称;
4. 若匹配成功,则创建 [ApplicationFunction] 对象,设置其键和名称,并封装 `app` 和 `appType` 到变量中;
5. 最后将构建好的功能对象加入结果列表 `applicationFunctionsList`。
2025-06-19 16:31:24 +08:00
OTTO a0aec9bf88 feat(gateway): 增加第三方工具 Token 获取的重试机制
- 为 Sentinel、Rancher、Nacos 和 Portainer 登录操作添加重试逻辑
- 引入 `retryTimes`配置项,用于控制重试次数
- 在获取 Token 失败时进行重试,提高系统稳定性
- 优化了异步请求和错误处理逻辑
2025-06-18 19:26:44 +08:00
OTTO a511f39a13 feat(运维监控模块): 新增接口:获取微服务应用功能列表
这段代码实现以下功能:

1. 调用 SkyWalking GraphQL 接口,查询指定分组下的所有服务;
2. 遍历返回的服务列表,匹配当前应用的 Kubernetes 标签(`k8sLabelValue`)与服务的 `shortName`;
3. 若匹配成功,则创建一个 [ApplicationFunction]对象,设置其功能类型(key/name)和服务 ID(作为变量),并加入结果列表。
2025-06-18 15:10:03 +08:00
OTTO 75f828b76c feat(运维监控模块): 新增接口:获取微服务应用功能列表
根据 Kubernetes 应用的标签(Labels)获取其对应的应用功能列表。

1. 调用Rancher接口获取指定应用的详细信息;
2. 将返回的 JSON 数据转换为 [RancherApplicationVo] 对象;
3. 提取该对象中的 Labels 信息;
4. 遍历 Labels 的 key,通过ApplicationFunctionType匹配应用功能类型;
5. 根据匹配到的功能类型(如链路追踪、流量控制等),执行相应的业务逻辑处理;
6. 最终返回应用功能列表。
2025-06-18 09:10:27 +08:00
wanjia9506 4b1042c25d Merge pull request '区块链交易信息查询' (#909) from liuhuazhong/microservices:feat_chievements_dev into feat_chievements_dev 2025-06-06 08:39:48 +08:00
wanjia9506 e9c41768e1 Merge pull request '资源库-数据产权子系统' (#907) from liuhuazhong/microservices:feat_chievements_dev into feat_chievements_dev 2025-06-03 11:50:12 +08:00
otto 15aa7016b9 Merge pull request 'qps数值保留两位小数,优化展示效果' (#893) from otto/microservices:dev_monitoring into dev_monitoring 2025-05-29 10:43:11 +08:00
OTTO f96cee3f4a fix(运维监控模块): qps数值保留两位小数,优化展示效果 2025-05-29 10:29:19 +08:00
otto 055b26ee29 Merge pull request '修复集群状态数据获取错误的问题' (#892) from otto/microservices:dev_monitoring into dev_monitoring 2025-05-29 09:45:40 +08:00
OTTO 368a45e8b2 fix(运维监控模块): 修复集群状态数据获取错误的问题
1. 修复获取微服务重启次数时为null的问题(由于重启次数计算的是一天内的重启次数所以数值存在小数,导致转换失败)

2. 修复获取qps失败的问题(指标使用错误、获取范围指标值时属性使用错误)
2025-05-29 09:45:11 +08:00
otto 2c5f0518ca Merge pull request '修改配置文件' (#879) from otto/microservices:dev_monitoring into dev_monitoring 2025-05-22 16:43:44 +08:00
OTTO 06fbbf7208 feat(运维监控模块): 修改配置文件 2025-05-22 16:43:09 +08:00
otto 6613950ff8 Merge pull request '完成运维监控模块开发' (#878) from otto/microservices:dev_monitoring into dev_monitoring 2025-05-22 15:41:30 +08:00
OTTO 8184eb2401 Merge branch 'master' of code.gitlink.org.cn:Gitlink/microservices into dev_monitoring 2025-05-22 15:40:31 +08:00
OTTO 213aee5587 feat(运维监控模块): 新增获取Node状态接口
1. 创建 [K8sNodeStatus] 实例并设置节点IP。
2. 构造PromQL查询语句,调用 Prometheus 接口获取节点CPU使用率:
   - 使用 [PromQLConstant.NODE_CPU_USAGE(nodeIp)] 生成查询语句。
   - 调用 `remoteGatewayService.getPrometheusQueryRes(...)` 发送请求。
   - 解析响应结果,若成功则提取CPU使用率数值并设置到对象中。
3. 同理,查询节点内存使用率并设置到对象中。
4. 返回封装好的 [K8sNodeStatus]对象。
2025-05-22 15:40:14 +08:00
OTTO 274af358dd feat(运维监控模块): 新增获取Pod容器状态接口
根据容器ID查询其在Kubernetes中的Pod的CPU和内存使用情况,并封装为[K8sPodContainerStatus]对象返回。具体逻辑如下:

1. 构造PromQL查询:通过 [PromQLConstant.POD_CPU_USAGE(containerID)] 构造CPU使用率查询语句。
2. 调用Prometheus接口:使用 `remoteGatewayService.getPrometheusQueryRes()` 向Prometheus发起查询请求,携带用户认证信息。
3. 解析CPU结果:若查询成功,从返回结果中提取Pod名称和CPU使用率并设置到返回对象中。
4. 构造内存查询并解析结果:类似步骤1-3,使用 [PromQLConstant.POD_MEMORY_USAGE(containerID)]查询内存使用字节数。
5. 单位转换:将内存由字节转为MiB或GiB,并格式化字符串设置进返回对象。
6. 返回结果:最终返回封装好的 [K8sPodContainerStatus] 对象。
2025-05-22 14:52:38 +08:00
OTTO 58bf8326c5 feat(运维监控模块): 增加 Pod 信息展示并优化应用列表
1. 在 K8sApplication 中添加 podSelector 和 podList 字段
2. 新增 K8sApplicationPod 类用于表示 Pod 信息
3. 修改 getNamespaceDetailList 方法支持获取 Pod 信息
4. 优化应用列表展示,增加 Pod选择器和 Pod 列表
2025-05-22 14:07:10 +08:00
OTTO 5e19e53b94 feat(运维监控模块):新增最近24小时应用接口请求次数查询接口
该方法用于获取 Kubernetes 集群中各个应用的请求次数,并封装成 [ApplicationRequestCountData]列表返回。其主要逻辑如下:

1. 获取 Nginx 服务端口别名映射:调用 Rancher 接口获取指定服务信息,提取各端口的名称(别名)并存入 `portMap`。
2. 查询 Prometheus 请求次数数据:通过 Prometheus 查询表达式 [APPLICATION_REQUEST_COUNT] 获取各应用的请求次数。
3. 组装结果数据:将 Prometheus 返回的端口号替换为对应别名,设置序号和请求次数,最终返回 [ApplicationRequestCountData] 列表。
2025-05-22 11:28:37 +08:00
OTTO 2be36ca12e feat(运维监控模块):新增集群每秒请求率(QPS)查询接口
该方法用于获取指定Kubernetes集群在过去一小时内的节点数量指标数据(QPS趋势),若无数据则填充默认值0,并按时间排序返回结果。具体逻辑如下:
1. 获取当前时间及一小时前的时间戳;
2. 调用Prometheus接口查询集群节点数指标数据;
3. 解析返回结果,封装为MetricCommonData对象列表;
4. 若无返回数据,则构造7个默认值为0的指标数据;
5. 按时间升序排序后返回结果。
2025-05-22 09:35:41 +08:00
OTTO ad405c912e feat(运维监控模块):新增获取集群状态数据接口
调用Prometheus接口查询Pod运行状态,并将相关状态翻译为中文后返回
2025-05-21 19:50:13 +08:00
wanjia9506 6ef1fafac8 Merge pull request '资源库-大屏展示' (#877) from liuhuazhong/microservices:feat_chievements_dev into feat_chievements_dev 2025-05-21 19:44:37 +08:00
OTTO 39d43e3d68 feat(运维监控模块):新增获取集群状态数据接口
优化方法命名
2025-05-21 19:07:35 +08:00
OTTO 1eefa65c32 refactor(运维监控模块): 重构监控模块集群状态数据获取方式
1. 将同步调用改为异步调用,使用线程池执行任务(原接口响应时间在2s左右,优化至700ms)
2. 优化了数据获取逻辑,提高了效率
3. 由于网关需要鉴权,调整了网关远程接口参数,使用 token 替代 source
4. 新增了线程池工厂和异步服务接口
2025-05-21 17:22:53 +08:00
OTTO c6b4a1bd80 feat(运维监控模块):新增获取集群状态数据接口
1. 从 Prometheus 查询集群节点总数、不可用节点数、CPU 使用率、内存使用率、Pod 使用率等指标。
2. 获取当天的总请求数、平均请求处理时间、请求成功率等网关性能数据。
3. 获取命名空间列表,并统计微服务应用数量及今日重启次数。
4. 将所有数据封装到 K8sClusterStatus 对象并返回。
2025-05-21 15:19:42 +08:00
OTTO 21ba1ce6bd refactor(运维监控模块):重构Rancher相关远程接口
- 新增 RemoteRancherService 接口,专门处理 Rancher 相关请求
- 移除 RemoteGatewayService 中的 Rancher 相关方法
- 更新 K8sNamespaceServiceImpl 中的远程调用方法,使用新的 RemoteRancherService
2025-05-21 08:42:23 +08:00
OTTO dd870b3cd3 feat(k8s): 添加获取应用 Pod IP 列表功能
该方法用于根据应用类型、命名空间和应用名称获取关联的 Pod 的 IP 地址列表。逻辑如下:

1. 调用 [getK8sApplicationDetail]获取应用详情;
2. 从应用的 relationships 中查找关联到 "pod" 的关系,并提取标签选择器;
3. 若选择器中包含 `app=` 标签,则提取应用名;
4. 调用 [getK8sPodListByNamespace] 获取命名空间下所有 Pod 列表;
5. 筛选出标签中 `app` 匹配的应用名的 Pod;
6. 返回这些 Pod 的 IP 地址列表;
7. 若无匹配数据则返回空列表。
2025-05-20 16:44:17 +08:00
OTTO 417d440549 feat(运维监控模块): 新增获取集群节点列表接口
1. 调用rancher接口获取节点列表
2. 将rancher返回对象转换为java对象
3. 从rancher的节点对象中摘取节点标识、节点名称、节点IP、节点角色以及是否就绪
2025-05-20 15:45:16 +08:00
OTTO 67b2e9ce7c fix(运维监控模块): 新增获取命名空间列表接口
修复StatefulSet类型应用的副本数无法获取的问题
2025-05-20 11:14:07 +08:00
OTTO 32b83749e2 feat(运维监控模块): 新增获取命名空间列表接口
- 修改 IK8sNamespaceService 接口,将返回类型从 GenericsTableDataInfo 改为 List
- 在 K8sApplication 中添加应用别名和命名空间字段
- 更新 K8sNamespaceController,适配新的接口返回类型
- 重构 K8sNamespaceServiceImpl,优化命名空间列表获取逻辑
- 在 RancherApplicationVo 中添加转换为 K8sApplication 的方法
- 更新 RancherNamespaceVo,简化 toK8sNamespace 方法
2025-05-16 16:55:09 +08:00
OTTO 010d5e5354 feat(运维监控模块): 新增获取命名空间列表接口
- 在 RemoteGatewayService 中新增 getK8sDeployList 和 getK8sStatefulList 方法
- 在 K8sNamespaceServiceImpl 中调用新方法获取 deployments 和 statefulsets 列表
- 重构 RancherDaemonsetsVo相关类,改为 RancherApplicationVo 以支持多种应用类型
- 优化命名空间列表获取逻辑,整合 daemonsets、deployments 和 statefulsets 数据
2025-05-16 15:51:02 +08:00
OTTO 94e0be0e71 feat(运维监控模块): 新增获取命名空间列表接口
- 在 RemoteGatewayService 中添加 getK8sDaemonsetsList 方法,调用rancher接口获取集群Daemonsets列表
- 新增Rancher Daemonsets对象以及Daemonsets的state和metadata对象,将Rancher接口返回的对象转换为Java对象
2025-05-16 15:28:02 +08:00
OTTO 2845d19aae feat(运维监控模块): 新增获取命名空间列表接口
- 在获取 K8s 命名空间列表后,增加筛选逻辑
- 遍历命名空间列表,仅保留包含 mon-key 标签的命名空间
2025-05-16 14:34:32 +08:00
OTTO 97b1c9437c feat(运维监控模块): 新增获取命名空间列表接口
- 在 RemoteGatewayService 中添加 getK8sNamespaceList 方法,调用rancher接口获取集群命名空间列表
- 新增Rancher命名空间对象,将Rancher接口返回的对象转换为Java对象
- 新增 K8sNamespace 和 K8sApplication 模型类
-
2025-05-16 14:34:04 +08:00
OTTO b0f9e06e18 feat(运维监控模块): 新增获取命名空间列表接口
- 在 RemoteGatewayService 中添加 getK8sNamespaceList 方法,调用rancher接口获取集群命名空间列表
- 新增Rancher命名空间对象,将Rancher接口返回的对象转换为Java对象
- 新增 K8sNamespace 和 K8sApplication 模型类
-
2025-05-16 14:33:57 +08:00
OTTO f58af4b0b5 feat(网关服务): 集成 Rancher 登录
新增Rancher免登录转发逻辑实现:
1. 构造登录Json对象调用Rancher登录接口获取Token
2. 将Token放入Redis缓存中,缓存失效时间为Rancher Token失效时间减一小时
3. 携带Token转发用户请求
2025-05-15 16:55:21 +08:00
OTTO 78cbd4b0ee Merge branch 'master' of https://gitlink.org.cn/Gitlink/microservices into dev_monitoring
# Conflicts:
#	microservices-modules/pom.xml
2025-05-13 14:52:33 +08:00
OTTO fca8490bdd feat(运维监控模块): 添加运维监控模块并更新项目管理模块描述
- 新增运维监控模块(microservices-modules-mon)
- 更新项目管理模块(microservices-modules-pms)的描述
2025-05-13 14:14:24 +08:00
otto 616bf1229f Merge pull request '数据管理模块-成果管理开发' (#865) from liuhuazhong/microservices:feat_chievements_dev into feat_chievements_dev 2025-04-28 11:34:33 +08:00
144 changed files with 7436 additions and 370 deletions

View File

@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件服务
@ -34,6 +35,17 @@ public interface RemoteFileService {
@RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* Base64 图片上传HashMap 版本
*/
@PostMapping(value = "/upload/base64")
R<SysFileInfo> uploadBase64(
@RequestBody Map<String, Object> params,
@RequestParam("type") String type,
@RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source
);
/**
* 根据文件Id列表获取文件列表
*

View File

@ -2,13 +2,13 @@ package com.microservices.system.api;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.ServiceNameConstants;
import com.microservices.common.core.constant.TokenConstants;
import com.microservices.system.api.factory.RemoteGatewayFallbackFactory;
import com.microservices.system.api.model.GraphqlQueryVo;
import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@ -28,6 +28,14 @@ public interface RemoteGatewayService {
@PostMapping("/sentinel/auth/login")
Response loginSentinel(@RequestParam("username") String username, @RequestParam("password") String password);
/**
* 登录Rancher
*
* @return 响应
*/
@PostMapping("/rancher/v3-public/localProviders/local?action=login")
Response loginRancher(@RequestBody JSONObject loginBody);
/**
* 登录Nacos
*
@ -43,4 +51,45 @@ public interface RemoteGatewayService {
*/
@PostMapping("/portainer/api/auth")
Response loginPortainer(@RequestBody JSONObject loginBody);
@GetMapping("/prometheus/api/v1/query")
JSONObject getPrometheusQueryRes(@RequestParam("query") String query,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/prometheus/api/v1/query_range")
JSONObject getPrometheusQueryRangeRes(@RequestParam("query") String query,
@RequestParam("step") Long step,
@RequestParam("start") Long start,
@RequestParam("end") Long end,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@PostMapping("/skywalking/graphql")
JSONObject executeSkywalkingGql(@RequestBody GraphqlQueryVo gqlBody);
@GetMapping("/sentinel/app/briefinfos.json")
JSONObject getSentinelAppList(@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/nacos/v1/ns/catalog/services?pageNo=1&pageSize=999")
JSONObject getNacosServiceList(@RequestParam("serviceNameParam") String serviceNameParam, @RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/nacos/v1/cs/configs?types=yaml&search=blur&group=&pageNo=1&pageSize=10")
JSONObject getGatewayNacosConfigList(@RequestParam("dataId") String dataId, @RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/nacos/v1/cs/listener")
JSONObject getNacosConfigList(@RequestParam("ip") String ip, @RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/nacos/v1/cs/configs")
String getNacosConfigDetail(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestHeader(TokenConstants.AUTHENTICATION) String token);
@PostMapping(value = "/nacos/v1/cs/configs", consumes = {"application/x-www-form-urlencoded"})
Response updateNacosConfig(Map<String, ?> formParams, @RequestHeader(TokenConstants.AUTHENTICATION) String userKey);
@PostMapping("/es/{esIndex}/_search")
JSONObject executeElasticsearchQuery(@PathVariable("esIndex") String esIndex, @RequestBody JSONObject esBody);
@PostMapping("/es/{esIndex}/_doc/{docId}/_update")
JSONObject executeElasticsearchUpdate(@PathVariable("esIndex") String esIndex,@PathVariable("docId") String docId, @RequestBody JSONObject esBody);
@PostMapping("/gns/notification/gitlink")
JSONObject sendNotification(@RequestBody JSONObject body);
}

View File

@ -0,0 +1,124 @@
package com.microservices.system.api;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.Constants;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.ServiceNameConstants;
import com.microservices.common.core.constant.TokenConstants;
import com.microservices.system.api.factory.RemoteGatewayFallbackFactory;
import com.microservices.system.api.factory.RemoteRancherFallbackFactory;
import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 网关
*
* @author microservices
*/
@Component
@FeignClient(contextId = "RemoteRancherService", value = ServiceNameConstants.GATEWAY_SERVICE, fallbackFactory = RemoteRancherFallbackFactory.class)
public interface RemoteRancherService {
@GetMapping("/rancher/v3/clusters/{cluster_id}/namespaces")
Response getK8sNamespaceList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/apps.daemonsets?exclude=metadata.managedFields")
Response getK8sDaemonsetsList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/apps.deployments?exclude=metadata.managedFields")
Response getK8sDeployList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/apps.statefulsets?exclude=metadata.managedFields")
Response getK8sStatefulList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/nodes?exclude=metadata.managedFields")
Response getK8sClusterNodeList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/{fullKind}/{namespace}/{applicationName}")
Response getK8sApplicationDetail(@PathVariable("cluster_id") String cluster_id,
@PathVariable("fullKind") String fullKind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/pods/{namespace}")
Response getK8sPodListByNamespace(@PathVariable("cluster_id") String cluster_id,
@PathVariable("namespace") String namespace,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/pods")
Response getK8sPodList(@PathVariable("cluster_id") String cluster_id,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/services/{nginxApplicationId}")
Response getK8sServiceByApplicationId(@PathVariable("cluster_id") String cluster_id,
@PathVariable("nginxApplicationId") String nginxApplicationId,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@DeleteMapping("/rancher/v1/pods/{namespace}/{podName}")
Response deletePod(@PathVariable("namespace") String namespace,
@PathVariable("podName") String podName,
@RequestBody JSONObject deletePodBody,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@PutMapping("/rancher/v1/{fullKind}/{namespace}/{applicationName}")
Response updateK8sApplication(@PathVariable("fullKind") String fullKind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestBody JSONObject applicationDetail,
@RequestHeader(TokenConstants.AUTHENTICATION) String userKey);
@PostMapping(value = "/rancher/v1/{fullKind}", consumes = {Constants.YAML_CONTENT_TYPE})
Response createK8sApplication(@PathVariable("fullKind") String fullName,
@RequestBody String yamlContent,
@RequestHeader(TokenConstants.AUTHENTICATION) String userKey);
@PostMapping(value = "/rancher/v1/management.cattle.io.clusters/{cluster_id}?action=apply")
Response applyK8sYaml(@PathVariable("cluster_id") String cluster_id,
@RequestBody JSONObject yamlContent,
@RequestHeader(TokenConstants.AUTHENTICATION) String userKey);
@GetMapping("/rancher/apis/apps/v1/namespaces/{namespace}/{kindName}/{applicationName}")
Response getApplicationYamlFile(@PathVariable("kindName") String kindName,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@PutMapping(value = "/rancher/v1/{fullKind}/{namespace}/{applicationName}", consumes = {Constants.YAML_CONTENT_TYPE})
Response editK8sApplication(@PathVariable("fullKind") String fullKind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestBody String yamlContent,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@DeleteMapping("/rancher/v1/{fullKind}/{namespace}/{applicationName}")
Response deleteApplication(@PathVariable("fullKind") String fullKind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/v1/configmaps/{namespace}")
Response getK8sConfigmapByNamespace(@PathVariable("namespace") String namespace,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/v1/secrets/{namespace}")
Response getK8sSecretByNamespace(@PathVariable("namespace") String namespace,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@GetMapping("/rancher/k8s/clusters/{cluster_id}/v1/services/{namespace}")
Response getK8sServiceByNamespace(@PathVariable("cluster_id") String cluster_id,
@PathVariable("namespace") String namespace,
@RequestHeader(TokenConstants.AUTHENTICATION) String token);
@DeleteMapping("/rancher/v1/services/{serviceId}")
Response deleteK8sService(@PathVariable("serviceId") String serviceId,@RequestHeader(TokenConstants.AUTHENTICATION) String token);
}

View File

@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件服务降级处理
@ -36,6 +37,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
return R.fail("上传文件失败:" + throwable.getMessage());
}
@Override
public R<SysFileInfo> uploadBase64(Map<String, Object> params, String type, String hierarchy, String source) {
return R.fail("上传base64文件失败:" + throwable.getMessage());
}
@Override
public R<List<SysFileInfo>> getFileList(String fileIds) {
return R.fail("获取文件列表失败:" + throwable.getMessage());

View File

@ -1,7 +1,9 @@
package com.microservices.system.api.factory;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.system.api.RemoteGatewayService;
import com.microservices.system.api.model.GraphqlQueryVo;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -25,7 +27,12 @@ public class RemoteGatewayFallbackFactory implements FallbackFactory<RemoteGatew
return new RemoteGatewayService() {
@Override
public Response loginSentinel(String password, String username) {
public Response loginSentinel(String username, String password) {
return null;
}
@Override
public Response loginRancher(JSONObject loginBody) {
return null;
}
@ -36,7 +43,66 @@ public class RemoteGatewayFallbackFactory implements FallbackFactory<RemoteGatew
@Override
public Response loginPortainer(JSONObject loginBody) {
System.out.println(throwable.getMessage());
return null;
}
@Override
public JSONObject getPrometheusQueryRes(String query, String token) {
return null;
}
@Override
public JSONObject getPrometheusQueryRangeRes(String query, Long step, Long start, Long end, String token) {
return null;
}
@Override
public JSONObject executeSkywalkingGql(GraphqlQueryVo gqlBody) {
return null;
}
@Override
public JSONObject getSentinelAppList(String token) {
return null;
}
@Override
public JSONObject getNacosServiceList(String serviceNameParam, String token) {
return null;
}
@Override
public JSONObject getGatewayNacosConfigList(String dataId, String token) {
return null;
}
@Override
public JSONObject getNacosConfigList(String ip, String token) {
return null;
}
@Override
public String getNacosConfigDetail(String dataId, String group, String token) {
return null;
}
@Override
public Response updateNacosConfig(Map<String, ?> formParams, String userKey) {
return null;
}
@Override
public JSONObject executeElasticsearchQuery(String esIndex, JSONObject esBody) {
return null;
}
@Override
public JSONObject executeElasticsearchUpdate(String esIndex, String docId, JSONObject esBody) {
return null;
}
@Override
public JSONObject sendNotification(JSONObject body) {
return null;
}
};

View File

@ -0,0 +1,133 @@
package com.microservices.system.api.factory;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.system.api.RemoteGatewayService;
import com.microservices.system.api.RemoteRancherService;
import com.microservices.system.api.utils.FeignUtils;
import feign.FeignException;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 用户服务降级处理
*
* @author microservices
*/
@Component
public class RemoteRancherFallbackFactory implements FallbackFactory<RemoteRancherService> {
private static final Logger log = LoggerFactory.getLogger(RemoteRancherFallbackFactory.class);
@Override
public RemoteRancherService create(Throwable throwable) {
log.error("Rancher服务调用失败:{}", throwable.getMessage());
return new RemoteRancherService() {
@Override
public Response getK8sNamespaceList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sDaemonsetsList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sDeployList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sStatefulList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sClusterNodeList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sApplicationDetail(String cluster_id, String fullKind, String namespace, String applicationName, String token) {
return null;
}
@Override
public Response getK8sPodListByNamespace(String cluster_id, String namespace, String token) {
return null;
}
@Override
public Response getK8sPodList(String cluster_id, String token) {
return null;
}
@Override
public Response getK8sServiceByApplicationId(String cluster_id, String nginxApplicationId, String token) {
return null;
}
@Override
public Response deletePod(String namespace, String podName, JSONObject deletePodBody, String token) {
return null;
}
@Override
public Response updateK8sApplication(String fullKind, String namespace, String applicationName, JSONObject applicationDetail, String userKey) {
return null;
}
@Override
public Response createK8sApplication(String fullName, String yamlContent, String userKey) {
return null;
}
@Override
public Response applyK8sYaml(String cluster_id, JSONObject yamlContent, String userKey) {
return null;
}
@Override
public Response getApplicationYamlFile(String kindName, String namespace, String applicationName, String token) {
return null;
}
@Override
public Response editK8sApplication(String fullKind, String namespace, String applicationName, String yamlContent, String token) {
return null;
}
@Override
public Response deleteApplication(String fullKind, String namespace, String applicationName, String token) {
return null;
}
@Override
public Response getK8sConfigmapByNamespace(String namespace, String token) {
return null;
}
@Override
public Response getK8sSecretByNamespace(String namespace, String token) {
return null;
}
@Override
public Response getK8sServiceByNamespace(String cluster_id, String namespace, String token) {
return null;
}
@Override
public Response deleteK8sService(String serviceId, String token) {
return null;
}
};
}
}

View File

@ -0,0 +1,11 @@
package com.microservices.system.api.model;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
@Data
public class GraphqlQueryVo {
String operationName;
String query;
JSONObject variables;
}

View File

@ -3,20 +3,24 @@ package com.microservices.system.api.utils;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import feign.Response;
/**
* @author otto
*/
public class FeignUtils {
public static <T> T getReturnData(R<T> result){
public static <T> T getReturnData(R<T> result) {
if (StringUtils.isNull(result)) {
return null;
}
if (R.FAIL == result.getCode())
{
if (R.FAIL == result.getCode()) {
throw new ServiceException(result.getMsg());
}
return result.getData();
}
public static Boolean isSuccess(Response response) {
return response != null && response.status() >= 200 && response.status() < 300;
}
}

View File

@ -0,0 +1,62 @@
package com.microservices.system.api.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
public class StandardMultipartFile implements MultipartFile {
private final byte[] content;
private final String name;
private final String originalFilename;
private final String contentType;
public StandardMultipartFile(byte[] content, String name, String originalFilename, String contentType) {
this.content = content != null ? content : new byte[0];
this.name = name;
this.originalFilename = originalFilename;
this.contentType = contentType;
}
@Override
public String getName() {
return name;
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() throws IOException {
return content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
try (FileOutputStream fos = new FileOutputStream(dest)) {
fos.write(content);
}
}
}

View File

@ -6,3 +6,4 @@ com.microservices.system.api.factory.RemoteCmsFallbackFactory
com.microservices.system.api.factory.RemoteZoneFallbackFactory
com.microservices.system.api.factory.RemotePmsFallbackFactory
com.microservices.system.api.factory.RemoteGatewayFallbackFactory
com.microservices.system.api.factory.RemoteRancherFallbackFactory

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9110
port: 8080
# Spring
spring:

View File

@ -213,6 +213,11 @@ public class CacheConstants {
*/
public final static String SENTINEL_TOKEN = "sentinel_token";
/**
* Rancher Token缓存Key
*/
public final static String RANCHER_TOKEN = "rancher_token";
/**
* Nacos Token缓存Key

View File

@ -5,8 +5,7 @@ package com.microservices.common.core.constant;
*
* @author microservices
*/
public class Constants
{
public class Constants {
/**
* UTF-8 字符集
*/
@ -125,6 +124,10 @@ public class Constants
* JSON类型
*/
public static final String JSON_CONTENT_TYPE = "application/json";
/**
* Yaml类型
*/
public static final String YAML_CONTENT_TYPE = "application/yaml";
/**
* 文本类型
*/

View File

@ -37,6 +37,11 @@ public class SecurityConstants
*/
public static final String USER_KEY = "user_key";
/**
* 用户Token
*/
public static final String TOKEN = "token";
/**
* 组织ID
*/

View File

@ -22,9 +22,16 @@ public class TokenConstants {
* Sentinel令牌标识
*/
public static final String Sentinel_Token_Key = "sentinel_dashboard_cookie";
/**
* Rancher令牌标识
*/
public static final String Rancher_Cookie_Token_Key = "R_SESS";
/**
* Portainer令牌标识
*/
public static final String Portainer_Token_Key = "portainer_api_key";
/**
* Sentinel令牌标识
* Nacos令牌标识
*/
public static final String Nacos_Token_Key = "Accesstoken";
/**

View File

@ -1,5 +1,6 @@
package com.microservices.common.core.threadPool;
import com.microservices.common.core.constant.TokenConstants;
import com.microservices.common.core.context.SecurityContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,6 +38,9 @@ public class ThreadPoolExecutorWrap extends ThreadPoolExecutor {
@Override
public void run() {
try {
if(contextValue != null&&contextValue.containsKey("user_key")){
contextValue.put(TokenConstants.AUTHENTICATION, contextValue.get("user_key"));
}
SecurityContextHolder.setLocalMap(contextValue);
// 用户任务逻辑
task.run();

View File

@ -13,17 +13,19 @@ import com.microservices.common.core.utils.reflect.ReflectUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
@ -1458,4 +1460,288 @@ public class ExcelUtil<T>
}
return method;
}
// 存储Excel中的图片key: 行号_列号value: MultipartFile
private final Map<String, MultipartFile> excelImageMap = new HashMap<>();
/**
* 获取解析后的Excel图片映射表行号_列号 -> MultipartFile
* <p>对外返回不可修改的Map避免外部篡改同时让静态检查识别到该集合被查询</p>
* @return 图片映射表
*/
public Map<String, MultipartFile> getExcelImageMap() {
// 返回不可修改的Map提升代码安全性同时让静态检查识别到集合被读取
return Collections.unmodifiableMap(excelImageMap);
}
/**
* 解析Excel中的图片并转换为MultipartFile存入{@link #excelImageMap}
* @param sheet Excel工作表
* @throws IOException 解析图片失败时抛出
*/
private void parseExcelImages(Sheet sheet) throws IOException {
if (wb instanceof XSSFWorkbook) {
parseXSSFImages((XSSFSheet) sheet);
} else if (wb instanceof HSSFWorkbook) {
parseHSSFImages((HSSFWorkbook) wb, sheet);
}
}
/**
* 解析XSSF格式xlsx图片
*/
private void parseXSSFImages(XSSFSheet sheet) {
XSSFDrawing drawing = sheet.createDrawingPatriarch();
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
if (shape instanceof XSSFPicture) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFPictureData pictureData = picture.getPictureData();
XSSFClientAnchor anchor = picture.getPreferredSize();
// 获取图片所在单元格位置
int row = anchor.getRow1();
int col = anchor.getCol1();
String cellKey = row + "_" + col;
// 转换为MultipartFile并存储
MultipartFile multipartFile = bytesToMultipartFile(pictureData.getData(), "xlsx_image.png");
excelImageMap.put(cellKey, multipartFile);
}
}
}
/**
* 解析HSSF格式xls图片
*/
private void parseHSSFImages(HSSFWorkbook workbook, Sheet sheet) {
List<HSSFPictureData> pictureDataList = workbook.getAllPictures();
for (int i = 0; i < pictureDataList.size(); i++) {
HSSFPictureData pictureData = pictureDataList.get(i);
HSSFPatriarch patriarch = (HSSFPatriarch) sheet.createDrawingPatriarch();
List<HSSFShape> shapes = patriarch.getChildren();
for (HSSFShape shape : shapes) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
if (picture.getPictureIndex() == i) {
HSSFClientAnchor anchor = picture.getClientAnchor();
int row = anchor.getRow1();
int col = anchor.getCol1();
String cellKey = row + "_" + col;
// 转换为MultipartFile并存储
MultipartFile multipartFile = bytesToMultipartFile(pictureData.getData(), "xls_image.png");
excelImageMap.put(cellKey, multipartFile);
break;
}
}
}
}
}
/**
* 自定义MultipartFile实现通用版无外部依赖
*/
public static class CustomMultipartFile implements MultipartFile {
private final byte[] content;
private final String filename;
private final String contentType;
public CustomMultipartFile(byte[] content, String filename, String contentType) {
this.content = content;
this.filename = filename;
this.contentType = contentType;
}
@Override
public String getName() {
return "file";
}
@Override
public String getOriginalFilename() {
return filename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content == null || content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() throws IOException {
return content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content);
}
@Override
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
try (java.io.OutputStream os = new java.io.FileOutputStream(dest)) {
os.write(content);
}
}
}
/**
* 将字节数组转换为MultipartFile核心工具方法
*/
public MultipartFile bytesToMultipartFile(byte[] bytes, String fileName) {
try {
String suffix = "png";
if (fileName != null && fileName.contains(".")) {
suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
}
String finalFileName = UUID.randomUUID().toString() + "." + suffix;
String contentType = "image/" + suffix;
return new CustomMultipartFile(bytes, finalFileName, contentType);
} catch (Exception e) {
throw new RuntimeException("转换图片为MultipartFile失败", e);
}
}
// 导入包含图片的excel
public List<T> importExcelWithImages(String sheetName, InputStream is, int titleNum) throws Exception {
this.type = Type.IMPORT;
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null) {
throw new IOException("文件sheet不存在");
}
// 1. 解析Excel中的图片生成imageMap行号_列号 -> MultipartFile
parseExcelImages(sheet);
int rows = sheet.getLastRowNum();
if (rows > 0) {
// ========== 参考原有逻辑构建表头列名和下标映射 ==========
Map<String, Integer> cellMap = new HashMap<>();
Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
Cell cell = heard.getCell(i);
if (StringUtils.isNotNull(cell)) {
String columnName = this.getCellValue(heard, i).toString().trim();
cellMap.put(columnName, i);
} else {
cellMap.put(null, i);
}
}
// ========== 参考原有逻辑构建字段映射通过Excel注解匹配列名 ==========
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
for (Object[] objects : fields) {
Excel attr = (Excel) objects[1];
Integer column = cellMap.get(attr.name());
if (column != null) {
fieldsMap.put(column, objects);
}
}
// 找到图片列的下标关键和Excel模板的列名对应
Integer imageColumnIndex = cellMap.get("图片");
// ========== 遍历数据行参考原有逻辑 ==========
for (int i = titleNum + 1; i <= rows; i++) {
Row row = sheet.getRow(i);
if (isRowEmpty(row)) {
continue;
}
T entity = null;
// 生成当前行的图片key行号_图片列下标i是数据行号对应Excel的行
String imageCellKey = i + "_" + imageColumnIndex;
// ========== 参考原有逻辑遍历字段映射赋值 ==========
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
Integer columnIndex = entry.getKey();
Object val = this.getCellValue(row, columnIndex);
// 如果不存在实例则新建
entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field和Excel注解
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// ========== 关键修改如果是图片替换值为图片key ==========
if (imageColumnIndex != null && columnIndex.equals(imageColumnIndex)) {
val = imageCellKey; // 将单元格原始值替换为图片key如1_12
}
// ========== 原有类型转换逻辑完全复用 ==========
Class<?> fieldType = field.getType();
if (String.class == fieldType) {
String s = Convert.toStr(val);
if (StringUtils.endsWith(s, ".0")) {
val = StringUtils.substringBefore(s, ".0");
} else {
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat)) {
val = parseDateToStr(dateFormat, val);
} else {
val = Convert.toStr(val);
}
}
} else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
val = Convert.toInt(val);
} else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
val = Convert.toLong(val);
} else if (Double.TYPE == fieldType || Double.class == fieldType) {
val = Convert.toDouble(val);
} else if (Float.TYPE == fieldType || Float.class == fieldType) {
val = Convert.toFloat(val);
} else if (BigDecimal.class == fieldType) {
val = Convert.toBigDecimal(val);
} else if (Date.class == fieldType) {
if (val instanceof String) {
val = DateUtils.parseDate(val);
} else if (val instanceof Double) {
val = DateUtil.getJavaDate((Double) val);
}
} else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
val = Convert.toBool(val, false);
}
// ========== 原有反射赋值逻辑完全复用 ==========
if (StringUtils.isNotNull(fieldType)) {
String propertyName = field.getName();
if (StringUtils.isNotEmpty(attr.targetAttr())) {
propertyName = field.getName() + "." + attr.targetAttr();
} else if (StringUtils.isNotEmpty(attr.readConverterExp())) {
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
} else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
val = dataFormatHandlerAdapter(val, attr);
}
// 使用原有工具类反射赋值核心替换自定义setEntityFieldValue
ReflectUtils.invokeSetter(entity, propertyName, val);
}
}
if (entity != null) {
list.add(entity);
}
}
}
return list;
}
}

View File

@ -51,7 +51,7 @@ public class GenericsTableDataInfo<T> implements Serializable {
/**
* 面包屑数据
*/
@ApiModelProperty("面包屑数据")
@ApiModelProperty(value = "面包屑数据",hidden = true)
@JsonInclude(JsonInclude.Include.NON_NULL) //为空时隐藏
private List<Breadcrumb> breadcrumb;

View File

@ -79,7 +79,7 @@ public class GitLinkRequestHelper {
HashMap<String, String> headers = prepareAuthenticationHeaders(gitLinkRequestUrl);
String requestId = UUID.randomUUID().toString();
String path = gitLinkRequestUrl.getPath();
logger.info("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}"
logger.debug("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}"
, requestId
, requestType
, gitLinkUrl
@ -111,7 +111,7 @@ public class GitLinkRequestHelper {
default:
throw new ServiceException("不支持该请求方式:" + requestType.name());
}
logger.info("[{}]请求响应为:{}"
logger.debug("[{}]请求响应为:{}"
, requestId
, result);
} catch (Exception e) {

View File

@ -34,6 +34,12 @@
<artifactId>microservices-common-redis</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,47 @@
package com.microservices.common.security.handler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.domain.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
BlockException ex) throws Exception {
R<?> body;
if (ex instanceof FlowException) { // 限流
body = R.fail(429, "请求过于频繁,请稍后重试");
} else if (ex instanceof DegradeException) { // 熔断
body = R.fail(500, "服务暂时不可用,请稍后重试");
} else if (ex instanceof ParamFlowException) { // 热点参数限流
body = R.fail(400, "热点参数限流");
} else if (ex instanceof AuthorityException) { // 授权规则
body = R.fail(403, "无权限访问该资源");
} else if (ex instanceof SystemBlockException) { // 系统规则
body = R.fail(500, "系统规则限制,无法访问");
} else { // 兜底
body = R.fail(500, "服务繁忙,请稍后重试");
}
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSONObject.toJSONString(body));
}
}

View File

@ -4,3 +4,4 @@ com.microservices.common.security.service.impl.RequestCustomServiceImpl
com.microservices.common.security.aspect.PreAuthorizeAspect
com.microservices.common.security.aspect.InnerAuthAspect
com.microservices.common.security.handler.GlobalExceptionHandler
com.microservices.common.security.handler.SentinelBlockExceptionHandler

View File

@ -90,7 +90,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
try {
claims = JwtUtils.parseToken(token);
} catch (Exception e) {
log.error("令牌解析失败:{}", e.getMessage());
log.error("[鉴权异常处理]令牌解析失败:{}", e.getMessage());
return unauthorizedResponse(exchange, "令牌格式不正确!");
}
if (claims == null) {
@ -139,7 +139,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
);
redisService.setCacheObject(redisKey, feignResult);
} catch (Exception e) {
log.error("获取用户【{}】身份列表时发生异常:{}", username, e.getMessage());
log.error("[鉴权异常处理]获取用户【{}】身份列表时发生异常:{}", username, e.getMessage());
}
}
}
@ -167,7 +167,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
}
return genCookieByToken(token);
} catch (Exception e) {
log.error("检查该gitlink token【{}】是否登录时发生异常:{}", token, e.getMessage());
log.error("[鉴权异常处理]检查该token【{}】是否登录时发生异常:{}", token, e.getMessage());
return null;
}
}
@ -215,11 +215,11 @@ public class AuthFilter implements GlobalFilter, Ordered {
try {
feignResult = future.get();
} catch (Exception e) {
log.error("异步请求获取本系统Token出错{}", e.getMessage());
log.error("[鉴权异常处理]异步请求获取本系统Token出错{}", e.getMessage());
throw new ServiceException("内部服务错误");
}
if (StringUtils.isNull(feignResult) || R.FAIL == feignResult.getCode()) {
log.error("Token获取出错{}", feignResult.getMsg());
log.error("[鉴权异常处理]Token获取出错{}", feignResult.getMsg());
throw new ServiceException("内部服务错误");
}
if (R.SUCCESS == feignResult.getCode()) {

View File

@ -42,6 +42,7 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
String lowerUrl = request.getURI().getPath().toLowerCase();
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
// 当请求为Nacos时Nacos返回的所有非JSON响应都会被自动转换为结构化的错误信息同时将HTTP状态码设置为200适合用于规范化微服务架构的响应格式
if (lowerUrl.startsWith("/nacos")) {
@ -53,7 +54,7 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
return super.writeWith(body);
}
if (status == HttpStatus.FORBIDDEN) {
log.error("nacos鉴权异常已清理redis中nacos token");
log.error("[网关转发异常处理]nacos鉴权异常已清理redis中nacos token");
// 设置新的状态码为 200
setStatusCode(HttpStatus.OK);
// 设置响应内容类型为 JSON
@ -125,7 +126,7 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
}
synchronousSink.complete();
}).flatMap(resStr -> {
log.error("Portainer响应处理异常异常信息{}", resStr);
log.error("[网关转发异常处理]Portainer响应处理异常异常信息{}", resStr);
originalResponse.setStatusCode(HttpStatus.OK);
byte[] bytes;
if (JSON.isValid(resStr)) {
@ -133,7 +134,7 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
if (resJson.containsKey("message")) {
String message = resJson.getString("message");
if (StringUtils.isNotBlank(message) && message.contains("A valid authorisation token is missing")) {
log.error("Portainer鉴权异常已清理redis中Portainer token");
log.error("[网关转发异常处理]Portainer鉴权异常已清理redis中Portainer token");
//token失效清除redis缓存
redisService.deleteObject(CacheConstants.PORTAINER_TOKEN);
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.RETRY_ERROR));
@ -160,12 +161,12 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
HttpStatus status = getStatusCode() != null ? getStatusCode() : HttpStatus.INTERNAL_SERVER_ERROR;
HttpStatus status = getStatusCode();
if (!status.isError()) {
return super.writeWith(body);
}
if (status == HttpStatus.UNAUTHORIZED) {
log.error("sentinel鉴权异常已清理redis中sentinel token");
log.error("[网关转发异常处理]sentinel鉴权异常已清理redis中sentinel token");
// 设置新的状态码为 200
setStatusCode(HttpStatus.OK);
// 设置响应内容类型为 JSON
@ -192,7 +193,7 @@ public class GlobalResponseFilter implements GlobalFilter, Ordered {
}
synchronousSink.complete();
}).flatMap(resStr -> {
log.error("Sentinel响应处理异常异常信息{}", resStr);
log.error("[网关转发异常处理]Sentinel响应处理异常异常信息{}", resStr);
byte[] bytes;
bytes = JSON.toJSONBytes(AjaxResult.error(ExceptionMsgConstants.SYSTEM_EXEC_ERROR));
setStatusCode(HttpStatus.OK);

View File

@ -53,8 +53,14 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
public String portainerUsername;
@Value("${thirdPartyTools.portainer.auth.password:}")
public String portainerPassword;
@Value("${thirdPartyTools.rancher.auth.username:}")
public String rancherUsername;
@Value("${thirdPartyTools.rancher.auth.password:}")
public String rancherPassword;
@Value("${thirdPartyTools.portainer.endpoints:}")
public Integer portainerEndpoints;
@Value("${thirdPartyTools.retryTimes:1}")
public Integer retryTimes;
ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor;
@Lazy
@Autowired
@ -83,27 +89,81 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
if (redisService.hasKey(CacheConstants.SENTINEL_TOKEN)) {
sentinelCookie = redisService.getCacheObject(CacheConstants.SENTINEL_TOKEN);
} else {
// 网关采用异步架构所以此处需要通过异步请求获取Sentinel Token
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword));
try {
Response response = future.get(1, TimeUnit.SECONDS);
Map<String, Collection<String>> header = response.headers();
if (header != null && header.containsKey("set-cookie")) {
sentinelCookie = header.get("set-cookie").iterator().next();
//Sentinel登录状态默认30分钟失效将Sentinel Token缓存过期时间调整为28分钟
redisService.setCacheObject(CacheConstants.SENTINEL_TOKEN, sentinelCookie, 28L, TimeUnit.MINUTES);
for (int i = 0; i < retryTimes; i++) {
// 网关采用异步架构所以此处需要通过异步请求获取Sentinel Token
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword));
try {
Response response = future.get(3, TimeUnit.SECONDS);
Map<String, Collection<String>> header = response.headers();
if (header != null && header.containsKey("set-cookie")) {
sentinelCookie = header.get("set-cookie").iterator().next();
//Sentinel登录状态默认30分钟失效将Sentinel Token缓存过期时间调整为28分钟
redisService.setCacheObject(CacheConstants.SENTINEL_TOKEN, sentinelCookie, 28L, TimeUnit.MINUTES);
}
} catch (TimeoutException e) {
log.error("[网关转发异常处理]获取Sentinel Token超时");
} catch (InterruptedException | ExecutionException e) {
log.error("[网关转发异常处理]获取Sentinel Token失败{}", e.getMessage());
}
} catch (TimeoutException e) {
log.error("获取Sentinel Token超时");
} catch (InterruptedException | ExecutionException e) {
log.error("获取Sentinel Token失败{}", e.getMessage());
if (StringUtils.isNotEmpty(sentinelCookie)) {
break;
}
log.warn("[网关转发异常处理]获取Sentinel Token失败进行第{}次重试中", i + 1);
}
}
if (StringUtils.isNotEmpty(sentinelCookie)) {
ServletUtils.removeHeader(mutate, TokenConstants.Cookie);
mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, sentinelCookie));
} else {
throw new ServiceException("Sentinel服务获取Token失败");
throw new ServiceException("[网关转发异常处理]Sentinel服务获取Token失败");
}
}
// 处理Rancher请求
if (lowerUrl.startsWith("/rancher")) {
String rancherToken = null;
if (redisService.hasKey(CacheConstants.RANCHER_TOKEN)) {
rancherToken = redisService.getCacheObject(CacheConstants.RANCHER_TOKEN);
} else {
for (int i = 0; i < retryTimes; i++) {
JSONObject loginRancherBody = new JSONObject();
loginRancherBody.put("description", "api session");
loginRancherBody.put("responseType", "cookie");
loginRancherBody.put("username", rancherUsername);
loginRancherBody.put("password", rancherPassword);
// 网关采用异步架构所以此处需要通过异步请求获取Rancher Token
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginRancher(loginRancherBody));
try {
Response response = future.get(3, TimeUnit.SECONDS);
Map<String, Collection<String>> header = response.headers();
if (header != null && header.containsKey("set-cookie")) {
String rancherCookie = header.get("set-cookie").iterator().next();
rancherToken = getValueByCookieKey(rancherCookie, TokenConstants.Rancher_Cookie_Token_Key);
//Rancher登录状态默认15小时失效将Rancher Token缓存过期时间调整为14分钟
redisService.setCacheObject(CacheConstants.RANCHER_TOKEN, rancherToken, 14L, TimeUnit.HOURS);
}
} catch (TimeoutException e) {
log.error("[网关转发异常处理]获取Rancher Token超时");
} catch (InterruptedException | ExecutionException e) {
log.error("[网关转发异常处理]获取Rancher Token失败{}", e.getMessage());
}
if (StringUtils.isNotEmpty(rancherToken)) {
break;
}
log.warn("[网关转发异常处理]获取Rancher Token失败进行第{}次重试中", i + 1);
}
}
if (StringUtils.isNotEmpty(rancherToken)) {
ServletUtils.removeHeader(mutate, TokenConstants.AUTHENTICATION);
mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + rancherToken);
} else {
throw new ServiceException("[网关转发异常处理]Rancher服务获取Token失败");
}
}
// 处理Nacos请求
@ -112,38 +172,44 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
} else {
Map<String, String> nacosMap = new HashMap<>();
nacosMap.put("username", nacosUsername);
nacosMap.put("password", nacosPassword);
// 网关采用异步架构所以此处需要通过异步请求获取Nacos Token
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap));
try {
Response res = future.get(1, TimeUnit.SECONDS);
StringWriter writer = new StringWriter();
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
String str = writer.toString();
if (!JSON.isValid(str)) {
return exceptionResponse(exchange, str, res.status());
for (int i = 0; i < retryTimes; i++) {
Map<String, String> nacosMap = new HashMap<>();
nacosMap.put("username", nacosUsername);
nacosMap.put("password", nacosPassword);
// 网关采用异步架构所以此处需要通过异步请求获取Nacos Token
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap));
try {
Response res = future.get(3, TimeUnit.SECONDS);
StringWriter writer = new StringWriter();
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
String str = writer.toString();
if (!JSON.isValid(str)) {
return exceptionResponse(exchange, str, res.status());
}
JSONObject resJsonObject = JSONObject.parseObject(str);
if (resJsonObject == null) {
return exceptionResponse(exchange, str, res.status());
}
nacosToken = resJsonObject.getString("accessToken");
Long tokenTtl = resJsonObject.getLong("tokenTtl");
if (nacosToken != null && tokenTtl != null) {
redisService.setCacheObject(CacheConstants.NACOS_TOKEN, nacosToken, tokenTtl - 10, TimeUnit.SECONDS);
}
} catch (TimeoutException e) {
log.error("[网关转发异常处理]获取Nacos Token超时");
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
log.error("[网关转发异常处理]获取Nacos Token失败{}", e.getMessage());
}
JSONObject resJsonObject = JSONObject.parseObject(str);
if (resJsonObject == null) {
return exceptionResponse(exchange, str, res.status());
if (StringUtils.isNotEmpty(nacosToken)) {
break;
}
nacosToken = resJsonObject.getString("accessToken");
Long tokenTtl = resJsonObject.getLong("tokenTtl");
if (nacosToken != null && tokenTtl != null) {
redisService.setCacheObject(CacheConstants.NACOS_TOKEN, nacosToken, tokenTtl - 10, TimeUnit.SECONDS);
}
} catch (TimeoutException e) {
log.error("获取Nacos Token超时");
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
log.error("获取Nacos Token失败{}", e.getMessage());
log.warn("[网关转发异常处理]获取Nacos Token失败进行第{}次重试中", i + 1);
}
}
if (StringUtils.isNotEmpty(nacosToken)) {
mutate.header(TokenConstants.Nacos_Token_Key, nacosToken);
} else {
throw new ServiceException("Nacos服务获取Token失败");
throw new ServiceException("[网关转发异常处理]Nacos服务获取Token失败");
}
}
@ -163,7 +229,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
try {
mutate.uri(new URI(url));
} catch (URISyntaxException e) {
log.error("替换Portainer请求中Endpoints时发生异常{}", e.getMessage());
log.error("[网关转发异常处理]替换Portainer请求中Endpoints时发生异常{}", e.getMessage());
}
}
}
@ -178,38 +244,44 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
} else {
// 网关采用异步架构所以此处需要通过异步请求获取Portainer Token
JSONObject loginPortainerBody = new JSONObject();
loginPortainerBody.put("username", portainerUsername);
loginPortainerBody.put("password", portainerPassword);
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody));
try {
Response res = future.get(1, TimeUnit.SECONDS);
StringWriter writer = new StringWriter();
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
String str = writer.toString();
if (!JSON.isValid(str)) {
return exceptionResponse(exchange, str, res.status());
}
JSONObject resJsonObject = JSONObject.parseObject(str);
portainerToken = resJsonObject.getString("jwt");
if (portainerToken == null) {
return exceptionResponse(exchange, str, res.status());
}
String[] tokenSplit = portainerToken.split("\\.");
String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
Long expiration = tokenBody.getLong("exp");
Long now = new Date().getTime() / 1000;
for (int i = 0; i < 5; i++) {
// 网关采用异步架构所以此处需要通过异步请求获取Portainer Token
JSONObject loginPortainerBody = new JSONObject();
loginPortainerBody.put("username", portainerUsername);
loginPortainerBody.put("password", portainerPassword);
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody));
try {
Response res = future.get(1, TimeUnit.SECONDS);
StringWriter writer = new StringWriter();
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
String str = writer.toString();
if (!JSON.isValid(str)) {
return exceptionResponse(exchange, str, res.status());
}
JSONObject resJsonObject = JSONObject.parseObject(str);
portainerToken = resJsonObject.getString("jwt");
if (portainerToken == null) {
return exceptionResponse(exchange, str, res.status());
}
String[] tokenSplit = portainerToken.split("\\.");
String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
Long expiration = tokenBody.getLong("exp");
Long now = new Date().getTime() / 1000;
long portainerTokenTtl = expiration - now;
if (portainerTokenTtl > 20) {
redisService.setCacheObject(CacheConstants.PORTAINER_TOKEN, portainerToken, portainerTokenTtl - 10, TimeUnit.SECONDS);
long portainerTokenTtl = expiration - now;
if (portainerTokenTtl > 20) {
redisService.setCacheObject(CacheConstants.PORTAINER_TOKEN, portainerToken, portainerTokenTtl - 10, TimeUnit.SECONDS);
}
} catch (TimeoutException e) {
log.error("[网关转发异常处理]获取Portainer Token超时");
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
log.error("[网关转发异常处理]获取Portainer Token失败{}", e.getMessage());
}
} catch (TimeoutException e) {
log.error("获取Portainer Token超时");
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
log.error("获取Portainer Token失败{}", e.getMessage());
if (StringUtils.isNotEmpty(portainerToken)) {
break;
}
log.warn("[网关转发异常处理]获取Portainer Token失败进行第{}次重试中", i + 1);
}
}
if (StringUtils.isNotEmpty(portainerToken)) {
@ -218,7 +290,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + portainerToken);
mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, portainerToken));
} else {
throw new ServiceException("Portainer服务获取Token失败");
throw new ServiceException("[网关转发异常处理]Portainer服务获取Token失败");
}
}
return null;
@ -226,7 +298,23 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) {
log.error("[第三方工具请求处理异常]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), msg);
log.error("[网关转发异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), msg);
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), ExceptionMsgConstants.SYSTEM_EXEC_ERROR, code);
}
/**
* 通过指定Key获取Cookie中指定值
*/
private static String getValueByCookieKey(String cookie, String key) {
if (cookie != null) {
String[] cookies = StringUtils.split(cookie, ';');
for (String cookieItem : cookies) {
if (cookieItem.contains(key)) {
String token = StringUtils.remove(cookieItem, key + "=");
return StringUtils.remove(token, " ");
}
}
}
return null;
}
}

View File

@ -231,7 +231,7 @@ public class CmsDocController extends BaseController {
public AjaxResult editCmsDIr(@ApiParam(name = "id", value = "栏目Id") @PathVariable Long id,
@ApiParam(name = "cmsDirUpdateVo", value = "栏目更新对象") @RequestBody CmsDirUpdateVo cmsDirUpdateVo) {
cmsDocService.updateCmsDir(id, cmsDirUpdateVo);
return success("异步更新栏目信息");
return success("栏目信息更新成功");
}
/**

View File

@ -68,7 +68,7 @@ public class OpenController extends BaseController {
public GenericsTableDataInfo<CmsDocBaseDto> docList(@ApiParam(name = "dirId", value = "栏目Id") @PathVariable("dirId") Long dirId
, CmsDocSearchVo cmsDocSearchVo) {
cmsDocSearchVo.setIsOpen(true);
List<CmsDocBaseDto> list = cmsDocService.selectCmsDocList(dirId, cmsDocSearchVo);
List<CmsDocBaseDto> list = cmsDocService.selectSetDefaultHeadImgCmsDocList(dirId, cmsDocSearchVo);
return getAllGenericsDataTableToPage(list);
}

View File

@ -41,6 +41,9 @@ public class CmsDir implements Comparable<CmsDir> {
@ApiModelProperty(value = "排序", hidden = true)
private Integer sort;
@ApiModelProperty(value = "文章头图")
private String headImg;
public String getName() {
if (name != null) {
name = name.replaceAll(CmsConstants.DIR_SEPARATOR,

View File

@ -21,9 +21,6 @@ public class CmsDirUpdateVo {
@ApiModelProperty(value = "顺序")
private Integer order;
public CmsDir toCmsDir() {
CmsDir cmsDir = new CmsDir();
cmsDir.setName(name);
return cmsDir;
}
@ApiModelProperty(value = "文章头图")
private String headImg;
}

View File

@ -166,6 +166,7 @@ public class CmsDoc extends BaseSortEntity {
CmsDir cmsDir = new CmsDir();
cmsDir.setId(id);
cmsDir.setName(name);
cmsDir.setHeadImg(headImg);
cmsDir.setSort(this.getSort());
cmsDir.setCreatedAt(getCreateTime());
return cmsDir;

View File

@ -1,5 +1,7 @@
package com.microservices.cms.doc.domain.gitlink;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.exception.ServiceException;
import lombok.Data;
/**
@ -22,4 +24,12 @@ public class EntryDto implements Comparable<EntryDto> {
// 重写Comparable接口的compareTo方法根据年龄升序排列降序修改相减顺序即可
return Math.toIntExact(entryDto.getCommit().getCreated_at_unix() - this.commit.getCreated_at_unix());
}
public static EntryDto getEntryDtoFromJson(JSONObject subEntriesResult) {
FileEntryDto fileEntryDto = subEntriesResult.toJavaObject(FileEntryDto.class);
if (fileEntryDto == null || fileEntryDto.getEntries() == null) {
throw new ServiceException("该文章不存在");
}
return fileEntryDto.getEntries();
}
}

View File

@ -31,6 +31,17 @@ public interface CmsDocMapper {
*/
public List<CmsDoc> selectCmsDocList(CmsDocSearchVo cmsDocSearchVo);
/**
* 查询文章信息列表(设置默认头图为栏目头图)
*
* @param cmsDocSearchVo 文章信息
* @return 文章信息集合
*/
List<CmsDoc> selectCmsDocListSetDefaultHeadImg(CmsDocSearchVo cmsDocSearchVo);
/**
* 新增文章信息
*
@ -172,4 +183,13 @@ public interface CmsDocMapper {
Long getNotAuditCmsCountByDeptId(Long deptId);
Long getCmsDocVisitsByDeptId(Long deptId);
/**
* 根据组织id获取专区下所有文章和栏目
* @param deptId 组织id
* @return 文章和栏目列表
*/
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
void updateCmsDocShaByPathAndDeptId(@Param("sha") String sha, @Param("filePath") String filePath, @Param("deptId") Long deptId);
}

View File

@ -91,7 +91,7 @@ public interface ICmsAsyncService {
void asyncCreateBranchAndDeleteFileAndCreatePR(CmsDoc newDocWithAudit, CmsProject cmsProject);
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir);
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName);
/**
* 推送文章数据到搜索引擎

View File

@ -288,4 +288,10 @@ public interface ICmsDocService {
CmsDocBaseDto selectCmsDocBaseInfoById(Long id);
Long getCmsDocVisitsByDeptId(Long deptId);
List<CmsDocBaseDto> selectSetDefaultHeadImgCmsDocList(Long dirId, CmsDocSearchVo cmsDocSearchVo);
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId);
}

View File

@ -16,6 +16,7 @@ import com.microservices.cms.project.service.ICmsProjectService;
import com.microservices.cms.utils.CmsConstants;
import com.microservices.cms.utils.CmsGitLinkRequestUrl;
import com.microservices.cms.utils.CmsUtils;
import com.microservices.cms.utils.CustomExecutorFactory;
import com.microservices.common.core.constant.CacheConstants;
import com.microservices.common.core.constant.Constants;
import com.microservices.common.core.constant.SecurityConstants;
@ -24,6 +25,7 @@ import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.html.EscapeUtil;
import com.microservices.common.core.utils.sign.Base64;
import com.microservices.common.core.utils.uuid.IdUtils;
import com.microservices.common.httpClient.constant.GitLinkConstants;
import com.microservices.common.httpClient.service.HttpAPIService;
@ -47,6 +49,7 @@ import org.springframework.util.DigestUtils;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -93,6 +96,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
private RemoteZoneService remoteZoneService;
@Autowired
private RequestConfig config;
/**
* 异步设置文章更新时间
*
@ -109,7 +113,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
} else {
redisService.setCacheObject(updateDocIntervalKey, cmsProject.getName(), 1L, TimeUnit.MINUTES);
}
CmsDocSearchVo cmsDocSearch=new CmsDocSearchVo();
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
cmsDocSearch.setDeptId(cmsProject.getDeptId());
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
for (EntryDto entryDto : entryDtoList) {
@ -150,7 +154,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
webhook.put("eventCondition", "push");
webhook.put("http_method", "POST");
webhook.put("secret", "");
webhook.put("url", gatewayUrl+"/cms/doc/open/dept/"+cmsProject.getDeptId());
webhook.put("url", gatewayUrl + "/cms/doc/open/dept/" + cmsProject.getDeptId());
webhook.put("events", new String[]{"push"});
jsonObject.put("webhook", webhook);
//增加Webhook
@ -231,7 +235,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
private void syncDocAddedData(CmsProject cmsProject, List<String> addedList) {
for (String addedFilePath:addedList) {
for (String addedFilePath : addedList) {
try {
String[] filePathList = addedFilePath.split("/");
if (filePathList.length != 2) {
@ -250,15 +254,15 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
syncCreateDoc(entry, dirName, cmsProject.getDeptId(), fileEntry.getLast_commit());
}
}
}catch (Exception e){
} catch (Exception e) {
logger.error("[{}]同步新增文章发生异常:{}"
, cmsProject.getName(), e.getMessage());
}
}
}
private void syncDocRemovedData(CmsProject cmsProject,List<String> removedList) {
for (String removedFilePath:removedList) {
private void syncDocRemovedData(CmsProject cmsProject, List<String> removedList) {
for (String removedFilePath : removedList) {
try {
String[] filePathList = removedFilePath.split("/");
if (filePathList.length != 2) {
@ -357,7 +361,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
cmsDocSearch.setDeptId(cmsDir.getDeptId());
cmsDocSearch.setDirName(cmsDir.getName());
cmsDocSearch.setIsDir(false);
List<CmsDoc> cmsDocList=cmsDocMapper.selectCmsDocList(cmsDocSearch);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
//获取数据库所有文件的filePath
List<String> databaseFilePathList = cmsDocList.stream().map(CmsDoc::getFilePath).collect(Collectors.toList());
//获取需要新增的文章
@ -383,16 +387,16 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
@Override
public void asyncDocData(Long deptId, String bodyStr) {
//防止多次操作同时执行将同步内容放入队列根据先后时间执行同步动作
String cmsSyncDeptIdKey=CacheConstants.CMS_SYNC_DEPT_ID_KEY+deptId;
String cmsSyncDeptIdKey = CacheConstants.CMS_SYNC_DEPT_ID_KEY + deptId;
//为当前同步操作生成UUID若拿到的UUID为自身则执行本次同步动作
String UUID= IdUtils.fastSimpleUUID();
String UUID = IdUtils.fastSimpleUUID();
redisService.leftPush(cmsSyncDeptIdKey, UUID);
//设置缓存过期时间防止异常情况导致同步无法继续执行
redisService.expire(cmsSyncDeptIdKey, 3600, TimeUnit.SECONDS);
String currentUUID = String.valueOf(redisService.listIndexOf(cmsSyncDeptIdKey, -1));
//设置最大循环阈值防止意外情况导致死循环(7200次约1小时左右)
int threshold=7200;
while (!UUID.equals(currentUUID)&&threshold!=0){
int threshold = 7200;
while (!UUID.equals(currentUUID) && threshold != 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
@ -402,41 +406,41 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
threshold--;
}
try{
CmsProject cmsProject=cmsProjectService.selectCmsProjectByDeptId(deptId);
JSONObject body=JSONObject.parseObject(bodyStr);
try {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId);
JSONObject body = JSONObject.parseObject(bodyStr);
//获取本次推送中Commit总数
Integer totalCommits=body.getInteger("total_commits");
Integer totalCommits = body.getInteger("total_commits");
//从webhook请求中获取本次提交内容
JSONArray commits=body.getJSONArray("commits");
JSONArray commits = body.getJSONArray("commits");
//当commit总数大于提交的列表数量时代表此次PUSH未携带所有commit此时进行一次全量同步
if(totalCommits!=null
&&commits!=null
&&totalCommits>commits.size()){
if (totalCommits != null
&& commits != null
&& totalCommits > commits.size()) {
fullSyncDocData(cmsProject);
}else{
for (int i = commits.size()-1; i >=0 ; i--) {
JSONObject commit=commits.getJSONObject(i);
} else {
for (int i = commits.size() - 1; i >= 0; i--) {
JSONObject commit = commits.getJSONObject(i);
//获取新增的文件列表
JSONArray addedList=commit.getJSONArray("added");
if(addedList!=null){
syncDocAddedData(cmsProject,addedList.toJavaList(String.class));
JSONArray addedList = commit.getJSONArray("added");
if (addedList != null) {
syncDocAddedData(cmsProject, addedList.toJavaList(String.class));
}
//获取删除的文件列表
JSONArray removedList=commit.getJSONArray("removed");
if(removedList!=null){
syncDocRemovedData(cmsProject,removedList.toJavaList(String.class));
JSONArray removedList = commit.getJSONArray("removed");
if (removedList != null) {
syncDocRemovedData(cmsProject, removedList.toJavaList(String.class));
}
//获取更新的文件列表
JSONArray modifiedList=commit.getJSONArray("modified");
if(modifiedList!=null){
JSONArray modifiedList = commit.getJSONArray("modified");
if (modifiedList != null) {
syncDocModifiedData(cmsProject, modifiedList.toJavaList(String.class));
}
}
}
}catch (Exception e) {
} catch (Exception e) {
logger.error("同步文章时发送异常:{}", e.getMessage());
}finally {
} finally {
//本次同步执行完成后再弹出该UUID
redisService.rightPop(cmsSyncDeptIdKey, 500, TimeUnit.MICROSECONDS);
}
@ -469,12 +473,12 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
@Override
public void syncCreateDir(String dirName,EntryDto entry,CmsProject cmsProject,boolean isCreateKeep){
public void syncCreateDir(String dirName, EntryDto entry, CmsProject cmsProject, boolean isCreateKeep) {
CmsDoc cmsDir = new CmsDoc();
cmsDir.setName(dirName);
cmsDir.setCreateTime(entry.getCommit().getCreated_at());
boolean insertResult;
if(isCreateKeep){
if (isCreateKeep) {
//若该栏目不存在检查文件夹中是否存在.keep文件
JSONObject subEntriesResult = gitLinkRequestHelper.doGet(
CmsGitLinkRequestUrl.GET_FILE_SUB_ENTRIES(cmsProject, cmsDir.getName(), CmsConstants.BRANCH_MASTER));
@ -484,14 +488,14 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
if (keepEntry != null) {
//若存在则在数据库中创建该栏目
insertResult=createCmsDir(cmsDir,cmsProject.getDeptId(),keepEntry);
insertResult = createCmsDir(cmsDir, cmsProject.getDeptId(), keepEntry);
} else {
//若不存在则在数据库中创建该栏目同时在仓库的该文件夹下创建.keep文件
insertResult = cmsDocService.insertCmsDocDir(cmsProject.getDeptId(), cmsDir.copyToCmsDir());
}
}else{
} else {
//若不自动创建则在数据库中创建根据文件名该栏目
insertResult=createCmsDir(cmsDir,cmsProject.getDeptId(),entry);
insertResult = createCmsDir(cmsDir, cmsProject.getDeptId(), entry);
}
@ -500,7 +504,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
}
private boolean createCmsDir(CmsDoc cmsDir,Long deptId,EntryDto entry){
private boolean createCmsDir(CmsDoc cmsDir, Long deptId, EntryDto entry) {
cmsDir.setIsDir(true);
cmsDir.setFilePath(cmsDir.getDirPath());
cmsDir.setDeptId(deptId);
@ -678,6 +682,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
requestBody);
cmsDoc.setAuditPrNumber(result.getString("pull_request_number") + "-" + result.getString("pull_request_id"));
}
@Override
public CmsDoc updateCmsDocDatabaseAndUpdateForGitLink(CmsDoc oldCmsDoc, CmsDocDto cmsDocDto, String branchName) {
if (oldCmsDoc == null) {
@ -871,27 +876,87 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
@Override
@Async
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir) {
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName) {
try {
remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 1, SecurityConstants.INNER);
//创建新栏目
String message = String.format("重命名栏目:由[%s]重命名为[%s]", oldCmsDir.getName(), newCmsDir.getName());
cmsDocService.createFileByGitLink(newCmsDir, message, "");
//在数据库中重命名该栏目防止后续操作推送的WebHook重复创建栏目
cmsDocService.updateCmsCommonDatabase(newCmsDir, CmsConstants.BRANCH_MASTER);
//将原栏目中所有文章移动到新栏目中
moveCmsDocToNewCmsDir(cmsProject, oldCmsDir.getName(), newCmsDir.getName(), message);
//删除旧栏目
deleteCmsDocByGitLink(cmsProject, oldCmsDir);
JSONObject dirFilesJson = gitLinkRequestHelper.doGet(CmsGitLinkRequestUrl.GET_DIR_SUB_ENTRIES(
cmsProject,
oldCmsDir.getName(),
CmsConstants.BRANCH_MASTER));
List<EntryDto> entryDtoList = cmsDocService.getEntriesFromRequestResult(dirFilesJson, cmsProject, false);
CountDownLatch countDownLatch = new CountDownLatch(entryDtoList.size());
// 异步获取每篇文章内容
entryDtoList.forEach(entryDto -> {
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
try {
JSONObject fileSubEntriesResult = gitLinkRequestHelper.doGet(
CmsGitLinkRequestUrl.GET_FILE_SUB_ENTRIES(
cmsProject,
entryDto.getPath(),
CmsConstants.BRANCH_MASTER));
EntryDto cmsDocEntryDto = EntryDto.getEntryDtoFromJson(fileSubEntriesResult);
entryDto.setContent(cmsDocEntryDto.getContent());
} finally {
countDownLatch.countDown();
}
});
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error("栏目重命名失败(将对应代码库中文章移动至新文件夹失败):{}", e.getMessage());
throw new ServiceException("栏目重命名失败");
}
for (EntryDto entryDto : entryDtoList) {
JSONObject updateFileBody = new JSONObject();
updateFileBody.put("branch", CmsConstants.BRANCH_MASTER);
updateFileBody.put("message", "移动文章至新栏目");
updateFileBody.put("from_path", entryDto.getPath());
String newFilePath = newCmsDirName + "/" + entryDto.getName();
updateFileBody.put("filepath", newFilePath);
updateFileBody.put("base64_filepath", Base64.contentToBase64(newFilePath));
updateFileBody.put("sha", entryDto.getSha());
String content = entryDto.getContent();
if (content == null) {
content = "";
}
updateFileBody.put("content", content);
gitLinkRequestHelper.doPut(CmsGitLinkRequestUrl.UPDATE_FILE(cmsProject), updateFileBody);
}
CmsDoc newCmsDoc = oldCmsDir.copyToRenameCmsDir(newCmsDirName);
//在数据库中重命名该栏目
cmsDocService.updateCmsCommonDatabase(newCmsDoc, CmsConstants.BRANCH_MASTER);
} catch (Exception e) {
logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]){}", oldCmsDir.getName(), newCmsDir.getName(), e.getMessage());
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDir.getName() + "])");
} finally {
remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]){}", oldCmsDir.getName(), newCmsDirName, e.getMessage());
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDirName + "])");
}
// finally {
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
// }
}
// @Override
// @Async
// public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir) {
// try {
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 1, SecurityConstants.INNER);
// //创建新栏目
// String message = String.format("重命名栏目:由[%s]重命名为[%s]", oldCmsDir.getName(), newCmsDir.getName());
// cmsDocService.createFileByGitLink(newCmsDir, message, "");
// //在数据库中重命名该栏目防止后续操作推送的WebHook重复创建栏目
// cmsDocService.updateCmsCommonDatabase(newCmsDir, CmsConstants.BRANCH_MASTER);
// //将原栏目中所有文章移动到新栏目中
// moveCmsDocToNewCmsDir(cmsProject, oldCmsDir.getName(), newCmsDir.getName(), message);
// //删除旧栏目
// deleteCmsDocByGitLink(cmsProject, oldCmsDir);
// } catch (Exception e) {
// logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]){}", oldCmsDir.getName(), newCmsDir.getName(), e.getMessage());
// throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDir.getName() + "])");
// } finally {
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
// }
// }
@Async
@Override
public void pushDocDataSearchEngine(CmsDoc cmsDoc) {

View File

@ -103,7 +103,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
String branchName = DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus()) ? CmsConstants.BRANCH_MASTER : cmsDoc.getAuditTmpBranchName();
String branchName = DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus()) ? CmsConstants.BRANCH_MASTER : cmsDoc.getAuditTmpBranchName();
FileEntryDto fileEntryDto = getFileEntryDto(cmsProject, cmsDoc.getFilePath(), branchName);
EntryDto entryDto = fileEntryDto.getEntries();
@ -197,20 +197,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Override
public void updateCmsDir(Long id, CmsDirUpdateVo cmsDirUpdateVo) {
CmsDoc oldCmsDir = selectCmsDir(id);
checkCmsLockStatus(oldCmsDir.getDeptId());
//设置栏目排序
setCmsSort(cmsDirUpdateVo.getOrder(), oldCmsDir, false, null);
oldCmsDir.setHeadImg(cmsDirUpdateVo.getHeadImg());
updateCmsCommonDatabase(oldCmsDir, CmsConstants.BRANCH_MASTER);
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(oldCmsDir.getDeptId());
CmsDoc newCmsDir = oldCmsDir.copyToRenameCmsDir(cmsDirUpdateVo.getName());
String newCmsDirName = cmsDirUpdateVo.getName();
if (StringUtils.isNotEmpty(newCmsDir.getName()) && !newCmsDir.getName().equals(oldCmsDir.getName())) {
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDir.getName(), oldCmsDir.getDeptId());
if (StringUtils.isNotEmpty(newCmsDirName) && !newCmsDirName.equals(oldCmsDir.getName())) {
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDirName, oldCmsDir.getDeptId());
if (cmsDir != null) {
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDir.getName() + "])");
}
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDir);
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDirName + "])");
}
// 异步修改栏目名称将仓库内原文件夹下所有文章移动到新文件夹下
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDirName);
}
}
@Override
@ -237,7 +238,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
cmsDocSearchVo.setDirName(cmsDir.getName());
cmsDocSearchVo.setDeptId(cmsDir.getDeptId());
cmsDocSearchVo.setIsDir(false);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList();
for (CmsDoc cmsDoc : cmsDocList) {
CompletableFuture<CmsDocDetailDto> future = CompletableFuture.supplyAsync(() -> {
@ -283,6 +284,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
return selectCmsDocListByDatabase(dirId, cmsDocSearchVo);
}
@Override
public List<CmsDocBaseDto> selectSetDefaultHeadImgCmsDocList(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
return selectCmsDocListSetDefaultHeadImgByDatabase(dirId, cmsDocSearchVo);
}
@Override
public List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId) {
return cmsDocMapper.selectCmsDocAndDirListByDept(deptId);
}
@Override
public void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId) {
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha,filePath,deptId);
}
private List<CmsDocBaseDto> selectCmsDocListByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
CmsDoc cmsDocDir = selectCmsDir(dirId);
@ -295,6 +311,24 @@ public class CmsDocServiceImpl implements ICmsDocService {
return cmsDocList.stream().map(CmsDoc::copyToCmsDocBaseDto).collect(Collectors.toList());
}
/**
* 设置文章默认头图为栏目头图
* @param dirId 栏目id
* @param cmsDocSearchVo 文章查询条件
* @return 文章列表
*/
private List<CmsDocBaseDto> selectCmsDocListSetDefaultHeadImgByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
CmsDoc cmsDocDir = selectCmsDir(dirId);
cmsDocSearchVo.setDeptId(cmsDocDir.getDeptId());
cmsDocSearchVo.setDirName(cmsDocDir.getName());
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
if (cmsDocList == null || cmsDocList.isEmpty()) {
return new ArrayList<>();
}
return cmsDocList.stream().map(CmsDoc::copyToCmsDocBaseDto).collect(Collectors.toList());
}
/**
* 检查专区资讯是否处于锁定状态若处于则抛出异常
*
@ -552,6 +586,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
throw new ServiceException("计算字符串Sha出错");
}
}
@Override
public void setCmsSort(Integer order, CmsDoc oldCmsDoc, boolean isChangeOnDir, String isHomePage) {
//调整文章/栏目顺序
@ -697,7 +732,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
Long originCmsDocId = Long.parseLong(oldCmsDoc.getAuditType().split("-")[1]);
CmsDoc originCmsDoc = cmsDocMapper.selectCmsDocById(originCmsDocId);
deleteTmpBranchIfBranchExist(oldCmsDoc);
BeanUtils.copyProperties(originCmsDoc,oldCmsDoc);
BeanUtils.copyProperties(originCmsDoc, oldCmsDoc);
cmsDocMapper.deleteCmsDocById(id);
} else
//文章审核类型create 直接新增文章
@ -724,11 +759,11 @@ public class CmsDocServiceImpl implements ICmsDocService {
} else
//更新已发布文章
if (oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
//1.创建一条auditType="update-原文章id"audit_status为2其余字段与待更新文章一致的新文章数据
newDocWithAudit = insertDocWithAudit(oldCmsDoc,
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
}
//1.创建一条auditType="update-原文章id"audit_status为2其余字段与待更新文章一致的新文章数据
newDocWithAudit = insertDocWithAudit(oldCmsDoc,
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
}
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
return true;
}
@ -785,7 +820,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
} catch (ServiceException e) {
//忽略分支不存在异常
if(!"分支不存在!".equals(e.getMessage())) {
if (!"分支不存在!".equals(e.getMessage())) {
throw new ServiceException(e.getMessage());
}
}
@ -863,6 +898,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
cmsDoc.setIsDir(true);
cmsDoc.setFilePath(cmsDoc.getDirPath(cmsDir.getName()));
cmsDoc.setDeptId(deptId);
cmsDoc.setHeadImg(cmsDir.getHeadImg());
cmsDoc.setName(cmsDir.getName());
return insertCommonCmsDoc(cmsDoc, "创建栏目:" + cmsDir.getName(), "");
}
@ -949,7 +985,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
List<CmsDoc> cmsDocList = cmsDocMapper.selectHotCmsDocList(cmsDocInput);
if (cmsDocList != null) {
//todo:需检查该文章是否在GitLink中存在
return cmsDocList.stream().map(x -> x.copyToHotCmsDocDto()).collect(Collectors.toList());
return cmsDocList.stream().map(CmsDoc::copyToHotCmsDocDto).collect(Collectors.toList());
}
return new ArrayList<>();
@ -990,7 +1026,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
for (CmsDoc cmsDir : cmsDirList) {
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
cmsDocSearchVo.setIsOpen(isOpen);
List<CmsDocBaseDto> cmsDocBaseDtoList = selectCmsDocList(cmsDir.getId(), cmsDocSearchVo);
List<CmsDocBaseDto> cmsDocBaseDtoList = selectSetDefaultHeadImgCmsDocList(cmsDir.getId(), cmsDocSearchVo);
CmsDocOverviewDto cmsDocOverviewDto = new CmsDocOverviewDto();
cmsDocOverviewDto.setId(cmsDir.getId());
cmsDocOverviewDto.setName(cmsDir.getName());
@ -1104,7 +1140,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
cmsDocSearch.setDeptId(deptId);
cmsDocSearch.setIsHomepage(CmsConstants.TRUE);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearch);
List<CmsDocBaseDto> cmsDocBaseDtoList = new ArrayList<>();
for (CmsDoc cmsDoc : cmsDocList) {
CmsDocBaseDto cmsDocBaseDto = cmsDoc.copyToCmsDocBaseDto();

View File

@ -95,8 +95,12 @@ public class CmsProjectController extends BaseController {
@GetMapping("/getProjectByDeptId/{deptId}")
public R<String> getProjectByDeptId(
@ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId);
return R.ok(JSONObject.toJSONString(cmsProject));
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptIdAndNoException(deptId);
if (cmsProject == null) {
return R.ok(null);
} else {
return R.ok(JSONObject.toJSONString(cmsProject));
}
}
/**
@ -170,4 +174,16 @@ public class CmsProjectController extends BaseController {
public R<Boolean> removeByDeptId(@PathVariable(value = "deptId") Long deptId) {
return R.ok(cmsProjectService.deleteCmsProjectByDeptId(deptId) > 0);
}
/**
* 初始化专区对应项目
*/
@RequiresPermissions("zone:project:add")
@ApiOperation("初始化专区对应项目")
@Log(title = "初始化专区对应项目", businessType = BusinessType.INSERT)
@PostMapping("initCmsProject/dept/{deptId}")
public AjaxResult initCmsProject(
@ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) {
return toAjax(cmsProjectService.initZoneCmsProjectByDept(deptId));
}
}

View File

@ -78,4 +78,8 @@ public interface ICmsProjectService {
Boolean updateCmsProjectByDept(SysDept dept);
boolean operateProtectedBranchByZoneAuditSetting(String userName, String identifier, Long deptId, String operationType);
boolean initZoneCmsProjectByDept(Long deptId);
CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId);
}

View File

@ -1,21 +1,23 @@
package com.microservices.cms.project.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.CmsDir;
import com.microservices.cms.doc.domain.CmsDocBaseDto;
import com.microservices.cms.doc.domain.CmsDocSearchVo;
import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsAsyncService;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.project.domain.CmsProject;
import com.microservices.cms.project.mapper.CmsProjectMapper;
import com.microservices.cms.project.service.ICmsProjectService;
import com.microservices.cms.utils.CmsConstants;
import com.microservices.cms.utils.CmsGitLinkRequestUrl;
import com.microservices.common.core.constant.HttpStatus;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.enums.SystemRole;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.PinYinStringUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.datascope.annotation.DataScope;
import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.system.api.RemoteDeptService;
import com.microservices.system.api.RemoteUserService;
@ -23,6 +25,7 @@ import com.microservices.system.api.domain.SysDept;
import com.microservices.system.api.domain.SysRole;
import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.utils.FeignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
@ -37,6 +40,7 @@ import java.util.List;
* @author otto
* @date 2023-03-27
*/
@Slf4j
@Service
public class CmsProjectServiceImpl implements ICmsProjectService {
@Autowired
@ -228,6 +232,75 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
return true;
}
@Override
public boolean initZoneCmsProjectByDept(Long deptId) {
boolean initResult = false;
CmsProject cmsProject = cmsProjectMapper.selectCmsProjectByDeptId(deptId);
if (cmsProject != null) {
JSONObject project = null;
try {
project = gitLinkRequestHelper.doGet(CmsGitLinkRequestUrl.GET_PROJECT_DETAIL(cmsProject.getUsername(), cmsProject.getIdentifier()));
} catch (ServiceException e) {
if (!e.getCode().equals(HttpStatus.NOT_FOUND)) {
throw new ServiceException(e.getCode(), e.getMessage());
}
}
if (project != null) {
throw new ServiceException("专区对应项目已存在,不能重复初始化");
} else {
Long organizationId = getOrganizationId();
// 创建专区对应项目
JSONObject jsonObject = new JSONObject();
jsonObject.put("user_id", organizationId);
jsonObject.put("name", cmsProject.getName());
jsonObject.put("repository_name", cmsProject.getIdentifier());
jsonObject.put("description", cmsProject.getDescription());
jsonObject.put("private", false);
//调用GitLink接口创建项目
JSONObject result = gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.CREATE_PROJECT(), jsonObject);
initResult = result != null && cmsProject.getIdentifier().equals(result.getString("identifier"));
}
} else {
Long projectId = insertZoneCmsProjectByDept(deptId);
if (projectId != null && projectId > 0) {
cmsProject = cmsProjectMapper.selectCmsProjectById(projectId);
initResult = true;
}
}
if (initResult) {
// 批量创建文章对应代码库文件
List<CmsDoc> cmsDocAndDirList = cmsDocService.selectCmsDocAndDirListByDept(deptId);
if (cmsDocAndDirList == null || cmsDocAndDirList.isEmpty()) {
return initResult;
}
JSONObject docCommitJson = new JSONObject();
docCommitJson.put("branch", CmsConstants.BRANCH_MASTER);
docCommitJson.put("message", "初始化专区文章对应代码库文件");
JSONArray docFiles = new JSONArray();
for (CmsDoc cmsDoc : cmsDocAndDirList) {
JSONObject docFile = new JSONObject();
docFile.put("action_type", "create");
docFile.put("content", "");
docFile.put("encoding", "text");
docFile.put("file_path", cmsDoc.getFilePath());
docFiles.add(docFile);
}
docCommitJson.put("files", docFiles);
JSONObject result = gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.BATCH_COMMIT(cmsProject.getUsername(), cmsProject.getIdentifier())
, docCommitJson);
if (result == null) {
throw new ServiceException("专区对应项目初始化成功,但专区文章在项目中创建失败");
}
JSONArray resultContents = result.getJSONArray("contents");
for (int i = 0; i < resultContents.size(); i++) {
JSONObject contentJson = resultContents.getJSONObject(i);
cmsDocService.updateCmsDocShaByPathAndDeptId(contentJson.getString("sha"), contentJson.getString("path"), deptId);
}
}
return initResult;
}
private String handleSpecialCharacters(String name) {
return StringUtils.replace(name, " ", "-");
@ -242,6 +315,11 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
return cmsProject;
}
@Override
public CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId) {
return cmsProjectMapper.selectCmsProjectByDeptId(deptId);
}
@Override
public Boolean changeUserRole(Long userId, Long oldDeptId, Long newDeptId, Long oldRoleId, Long newRoleId) {
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER));

View File

@ -1,4 +1,4 @@
package com.microservices.cms.common.service.impl;
package com.microservices.cms.service.impl;
import com.microservices.cms.common.service.ISysUserService;
import com.microservices.common.core.constant.SecurityConstants;

View File

@ -148,19 +148,19 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
);
}
public static GitLinkRequestUrl CREATE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
public static GitLinkRequestUrl CREATE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
String path = buildUri(String.format("/api/%s/%s/protected_branches.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path);
}
public static GitLinkRequestUrl UPDATE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
public static GitLinkRequestUrl UPDATE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path);
}
public static GitLinkRequestUrl DELETE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
public static GitLinkRequestUrl DELETE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path);
@ -197,4 +197,12 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static String ZONE_DOC_URL(String gitlinkUrl, String zoneKey, Long docId) {
return EscapeUtil.removeExtraSlashOfUrl(String.format("%s/zone/%s/newdetail/%d", gitlinkUrl, zoneKey, docId));
}
public static GitLinkRequestUrl GET_PROJECT_DETAIL(String username, String identifier) {
return getAdminGitLinkRequestUrl(String.format("/api/%s/%s/detail.json", username, identifier));
}
public static GitLinkRequestUrl BATCH_COMMIT(String username, String identifier) {
return getAdminGitLinkRequestUrl(String.format("/api/v1/%s/%s/contents/batch.json", username, identifier));
}
}

View File

@ -0,0 +1,37 @@
package com.microservices.cms.utils;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.threadPool.ThreadPoolExecutorWrap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池工厂
*
* @author OTTO
*/
public class CustomExecutorFactory {
/**
* 创建线程池
* 存在并发处理的情况设置核心线程为2设置有界队列长度为100最大线程数为10
* 当超出队列已满且达到最大线程数时抛出异常
* Ncpu=CPU数量
* Ucpu=目标CPU的使用率0<=Ucpu<=1
* W/C=任务等待时间与任务计算时间的比率
* Nthreads =Ncpu*Ucpu*(1+W/C)
*/
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutorWrap(
5
, 10
, 10
, TimeUnit.SECONDS
, new ArrayBlockingQueue<>(100)
, Executors.defaultThreadFactory()
, (r, executor) -> {
throw new ServiceException("目前处理的人太多了,请稍后再试");
}
);
}

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9116
port: 8080
# Spring
spring:

View File

@ -107,6 +107,50 @@
from cms_doc cd
</sql>
<sql id="selectCmsDocAliasSetDefaultHeadImgVo">
select cd.id,
cd.create_by,
cd.create_time,
cd.update_by,
cd.update_time,
cd.name,
cd.file_name,
cd.file_path,
cd.file_sha,
cd.file_size,
cd.file_url,
cd.html_url,
cd.visits,
cd.sort,
cd.publish_time,
cd.publish_user,
cd.publish_user_image,
cd.publish_user_url,
cd.publish_user_nickname,
cd.summary,
-- 处理文章和栏目头图为NULL或空字符串的情况
CASE
-- 文章头图不为空非NULL且非空字符串
WHEN cd.head_img IS NOT NULL AND TRIM(cd.head_img) != '' THEN cd.head_img
-- 栏目存在且头图不为空
WHEN dir.head_img IS NOT NULL AND TRIM(dir.head_img) != '' THEN dir.head_img
END AS head_img,
cd.dir_name,
cd.dept_id,
cd.issue_id,
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir
from cms_doc cd
left join cms_doc dir on cd.dir_name = dir.name and cd.dept_id = dir.dept_id
</sql>
<select id="selectCmsDocAndDirListByDept" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
where dept_id=#{deptId}
</select>
<select id="selectCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDocSearchVo"
resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
@ -150,13 +194,13 @@
where file_path = #{filePath} and dept_id = #{deptId} and is_dir = 0
</select>
<select id="selectHotCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDoc" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
<include refid="selectListWhere"/>
<include refid="selectCmsDocAliasSetDefaultHeadImgVo"/>
<include refid="selectListWhereAlias"/>
ORDER BY
visits DESC,
sort DESC,
update_time DESC,
create_time DESC
cd.visits DESC,
cd.sort DESC,
cd.update_time DESC,
cd.create_time DESC
</select>
<select id="selectCmsDocCount" resultType="java.lang.Integer">
@ -180,6 +224,23 @@
</where>
</sql>
<sql id="selectListWhereAlias">
<where>
<if test="name != null and name != ''">and cd.name like concat('%', #{name}, '%')</if>
<if test="keywords != null and keywords != ''">and cd.keywords like concat('%', #{keywords}, '%')</if>
<if test="fileName != null and fileName != ''">and cd.file_name like concat('%', #{fileName}, '%')</if>
<if test="filePath != null and filePath != ''">and cd.file_path = #{filePath}</if>
<if test="publishTime != null ">and cd.publish_time = #{publishTime}</if>
<if test="isDir != null ">and cd.is_dir = #{isDir}</if>
<if test="dirName != null ">and cd.dir_name = #{dirName}</if>
<if test="deptId != null ">and cd.dept_id = #{deptId}</if>
<if test="isHomepage != null ">and cd.is_homepage = #{isHomepage}</if>
<if test="auditStatus != null and auditStatus != '-1' ">and cd.audit_status = #{auditStatus}</if>
<if test="auditStatus == null ">and cd.audit_status = '1'</if>
<if test="createBy != null and createBy != '' ">and cd.create_by = #{createBy}</if>
</where>
</sql>
<select id="selectCmsDocDirByDeptIdAndInName" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/>
where dept_id = #{deptId} and is_dir = 1
@ -283,6 +344,15 @@
from cms_doc
where dept_id = #{deptId};
</select>
<select id="selectCmsDocListSetDefaultHeadImg" parameterType="com.microservices.cms.doc.domain.CmsDocSearchVo"
resultMap="CmsDocResult">
<include refid="selectCmsDocAliasSetDefaultHeadImgVo"/>
<include refid="selectListWhereAlias"/>
ORDER BY
cd.sort DESC,
cd.update_time DESC,
cd.create_time DESC
</select>
<insert id="insertCmsDoc" parameterType="com.microservices.cms.doc.domain.CmsDoc" useGeneratedKeys="true"
keyProperty="id">
@ -405,6 +475,12 @@
and dept_id = #{deptId}
and is_dir = #{isDir};
</update>
<update id="updateCmsDocShaByPathAndDeptId">
update cms_doc
set file_sha=#{sha}
where file_path=#{filePath}
and dept_id=#{deptId}
</update>
<delete id="deleteCmsDocById" parameterType="Long">
delete

View File

@ -87,10 +87,18 @@
<groupId>com.microservices</groupId>
<artifactId>microservices-common-security</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-core</artifactId>
@ -125,7 +133,7 @@
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.8.4</version>
<version>portable-1.3.4</version>
</dependency>
</dependencies>

View File

@ -7,6 +7,7 @@ import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import io.swagger.annotations.Api;
@ -15,9 +16,11 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 校企成果Controller
@ -90,6 +93,21 @@ public class SchoolEnterpriseAchievementsController extends BaseController
return AjaxResult.success(schoolEnterpriseAchievementsService.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievements));
}
@PostMapping("/importData")
@Log(title = "校企成果", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<SchoolEnterpriseAchievements> util = new ExcelUtil<>(SchoolEnterpriseAchievements.class);
// 1. 解析Excel获取实体列表和图片Map
List<SchoolEnterpriseAchievements> list = util.importExcelWithImages(StringUtils.EMPTY, file.getInputStream(), 0);
Map<String, MultipartFile> imageMap = util.getExcelImageMap();
// 2. 调用业务层处理传入实体列表和图片Map
String operName = SecurityUtils.getUsername();
String message = schoolEnterpriseAchievementsService.batchInsertSchoolEnterpriseAchievements(list, imageMap, updateSupport, operName);
return AjaxResult.success(message);
}
/**
* 修改校企成果
*/

View File

@ -167,7 +167,7 @@ public class Achievements extends BaseEntity {
/**
* 成果图片
*/
@Excel(name = "成果图片")
@Excel(name = "图片")
private String images;
/**

View File

@ -32,7 +32,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果领域1
*/
@Excel(name = "成果领域1")
@Excel(
name = "成果领域",
readConverterExp = "1=理论研究,2=政策法规,3=医学,4=电子信息,5=通信工程,6=计算机科学,7=软件工程,9=人工智能,10=知识图谱,12=大数据,23=机器人,24=无人系统,25=创意征集,26=文献翻译,27=成果转化,28=科技协同,29=科研组织模式,30=新兴领域,31=其他,32=电子元器件,33=测试仪器,34=军事历史,35=计算机网络"
)
private String field1;
/**
@ -50,7 +53,7 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果类型
*/
@Excel(name = "成果类型")
@Excel(name = "成果类型", readConverterExp = "1=理论成果,2=技术成果,3=应用成果,4=软科学成果,5=文化艺术成果,6=社会服务成果,7=交叉领域成果")
private String achievementType;
/**
@ -74,7 +77,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果标签
*/
@Excel(name = "成果标签")
@Excel(
name = "成果标签",
readConverterExp = "1=高校,2=企业,3=其他"
)
private String tags;
/**
@ -159,7 +165,7 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果图片
*/
@Excel(name = "成果图片")
@Excel(name = "图片")
private String images;
/**
@ -169,10 +175,18 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
private String attachments;
private Integer achievementStatus;
//成果小类型
@Excel(
name = "成果小类型",
readConverterExp = "7=学术论文,8=学术专著,9=理论模型,10=发明专利,11=技术标准,12=软件系统,13=技术原型,14=产品设备,15=解决方案,16=示范工程,17=政策建议,18=咨询报告,19=行业标准,20=管理办法,21=文学作品,22=艺术作品,23=公益项目,24=社会调查报告,25=教育培训成果,26=理论模型+产品设备,27=学术论文+技术原型"
)
private String achievementLittleType;
//成果状态
@Excel(
name = "成果状态",
readConverterExp = "1=已有阶段性成果,2=已有成熟成果,3=已公开发表,4=已对外发布,5=已开展应用,6=已形成商业化产品"
)
private String achievementCurStatus;
//团队名称
@Excel(name = "成果团队名称")
private String teamName;
private List<AchievementTeam> teamList;

View File

@ -1,7 +1,10 @@
package com.microservices.dms.achievementLibrary.service;
import java.util.List;
import java.util.Map;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import org.springframework.web.multipart.MultipartFile;
/**
* 校企成果Service接口
@ -64,4 +67,6 @@ public interface ISchoolEnterpriseAchievementsService
List<SchoolEnterpriseAchievements> listFront(SchoolEnterpriseAchievements schoolEnterpriseAchievements);
boolean getCheckSchoolAchName(SchoolEnterpriseAchievements schoolEnterpriseAchievements);
String batchInsertSchoolEnterpriseAchievements(List<SchoolEnterpriseAchievements> list, Map<String, MultipartFile> imageMap, boolean updateSupport, String operName);
}

View File

@ -1,25 +1,34 @@
package com.microservices.dms.achievementLibrary.service.impl;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.datasource.annotation.Slave;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.AchievementTeam;
import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.mapper.AchievementTeamMapper;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.model.LoginUser;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
/**
* 校企成果Service业务层处理
@ -41,6 +50,9 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
@Autowired
private AchievementsMapper achievementsMapper;
@Autowired
private RemoteFileService remoteFileService;
/**
* 查询校企成果
*
@ -124,6 +136,144 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
return UserConstants.UNIQUE;
}
/**
* 导入成果数据包含图片处理
*/
@Override
@Transactional
@Slave
public String batchInsertSchoolEnterpriseAchievements(List<SchoolEnterpriseAchievements> list,
Map<String, MultipartFile> imageMap,
boolean updateSupport,
String operName) {
if (list == null || list.isEmpty()) {
throw new RuntimeException("导入成果数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
int imageErrorNum = 0; // 新增图片处理失败数
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
StringBuilder imageErrorMsg = new StringBuilder(); // 新增图片错误信息
for (SchoolEnterpriseAchievements schoolEnterpriseAchievement : list) {
try {
// 处理图片字段优化后返回处理结果不抛出异常
String imageHandleResult = handleAchievementImage(schoolEnterpriseAchievement, imageMap);
if (imageHandleResult.contains("失败") || imageHandleResult.contains("异常")) {
imageErrorNum++;
imageErrorMsg.append("<br/>").append(imageErrorNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 图片处理失败:").append(imageHandleResult);
}
// 原有查重和入库逻辑不受图片处理影响
SchoolEnterpriseAchievements exist = schoolEnterpriseAchievementsMapper.selectSchoolEnterpriseAchievementsByName(schoolEnterpriseAchievement.getAchievementName());
if (exist == null) {
schoolEnterpriseAchievement.setCreateBy(operName);
schoolEnterpriseAchievement.setCreateTime(new Date());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievement.setStatus("2");
schoolEnterpriseAchievementsMapper.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 导入成功");
} else {
if (updateSupport) {
schoolEnterpriseAchievement.setId(exist.getId());
schoolEnterpriseAchievement.setCreateBy(exist.getCreateBy());
schoolEnterpriseAchievement.setCreateTime(exist.getCreateTime());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievementsMapper.updateSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 已存在(未开启更新)");
}
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、成果 " + schoolEnterpriseAchievement.getAchievementName() + " 导入失败:";
failureMsg.append(msg).append(e.getMessage());
}
}
// 组装最终提示信息包含图片处理错误
StringBuilder finalMsg = new StringBuilder();
if (successNum > 0) {
finalMsg.append("").append(successNum).append(" 条数据导入/更新成功:").append(successMsg);
}
if (failureNum > 0) {
finalMsg.append("<br/>共 ").append(failureNum).append(" 条数据导入失败:").append(failureMsg);
}
if (imageErrorNum > 0) {
finalMsg.append("<br/>共 ").append(imageErrorNum).append(" 条数据图片处理失败:").append(imageErrorMsg);
}
return finalMsg.toString();
}
/**
* 处理成果图片上传并格式化存储FastJSON解析版
* @param achievement 成果实体
* @param imageMap Excel解析出的图片映射表
* @return 图片处理结果提示成功/失败信息
*/
private String handleAchievementImage(SchoolEnterpriseAchievements achievement, Map<String, MultipartFile> imageMap) {
String imageCellKey = achievement.getImages();
if (StringUtils.isEmpty(imageCellKey) || !imageMap.containsKey(imageCellKey)) {
achievement.setImages("[]");
return "无图片,跳过处理";
}
try {
MultipartFile file = imageMap.get(imageCellKey);
byte[] fileBytes = file.getBytes();
if (fileBytes == null || fileBytes.length == 0) {
return "图片文件为空,跳过处理";
}
// 构建 HashMap 参数
String originalFilename = StringUtils.isEmpty(file.getOriginalFilename())
? "excel_import_image_" + System.currentTimeMillis() + ".png"
: file.getOriginalFilename();
Map<String, Object> params = new HashMap<>();
params.put("base64Data", Base64.getEncoder().encodeToString(fileBytes));
params.put("fileName", originalFilename);
params.put("contentType", file.getContentType());
params.put("size", fileBytes.length);
// 调用 Base64 上传接口
R<?> fileResult = remoteFileService.uploadBase64(params, "dms", "import", SecurityConstants.INNER);
// 解析结果与原逻辑相同
if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData())) {
String errorMsg = !StringUtils.isNull(fileResult) ? fileResult.getMsg() : "接口返回异常";
return "图片上传失败:" + errorMsg;
}
JSONObject resultJson = JSONObject.parseObject(JSONObject.toJSONString(fileResult.getData()));
String fileOriginName = resultJson.getString("fileOriginName");
String url = resultJson.getString("downloadUrl");
Integer fileId = resultJson.getInteger("fileId");
if (StringUtils.isEmpty(fileOriginName) || StringUtils.isEmpty(url) || fileId == null) {
return "图片上传成功,但返回数据不完整";
}
String imageJson = String.format("[{\"k\":\"%s\",\"v\":\"%s\",\"id\":%d}]",
fileOriginName, url, fileId);
achievement.setImages(imageJson);
return "图片处理成功";
} catch (Exception e) {
return "图片处理异常:" + e.getMessage();
}
}
/**
* 修改校企成果

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9119
port: 8080
# Spring
spring:

View File

@ -473,7 +473,7 @@
from (
(select count(1) as "auditSum"
from memos
where destroy_status is null and hidden = 0 and published_at is not null) a,
where destroy_status is null and hidden = 0 and published_at is not null AND root_id IS NULL) a,
(select count(1) as "allSum" from memos) b,
(select count(1) as "currentYearSum"
from memos

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9198
port: 8080
# Spring
spring:

View File

@ -1,20 +1,26 @@
package com.microservices.file.controller;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.security.annotation.InnerAuth;
import com.microservices.file.service.ISysFileInfoService;
import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.utils.StandardMultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Base64;
import java.util.Map;
/**
* 文件请求处理
*
@ -49,4 +55,56 @@ public class SysFileController extends BaseController {
return R.fail(e.getMessage());
}
}
@InnerAuth
@PostMapping("/upload/base64")
public R<SysFileInfo> uploadBase64(@RequestBody Map<String, Object> params,
@RequestParam("type") String type,
@RequestParam("hierarchy") String hierarchy) {
try {
// 1. 提取参数
String base64Data = (String) params.get("base64Data");
String fileName = (String) params.get("fileName");
String contentType = (String) params.get("contentType");
// 2. 参数校验
if (StringUtils.isEmpty(base64Data)) {
return R.fail("base64Data不能为空");
}
// 3. Base64解码
byte[] fileBytes;
try {
fileBytes = Base64.getDecoder().decode(base64Data);
} catch (IllegalArgumentException e) {
log.error("Base64解码失败", e);
return R.fail("图片数据格式错误");
}
if (fileBytes.length == 0) {
return R.fail("解码后文件为空");
}
// 4. 构造MultipartFile
String originalFilename = StringUtils.isEmpty(fileName)
? "base64_image_" + System.currentTimeMillis() + ".png"
: fileName;
MultipartFile multipartFile = new StandardMultipartFile(
fileBytes,
"file", // 字段名与原接口一致
originalFilename,
contentType
);
// 5. 复用原有上传逻辑
SysFileInfo fileInfo = sysFileInfoService.upload(multipartFile, type, hierarchy);
return R.ok(fileInfo);
} catch (Exception e) {
log.error("Base64上传文件失败", e);
return R.fail(e.getMessage());
}
}
}

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9111
port: 8080
# Spring
spring:

View File

@ -1,6 +1,6 @@
# Tomcat
server:
port: 9113
port: 8080
# Spring
spring:

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.microservices</groupId>
<artifactId>microservices-modules</artifactId>
<version>3.6.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>microservices-modules-mon</artifactId>
<description>
microservices-modules-mon运维监控模块
</description>
<dependencies>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Swagger UI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.fox.version}</version>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Microservices Common Async -->
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-async</artifactId>
</dependency>
<!-- Microservices Common DataSource -->
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-datasource</artifactId>
</dependency>
<!-- Microservices Common DataScope -->
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-datascope</artifactId>
</dependency>
<!-- Microservices Common Log -->
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-log</artifactId>
</dependency>
<!-- Microservices Common Swagger -->
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.microservices</groupId>
<artifactId>microservices-common-httpClient</artifactId>
<version>3.6.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,25 @@
package com.microservices.mon;
import com.microservices.common.security.annotation.EnableCustomConfig;
import com.microservices.common.security.annotation.EnableRyFeignClients;
import com.microservices.common.swagger.annotation.EnableCustomSwagger2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 运维监控模块
*
* @author otto
*/
@EnableCustomConfig
@EnableCustomSwagger2
@EnableRyFeignClients
@SpringBootApplication
@EnableScheduling
public class MicroservicesMonApplication {
public static void main(String[] args) {
SpringApplication.run(MicroservicesMonApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 运维监控模块启动成功 ლ(´ڡ`ლ)゙ \n");
}
}

View File

@ -0,0 +1,83 @@
package com.microservices.mon.k8s.controller;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.domain.GenericsAjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.mon.k8s.domain.*;
import com.microservices.mon.k8s.service.IK8sDashboardService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("dashboard")
@Api(tags = "微服务大屏")
@RefreshScope
public class DashboardController extends BaseController {
@Autowired
private IK8sDashboardService k8sDashboardService;
/**
* 获取命名空间列表
*/
@ApiOperation(value = "获取命名空间列表")
@GetMapping("/namespace/list")
public GenericsTableDataInfo<K8sNamespace> namespaceList(@RequestParam(value = "search", required = false) @ApiParam(value = "根据应用名称查找") String search) {
return new GenericsTableDataInfo<>(k8sDashboardService.getNamespaceDetailList(true,search));
}
@ApiOperation(value = "获取集群节点列表")
@GetMapping("/cluster/Nodelist")
public GenericsTableDataInfo<K8sClusterNode> clusterNodeList() {
return new GenericsTableDataInfo<>(k8sDashboardService.getClusterNodeList());
}
@ApiOperation(value = "获取应用容器Ip列表")
@GetMapping("/application/{kind}/{namespace}/{applicationName}/podIdList")
public GenericsTableDataInfo<String> applicationPodIdList(@PathVariable("kind") String kind, @PathVariable("namespace") String namespace, @PathVariable("applicationName") String applicationName) {
return new GenericsTableDataInfo<>(k8sDashboardService.getApplicationPodIdList(kind, namespace, applicationName));
}
@ApiOperation(value = "获取集群状态数据")
@GetMapping("/cluster/status")
public GenericsAjaxResult<K8sClusterStatus> clusterStatus() {
return GenericsAjaxResult.success(k8sDashboardService.getClusterStatus());
}
@ApiOperation(value = "集群每秒请求率QPS")
@GetMapping("/cluster/qps")
public GenericsTableDataInfo<MetricCommonData> clusterQps() {
return new GenericsTableDataInfo<>(k8sDashboardService.getClusterQps());
}
@ApiOperation(value = "最近24小时应用接口请求次数")
@GetMapping("/cluster/applicationRequestCount")
public GenericsTableDataInfo<ApplicationRequestCountData> applicationRequestCount() {
return new GenericsTableDataInfo<>(k8sDashboardService.getApplicationRequestCount());
}
@ApiOperation(value = "获取Pod容器状态")
@GetMapping("/cluster/podContainerStatus/{podName}")
public GenericsAjaxResult<K8sPodContainerStatus> podContainerStatus(@PathVariable("podName") String podName) {
return GenericsAjaxResult.success(k8sDashboardService.getPodContainerStatus(podName));
}
@ApiOperation(value = "获取服务器节点状态")
@GetMapping("/cluster/nodeStatus/{nodeIp}")
public GenericsAjaxResult<K8sNodeStatus> nodeStatus(@PathVariable("nodeIp") String nodeIp) {
return GenericsAjaxResult.success(k8sDashboardService.getNodeStatus(nodeIp));
}
@ApiOperation(value = "获取微服务应用功能列表")
@GetMapping("/applicationFunction/{kind}/{namespace}/{applicationName}")
public GenericsTableDataInfo<ApplicationFunction> applicationFunction(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestParam("podIpList") @ApiParam(value = "Pod Ip列表(使用逗号分割的ip列表字符串)") String podIpList) {
return new GenericsTableDataInfo<>(k8sDashboardService.getApplicationFunction(kind, namespace, applicationName, podIpList));
}
}

View File

@ -0,0 +1,247 @@
package com.microservices.mon.k8s.controller;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.utils.PageUtils;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.domain.GenericsAjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.mon.k8s.domain.*;
import com.microservices.mon.k8s.domain.vo.ApplicationEnvTypeVo;
import com.microservices.mon.k8s.domain.vo.ApplicationTypeVo;
import com.microservices.mon.k8s.domain.vo.CommonTypeVo;
import com.microservices.mon.k8s.service.IMicroManagementService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("micro")
@Api(tags = "微服务管理")
@RefreshScope
public class MicroManagementController extends BaseController {
@Autowired
private IMicroManagementService microManagementService;
@ApiOperation(value = "获取可视化面板容器列表")
@GetMapping("/pod/list")
public GenericsTableDataInfo<K8sApplicationPod> podList() {
return new GenericsTableDataInfo<>(microManagementService.getPodList());
}
@ApiOperation(value = "获取微服务运行状态监控数据")
@GetMapping("/pod/status/{podName}")
public GenericsAjaxResult<K8sPodContainerMetricOverview> podStatus(@PathVariable("podName") String podName,
@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return GenericsAjaxResult.success(microManagementService.getPodStatus(podName, step));
}
@ApiOperation(value = "获取微服务应用详情")
@GetMapping("/application/{kind}/{namespace}/{applicationName}/detail")
public GenericsAjaxResult<K8sApplication> applicationDetail(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName) {
return GenericsAjaxResult.success(microManagementService.getApplicationDetail(kind, namespace, applicationName));
}
@ApiOperation(value = "删除POD容器")
@DeleteMapping("/pod/{namespace}/{podName}")
public AjaxResult applicationDetail(@PathVariable("namespace") String namespace,
@PathVariable("podName") String podName,
@RequestParam(value = "isForce", required = false) @ApiParam(value = "是否强制删除") Boolean isForce) {
return toAjax(microManagementService.deletePod(namespace, podName, isForce));
}
@ApiOperation(value = "操作应用")
@PutMapping("/application/{kind}/{namespace}/{applicationName}/operate/{operationKey}")
public AjaxResult operateApplication(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@PathVariable("operationKey") String operationKey) {
return toAjax(microManagementService.operateApplication(kind, namespace, applicationName, operationKey));
}
@ApiOperation(value = "获取应用类型")
@GetMapping("/applicationType")
public GenericsTableDataInfo<ApplicationTypeVo> applicationType() {
return new GenericsTableDataInfo<>(microManagementService.getApplicationTypeList());
}
@ApiOperation(value = "创建应用")
@PostMapping("/application/{kind}")
public AjaxResult createApplication(@PathVariable("kind") String kind,
@RequestBody String yamlContent) {
return AjaxResult.success(microManagementService.createApplication(kind, yamlContent));
}
@ApiOperation(value = "获取应用Yaml文件")
@GetMapping(value = "/application/{kind}/{namespace}/{applicationName}/yamlFile", produces = "application/yaml")
public JSONObject getApplicationYamlFile(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName) {
return microManagementService.getApplicationYamlFile(kind, namespace, applicationName);
}
@ApiOperation(value = "编辑应用Yaml文件")
@PutMapping("/application/{kind}/{namespace}/{applicationName}")
public AjaxResult editApplication(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestBody String yamlContent) {
return toAjax(microManagementService.editApplication(kind, namespace, applicationName, yamlContent));
}
@ApiOperation(value = "删除应用")
@DeleteMapping("/application/{kind}/{namespace}/{applicationName}")
public AjaxResult deleteApplication(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName) {
return toAjax(microManagementService.deleteApplication(kind, namespace, applicationName));
}
@ApiOperation(value = "获取应用环境变量")
@GetMapping(value = "/application/{kind}/{namespace}/{applicationName}/env")
public GenericsTableDataInfo<K8sApplicationEnv> getApplicationEnv(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName) {
return new GenericsTableDataInfo<>(microManagementService.getApplicationEnv(kind, namespace, applicationName));
}
@ApiOperation(value = "获取应用环境变量相关可选参数")
@GetMapping(value = "/application/{namespace}/envOptionalParameters")
public GenericsTableDataInfo<K8sApplicationEnvOptionalParameter> getEnvOptionalParameters(@PathVariable("namespace") String namespace) {
return new GenericsTableDataInfo<>(microManagementService.getEnvOptionalParameters(namespace));
}
@ApiOperation(value = "更新应用环境变量")
@PutMapping("/application/{kind}/{namespace}/{applicationName}/env")
public AjaxResult updateApplicationEnv(@PathVariable("kind") String kind,
@PathVariable("namespace") String namespace,
@PathVariable("applicationName") String applicationName,
@RequestBody List<K8sApplicationEnv> k8sApplicationEnvList) {
return toAjax(microManagementService.updateApplicationEnv(kind, namespace, applicationName, k8sApplicationEnvList));
}
@ApiOperation(value = "获取应用环境变量类型")
@GetMapping(value = "/application/envTypes")
public GenericsTableDataInfo<ApplicationEnvTypeVo> getEnvTypes() {
return new GenericsTableDataInfo<>(microManagementService.getEnvTypes());
}
@ApiOperation(value = "获取网关路由配置")
@GetMapping(value = "/gateway/routeConfig")
public GenericsTableDataInfo<GatewayRouteConfig> getGatewayRouteConfig() {
return new GenericsTableDataInfo<>(microManagementService.getGatewayRouteConfig());
}
@ApiOperation(value = "更新网关路由配置")
@PutMapping(value = "/gateway/routeConfig")
public AjaxResult updateGatewayRouteConfig(@RequestBody List<GatewayRouteConfig> gatewayRouteConfigList) {
return toAjax(microManagementService.updateGatewayRouteConfig(gatewayRouteConfigList));
}
@ApiOperation(value = "获取网关安全认证配置")
@GetMapping(value = "/gateway/safetyConfig")
public GenericsAjaxResult<GatewaySafetyConfig> getGatewaySafetyConfig() {
return GenericsAjaxResult.success(microManagementService.getGatewaySafetyConfig());
}
@ApiOperation(value = "更新网关安全认证配置")
@PutMapping(value = "/gateway/safetyConfig")
public AjaxResult updateGatewaySafetyConfig(@RequestBody GatewaySafetyConfig gatewaySafetyConfig) {
return toAjax(microManagementService.updateGatewaySafetyConfig(gatewaySafetyConfig));
}
@ApiOperation(value = "获取网关白名单配置")
@GetMapping(value = "/gateway/whitelistConfig")
public GenericsAjaxResult<GatewayWhitelistConfig> getGatewayWhitelistConfig() {
return GenericsAjaxResult.success(microManagementService.getGatewayWhitelistConfig());
}
@ApiOperation(value = "更新网关白名单配置")
@PutMapping(value = "/gateway/whitelistConfig")
public AjaxResult updateGatewayWhitelistConfig(@RequestBody GatewayWhitelistConfig gatewayWhitelistConfig) {
return toAjax(microManagementService.updateGatewayWhitelistConfig(gatewayWhitelistConfig));
}
@ApiOperation(value = "获取存在网关异常处理记录的日期列表")
@GetMapping(value = "/gateway/exceptionHandingDateList")
public GenericsTableDataInfo<String> getExceptionHandingDateList() {
return new GenericsTableDataInfo<>(microManagementService.getExceptionHandingDateList());
}
@ApiOperation(value = "获取网关异常处理记录")
@GetMapping(value = "/gateway/exceptionHandingRecordList")
public GenericsTableDataInfo<String> getExceptionHandingRecordList(@RequestParam(value = "date", required = false) @ApiParam(value = "查看日期") String date) {
return new GenericsTableDataInfo<>(microManagementService.getExceptionHandingRecordList(date));
}
@ApiOperation(value = "获取网关限流跳转参数")
@GetMapping(value = "/gateway/flowLimitingPara")
public GenericsAjaxResult<JSONObject> getGatewayFlowLimitingPara() {
return GenericsAjaxResult.success(microManagementService.getGatewayFlowLimitingPara());
}
@ApiOperation(value = "获取链路追踪应用列表")
@GetMapping(value = "/application/linkTrackingList")
public GenericsTableDataInfo<LinkTrackingApplication> getLinkTrackingList(@RequestParam(value = "applicationName", required = false) @ApiParam(value = "应用名称") String applicationName) {
return new GenericsTableDataInfo<>(microManagementService.getLinkTrackingList(applicationName));
}
@ApiOperation(value = "获取K8S事件关键活动操作列表")
@GetMapping(value = "/k8sEvent/keyActivitiesOperationList")
public GenericsTableDataInfo<CommonTypeVo> getKeyActivitiesOperationList() {
return new GenericsTableDataInfo<>(microManagementService.getK8SEventReasonList("Normal"));
}
@ApiOperation(value = "获取K8S事件关键活动列表")
@GetMapping(value = "/k8sEvent/keyActivitiesList")
public GenericsTableDataInfo<K8SEvent> getKeyActivitiesList(@RequestParam(value = "reason", required = false) @ApiParam(value = "操作类型") String reason,
@RequestParam(value = "startTime", required = false) @ApiParam(value = "开始时间") Date startTime,
@RequestParam(value = "endTime", required = false) @ApiParam(value = "结束时间") Date endTime,
@RequestParam(value = "isAscOrderByOccurredTime", required = false) @ApiParam(value = "是否根据发生时间升序排序(默认根据发生时间降序排序)") Boolean isAscOrderByOccurredTime,
@RequestParam(value = "pageNum", required = false) @ApiParam(value = "当前记录起始索引") Integer pageNum,
@RequestParam(value = "pageSize", required = false) @ApiParam(value = "每页显示记录数") Integer pageSize) {
return microManagementService.getK8SEventList("Normal", reason, startTime, endTime, isAscOrderByOccurredTime, pageNum, pageSize);
}
@ApiOperation(value = "获取K8S事异常结束事件原因列表")
@GetMapping(value = "/k8sEvent/exceptionEndReasonList")
public GenericsTableDataInfo<CommonTypeVo> getExceptionEndReasonList() {
return new GenericsTableDataInfo<>(microManagementService.getK8SEventReasonList("Warning"));
}
@ApiOperation(value = "获取K8S事件异常结束事件列表")
@GetMapping(value = "/k8sEvent/exceptionEndList")
public GenericsTableDataInfo<K8SEvent> getExceptionEndListList(@RequestParam(value = "reason", required = false) @ApiParam(value = "操作类型") String reason,
@RequestParam(value = "startTime", required = false) @ApiParam(value = "开始时间") Date startTime,
@RequestParam(value = "endTime", required = false) @ApiParam(value = "结束时间") Date endTime,
@RequestParam(value = "isAscOrderByOccurredTime", required = false) @ApiParam(value = "是否根据发生时间升序排序(默认根据发生时间降序排序)") Boolean isAscOrderByOccurredTime,
@RequestParam(value = "pageNum", required = false) @ApiParam(value = "当前记录起始索引") Integer pageNum,
@RequestParam(value = "pageSize", required = false) @ApiParam(value = "每页显示记录数") Integer pageSize) {
return microManagementService.getK8SEventList("Warning", reason, startTime, endTime, isAscOrderByOccurredTime, pageNum, pageSize);
}
@ApiOperation(value = "获取K8S资源对象列表")
@GetMapping(value = "/k8sEvent/resourceObjectList")
public GenericsTableDataInfo<CommonTypeVo> getResourceObjectList() {
return new GenericsTableDataInfo<>(microManagementService.getResourceObjectList());
}
@ApiOperation(value = "获取K8S资源占用列表")
@GetMapping(value = "/k8sEvent/resourceUsageList")
@ApiImplicitParams({
@ApiImplicitParam(name = "pageNum", value = "当前记录起始索引", paramType = "query", dataType = "Integer"),
@ApiImplicitParam(name = "pageSize", value = "每页显示记录数", paramType = "query", dataType = "Integer")
})
public GenericsTableDataInfo<K8SResourceUsage> getResourceUsageList(@RequestParam(value = "searchResourceObject", required = false) @ApiParam(value = "资源对象") String searchResourceObject,
@RequestParam(value = "startTime", required = false) @ApiParam(value = "开始时间") Date startTime,
@RequestParam(value = "endTime", required = false) @ApiParam(value = "结束时间") Date endTime) {
return getAllGenericsDataTableToPage(microManagementService.getResourceUsageList(searchResourceObject, startTime, endTime));
}
}

View File

@ -0,0 +1,71 @@
package com.microservices.mon.k8s.controller;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.domain.GenericsAjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.mon.k8s.domain.*;
import com.microservices.mon.k8s.domain.vo.ApplicationEnvTypeVo;
import com.microservices.mon.k8s.domain.vo.ApplicationTypeVo;
import com.microservices.mon.k8s.service.IMicroManagementService;
import com.microservices.mon.k8s.service.IMicroStatusService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("microStatus")
@Api(tags = "微服务状态监控")
@RefreshScope
public class MicroStatusController extends BaseController {
@Autowired
private IMicroStatusService microStatusService;
@ApiOperation(value = "获取容器启停状态")
@GetMapping("/podStartStopStatus")
public GenericsAjaxResult<K8sPodStartStopStatus> getPodStartStopStatus() {
return GenericsAjaxResult.success(microStatusService.getPodStartStopStatus());
}
@ApiOperation(value = "获取容器CPU使用率")
@GetMapping("/podCpuUsage")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodCpuUsage(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodCpuUsage(step));
}
@ApiOperation(value = "获取容器内存使用情况")
@GetMapping("/podMemoryUsage")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodMemoryUsage(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodMemoryUsage(step));
}
@ApiOperation(value = "获取容器硬盘读取情况")
@GetMapping("/podDiskReads")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodDiskReads(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodDiskReads(step));
}
@ApiOperation(value = "获取容器硬盘写入情况")
@GetMapping("/podDiskWrites")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodDiskWrites(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodDiskWrites(step));
}
@ApiOperation(value = "获取容器网络接收情况")
@GetMapping("/podNetworkReception")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodNetworkReception(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodNetworkReception(step));
}
@ApiOperation(value = "获取容器网络发送情况")
@GetMapping("/podNetworkTransmission")
public GenericsTableDataInfo<K8sPodContainerMetric> getPodNetworkTransmission(@RequestParam(value = "step", required = false) @ApiParam(value = "统计频率") Long step) {
return new GenericsTableDataInfo<>(microStatusService.getPodNetworkTransmission(step));
}
}

View File

@ -0,0 +1,37 @@
package com.microservices.mon.k8s.domain;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import lombok.Getter;
/**
* @author OTTO
*/
@Getter
public enum ApplicationEnvType {
KEY_VALUE_PAIR("keyValuePair", "键值对"),
RESOURCE("resource", "资源字段"),
CONFIG_MAP_KEY("configMapKey", "ConfigMap键"),
SECRET_KEY("secretKey", "Secret键"),
POD_FIELD("podField", "容器字段"),
SECRET("secret", "Secret整体"),
CONFIGMAP("configMap", "ConfigMap整体");
private final String key;
private final String alias;
ApplicationEnvType(String key, String alias) {
this.key = key;
this.alias = alias;
}
public static ApplicationEnvType getApplicationEnvTypeByKey(String type) {
for (ApplicationEnvType applicationEnvType : ApplicationEnvType.values()) {
if (StringUtils.equals(applicationEnvType.getKey(), type)) {
return applicationEnvType;
}
}
throw new ServiceException("该应用环境变量类型不存在(环境变量类型[%s])", type);
}
}

View File

@ -0,0 +1,22 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("应用功能")
public class ApplicationFunction {
@ApiModelProperty(value = "应用功能标识")
private String key;
@ApiModelProperty(value = "应用功能名称")
private String name;
@ApiModelProperty(value = "应用功能访问所需参数")
private JSONObject variables;
public void setKeyAndName(ApplicationFunctionType applicationFunctionType) {
this.key = applicationFunctionType.getKey();
this.name = applicationFunctionType.getName();
}
}

View File

@ -0,0 +1,41 @@
package com.microservices.mon.k8s.domain;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* @author OTTO
*/
@Getter
public enum ApplicationFunctionType {
LINK_TRACKING("LinkTracking", "链路追踪", "mon-skywalking-key"),
FLOW_CONTROL("FlowControl", "流量控制", "mon-sentinel-key"),
CONFIGURATION_MANAGEMENT("ConfigurationManagement", "配置管理", ""),
REGISTRATION_CENTER("RegistrationCenter", "注册中心", "mon-nacos-key"),
GATEWAY_MANAGEMENT("GatewayManagement", "网关管理", "mon-gateway-key"),
START_AND_STOP_MANAGEMENT("StartAndStopManagement", "启停管理", "");
private final String key;
private final String name;
private final String k8sLabel;
ApplicationFunctionType(String key, String name, String k8sLabel) {
this.key = key;
this.name = name;
this.k8sLabel = k8sLabel;
}
public static ApplicationFunctionType getByLabelsKey(String k8sLabel) {
if (StringUtils.isNotEmpty(k8sLabel)) {
for (ApplicationFunctionType type : ApplicationFunctionType.values()) {
if (type.getK8sLabel().equals(k8sLabel)) {
return type;
}
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("应用请求次数对象")
public class ApplicationRequestCountData {
@ApiModelProperty(value = "序号")
private Integer number;
@ApiModelProperty(value = "应用名称")
private String applicationAlias;
@ApiModelProperty(value = "请求次数")
private Long requestCount;
}

View File

@ -0,0 +1,93 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.mon.k8s.domain.vo.RancherApplicationVo;
import lombok.Getter;
/**
* @author OTTO
*/
@Getter
public enum ApplicationStartAndStopType {
START("start", "启动", false),
STOP("stop", "停止", false),
FORCE_STOP("forceStop", "强制停止", false),
RESTART("restart", "重启", true),
PAUSED("paused", "暂停", true),
RESUME("resume", "恢复", true);
private final String key;
private final String name;
private final Boolean isDaemonSetOperation;
ApplicationStartAndStopType(String key, String name, Boolean isDaemonSetOperation) {
this.key = key;
this.name = name;
this.isDaemonSetOperation = isDaemonSetOperation;
}
public static JSONArray getOperationList(String kind, K8sApplication k8sApplication) {
JSONArray jsonArray = new JSONArray();
if (k8sApplication == null) {
return jsonArray;
}
for (ApplicationStartAndStopType value : values()) {
if ("DaemonSet".equals(kind) && !value.isDaemonSetOperation) {
continue;
}
boolean shouldAdd = false;
switch (value) {
case START:
if (k8sApplication.getReplicas() == null || k8sApplication.getReplicas() == 0) {
shouldAdd = true;
}
break;
case STOP:
case FORCE_STOP:
case RESTART:
if (k8sApplication.getReplicas() != null && k8sApplication.getReplicas() > 0) {
shouldAdd = true;
}
break;
case PAUSED:
if (!k8sApplication.getPaused()) {
shouldAdd = true;
}
break;
case RESUME:
if (k8sApplication.getPaused()) {
shouldAdd = true;
}
break;
}
if (shouldAdd) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("operationKey", value.getKey());
jsonObject.put("operationName", value.getName());
jsonArray.add(jsonObject);
}
}
return jsonArray;
}
public static boolean containsKey(String operationKey) {
for (ApplicationStartAndStopType value : values()) {
if (StringUtils.equals(value.getKey(), operationKey)) {
return true;
}
}
return false;
}
public static ApplicationStartAndStopType getByKey(String operationKey) {
for (ApplicationStartAndStopType value : values()) {
if (StringUtils.equals(value.getKey(), operationKey)) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,44 @@
package com.microservices.mon.k8s.domain;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import lombok.Getter;
/**
* @author OTTO
*/
@Getter
public enum ApplicationType {
DAEMON_SET("DaemonSet", "apps.daemonsets", "确保每个节点(或指定节点)上都运行一个容器副本", "daemonsets"),
DEPLOYMENT("Deployment", "apps.deployments", "管理无状态应用的容器副本,支持声明式更新和回滚", "deployments"),
STATEFUL_SET("StatefulSet", "apps.statefulsets", "管理有状态应用,为容器提供稳定的标识和持久化存储", "statefulsets");
private final String key;
private final String fullName;
private final String description;
private final String name;
ApplicationType(String key, String fullName, String description, String name) {
this.key = key;
this.fullName = fullName;
this.description = description;
this.name = name;
}
public static ApplicationType getApplicationTypeByKey(String key) {
for (ApplicationType value : ApplicationType.values()) {
if (StringUtils.equalsIgnoreCase(value.getKey(), key)) {
return value;
}
}
throw new ServiceException("该应用类型不存在(应用类型[%s])", key);
}
public static Boolean isApplicationType(String key) {
for (ApplicationType value : ApplicationType.values()) {
if (StringUtils.equalsIgnoreCase(value.getKey(), key)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,20 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("网关路由配置")
public class GatewayRouteConfig {
@ApiModelProperty(value = "路由标识")
private String id;
@ApiModelProperty(value = "目标地址")
private String uri;
@ApiModelProperty(value = "转发规则")
private List<String> predicates;
@ApiModelProperty(value = "过滤规则")
private List<String> filters;
}

View File

@ -0,0 +1,22 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("网关安全认证配置")
public class GatewaySafetyConfig {
@ApiModelProperty(value = "允许请求来源")
private List<String> allowedOriginPatterns;
@ApiModelProperty(value = "允许请求头")
private List<String> allowedHeaders;
@ApiModelProperty(value = "预检请求缓存时间")
private Integer maxAge=360000;
@ApiModelProperty(value = "允许请求方法")
private List<String> allowedMethods;
@ApiModelProperty(value = "允许证书")
private Boolean allowCredentials = true;
}

View File

@ -0,0 +1,14 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("网关白名单配置")
public class GatewayWhitelistConfig {
@ApiModelProperty(value = "白名单列表")
private List<String> whites;
}

View File

@ -0,0 +1,75 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("K8S事件")
public class K8SEvent {
@ApiModelProperty(value = "事件id")
private String docId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "发生时间")
private Date occurredTime;
@ApiModelProperty(value = "操作类型/异常原因")
private String reason;
@ApiModelProperty(value = "事件发生对象")
private String object;
@ApiModelProperty(value = "事件发生子对象")
private String childObject;
@ApiModelProperty(value = "所处命名空间")
private String namespace;
@ApiModelProperty(value = "运行结果/详细信息")
private String message;
public static GenericsTableDataInfo<K8SEvent> toK8SEventTable(JSONObject esQueryRes) {
List<K8SEvent> k8SEventList = new ArrayList<>();
Long total = getK8SEventTotal(esQueryRes, k8SEventList);
return new GenericsTableDataInfo<>(k8SEventList, total);
}
public static List<K8SEvent> toK8SEventList(JSONObject esQueryRes) {
List<K8SEvent> k8SEventList = new ArrayList<>();
getK8SEventTotal(esQueryRes, k8SEventList);
return k8SEventList;
}
private static Long getK8SEventTotal(JSONObject esQueryRes, List<K8SEvent> k8SEventList) {
Long total = 0L;
if (esQueryRes.getJSONObject("hits") != null && esQueryRes.getJSONObject("hits").getJSONArray("hits") != null) {
JSONObject hits = esQueryRes.getJSONObject("hits");
JSONArray dataHits = hits.getJSONArray("hits");
for (int i = 0; i < dataHits.size(); i++) {
JSONObject dataHit = dataHits.getJSONObject(i);
K8SEvent k8SEvent = new K8SEvent();
k8SEvent.setDocId(dataHit.getString("_id"));
JSONObject source = dataHit.getJSONObject("_source");
k8SEvent.setOccurredTime(source.getDate("lastTimestamp"));
k8SEvent.setReason(source.getString("reason"));
if (source.getJSONObject("involvedObject") != null) {
JSONObject involvedObject = source.getJSONObject("involvedObject");
k8SEvent.setObject(String.format("%s:%s", involvedObject.getString("kind"), involvedObject.getString("name")));
k8SEvent.setChildObject(involvedObject.getString("fieldPath"));
k8SEvent.setNamespace(involvedObject.getString("namespace"));
}
k8SEvent.setMessage(source.getString("message"));
k8SEventList.add(k8SEvent);
}
total = hits.getJSONObject("total").getLong("value");
}
return total;
}
}

View File

@ -0,0 +1,26 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("K8S资源占用情况")
public class K8SResourceUsage {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "发生时间")
private Date occurredTime;
@ApiModelProperty(value = "资源类型")
private String resourceType;
@ApiModelProperty(value = "资源对象")
private String resourceObject;
@ApiModelProperty(value = "资源占用情况")
private String message;
}

View File

@ -0,0 +1,59 @@
package com.microservices.mon.k8s.domain;
import com.microservices.mon.k8s.utils.MonUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("K8S应用")
public class K8sApplication {
@ApiModelProperty(value = "应用Id")
private String id;
@ApiModelProperty(value = "应用名称")
private String name;
@ApiModelProperty(value = "应用别名")
private String alias;
@ApiModelProperty(value = "应用类型")
private String kind;
@ApiModelProperty(value = "所属命名空间")
private String namespace;
@ApiModelProperty(value = "副本数量")
private Integer replicas;
@ApiModelProperty(value = "有效副本数量(当有效副本数为0时代表应用失败呈现红色当有效副本数大于0但小于副本数时代表应用部分失败呈现橙色当有效副本数等于副本数代表应用正常呈现蓝色)")
private Integer readyReplicas;
@ApiModelProperty(value = "容器选择器")
private String podSelector;
@ApiModelProperty(value = "Docker镜像")
private String dockerImage;
@ApiModelProperty(value = "重启次数")
private Integer restartCount;
@ApiModelProperty(value = "存活时间")
private String survivalTime;
@ApiModelProperty(value = "运行状态")
private K8sApplicationAndPodState state;
@ApiModelProperty(value = "暂停编排")
private Boolean paused;
private List<K8sApplicationPod> podList;
// public void setDockerImage(String dockerImage) {
// this.dockerImage = MonUtils.getDockerImage(dockerImage);
// }
}

View File

@ -0,0 +1,121 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("应用状态")
public class K8sApplicationAndPodState {
@ApiModelProperty(value = "状态标识")
private String name;
@ApiModelProperty(value = "应用展示名称(中文)")
private String alias;
@ApiModelProperty(value = "是否错误")
private Boolean isError;
@ApiModelProperty(value = "状态消息")
private String message;
public static K8sApplicationAndPodState k8sApplicationStopState(String stateName) {
K8sApplicationAndPodState state = new K8sApplicationAndPodState();
state.setName(stateName);
state.setIsError(false);
state.setMessage("");
state.setApplicationAliasByName(stateName);
return state;
}
public void setApplicationAliasByName(String name) {
this.name = name;
if ("active".equalsIgnoreCase(name)) {
this.alias = "运行中";
} else if ("updating".equalsIgnoreCase(name)) {
this.alias = "更新中";
} else if ("in-progress".equalsIgnoreCase(name)) {
this.alias = "处理中";
} else if ("error".equalsIgnoreCase(name)) {
this.alias = "运行失败";
} else if ("paused".equalsIgnoreCase(name)) {
this.alias = "暂停编排";
} else if ("pending".equalsIgnoreCase(name)) {
this.alias = "等待中";
} else if ("stop".equalsIgnoreCase(name)) {
this.alias = "已停止运行";
} else {
this.alias = name;
}
}
public void setPodAliasAndErrorByName(String name) {
this.name = name;
this.isError=true;
if ("imagePullBackOff".equalsIgnoreCase(name)) {
this.alias = "镜像拉取失败";
} else if ("pending".equalsIgnoreCase(name)) {
this.alias = "等待中";
this.isError=false;
} else if ("running".equalsIgnoreCase(name)) {
this.alias = "运行中";
this.isError=false;
} else if ("podScheduled".equalsIgnoreCase(name)) {
this.alias = "容器已调度";
this.isError=false;
} else if ("containersReady".equalsIgnoreCase(name)) {
this.alias = "容器已就绪";
this.isError=false;
} else if ("initialized".equalsIgnoreCase(name)) {
this.alias = "容器已初始化";
this.isError=false;
} else if ("ready".equalsIgnoreCase(name)) {
this.alias = "容器已准备就绪";
this.isError=false;
} else if ("crashLoopBackOff".equalsIgnoreCase(name)) {
this.alias = "容器崩溃循环重启";
} else if ("invalidImageName".equalsIgnoreCase(name)) {
this.alias = "无法解析镜像名称";
} else if ("imageInspectError".equalsIgnoreCase(name)) {
this.alias = "无法校验镜像";
} else if ("errImageNeverPull".equalsIgnoreCase(name)) {
this.alias = "策略禁止拉取镜像";
} else if ("registryUnavailable".equalsIgnoreCase(name)) {
this.alias = "连接不到镜像中心";
} else if ("errImagePull".equalsIgnoreCase(name)) {
this.alias = "拉取镜像出错";
} else if ("createContainerConfigError".equalsIgnoreCase(name)) {
this.alias = "不能创建容器配置";
} else if ("createContainerError".equalsIgnoreCase(name)) {
this.alias = "创建容器失败";
} else if ("runContainerError".equalsIgnoreCase(name)) {
this.alias = "启动容器失败";
} else if ("postStartHookError".equalsIgnoreCase(name)) {
this.alias = "执行hook报错";
} else if ("containersNotInitialized".equalsIgnoreCase(name)) {
this.alias = "容器未初始化完毕";
} else if ("containersNotReady".equalsIgnoreCase(name)) {
this.alias = "容器未准备完毕";
} else if ("containerCreating".equalsIgnoreCase(name)) {
this.alias = "容器创建中";
this.isError=false;
} else if ("podInitializing".equalsIgnoreCase(name)) {
this.alias = "Pod 初始化中";
this.isError=false;
} else if ("dockerDaemonNotReady".equalsIgnoreCase(name)) {
this.alias = "Docker 未完全启动";
} else if ("networkPluginNotReady".equalsIgnoreCase(name)) {
this.alias = "网络插件未完全启动";
} else if ("evicted".equalsIgnoreCase(name)) {
this.alias = "Pod 被驱赶";
} else if ("terminating".equalsIgnoreCase(name)) {
this.alias = "删除中";
this.isError=false;
} else {
this.alias = name;
}
}
}

View File

@ -0,0 +1,122 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("K8S应用环境变量")
public class K8sApplicationEnv {
@ApiModelProperty(value = "环境变量类型")
private String type;
@ApiModelProperty(value = "环境变量键")
private String key;
@ApiModelProperty(value = "环境变量值")
private JSONObject valueObj;
/**
* Kubernetes 容器 env 数组中的单个元素JSONObject 形式转换成内部统一的 K8sApplicationEnv 对象
*
* <p>支持解析以下四种 value 来源</p>
* <ul>
* <li>普通键值对{@code {"name":"KEY","value":"VAL"}}</li>
* <li>资源字段引用{@code {"name":"CPU_LIMIT","valueFrom":{"resourceFieldRef":{...}}}}</li>
* <li>ConfigMap 键引用{@code {"name":"CM_KEY","valueFrom":{"configMapKeyRef":{...}}}}</li>
* <li>Secret 键引用{@code {"name":"SECRET_KEY","valueFrom":{"secretKeyRef":{...}}}}</li>
* <li>Pod 字段引用{@code {"name":"POD_IP","valueFrom":{"fieldRef":{...}}}}</li>
* </ul>
*
* @param envJsonObject 单条 env 的原始 JSON 对象允许为 null
* @return 转换后的 K8sApplicationEnv 实例无法识别或入参为 null 时返回 null
*/
public static K8sApplicationEnv envJsonObjectToK8sApplicationEnv(JSONObject envJsonObject) {
K8sApplicationEnv k8sApplicationEnv = null;
if (envJsonObject != null) {
JSONObject valueObj = new JSONObject();
if (envJsonObject.containsKey("value")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envJsonObject.getString("name"));
k8sApplicationEnv.setType(ApplicationEnvType.KEY_VALUE_PAIR.getKey());
valueObj.put("value", envJsonObject.getString("value"));
k8sApplicationEnv.setValueObj(valueObj);
} else if (envJsonObject.containsKey("valueFrom")) {
JSONObject valueFromJson = envJsonObject.getJSONObject("valueFrom");
if (valueFromJson.containsKey("resourceFieldRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envJsonObject.getString("name"));
k8sApplicationEnv.setType(ApplicationEnvType.RESOURCE.getKey());
JSONObject resourceFieldRef = valueFromJson.getJSONObject("resourceFieldRef");
valueObj.put("name", resourceFieldRef.getString("containerName"));
valueObj.put("key", resourceFieldRef.getString("resource"));
k8sApplicationEnv.setValueObj(valueObj);
} else if (valueFromJson.containsKey("configMapKeyRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envJsonObject.getString("name"));
k8sApplicationEnv.setType(ApplicationEnvType.CONFIG_MAP_KEY.getKey());
JSONObject configMapKeyRef = valueFromJson.getJSONObject("configMapKeyRef");
valueObj.put("name", configMapKeyRef.getString("name"));
valueObj.put("key", configMapKeyRef.getString("key"));
k8sApplicationEnv.setValueObj(valueObj);
} else if (valueFromJson.containsKey("secretKeyRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envJsonObject.getString("name"));
k8sApplicationEnv.setType(ApplicationEnvType.SECRET_KEY.getKey());
JSONObject secretKeyRef = valueFromJson.getJSONObject("secretKeyRef");
valueObj.put("name", secretKeyRef.getString("name"));
valueObj.put("key", secretKeyRef.getString("key"));
k8sApplicationEnv.setValueObj(valueObj);
} else if (valueFromJson.containsKey("fieldRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envJsonObject.getString("name"));
k8sApplicationEnv.setType(ApplicationEnvType.POD_FIELD.getKey());
JSONObject fieldRef = valueFromJson.getJSONObject("fieldRef");
valueObj.put("value", fieldRef.getString("fieldPath"));
k8sApplicationEnv.setValueObj(valueObj);
}
}
}
return k8sApplicationEnv;
}
/**
* Kubernetes 容器 envFrom 数组中的单个元素JSONObject 形式转换成内部统一的 K8sApplicationEnv 对象
*
* <p>支持解析以下两种来源</p>
* <ul>
* <li>整个 ConfigMap{@code {"prefix":"PRE_","configMapRef":{"name":"cm-name"}}}</li>
* <li>整个 Secret{@code {"prefix":"PRE_","secretRef":{"name":"secret-name"}}}</li>
* </ul>
*
* <p>注意prefix 可能为 null</p>
*
* @param envFromJson 单条 envFrom 的原始 JSON 对象允许为 null
* @return 转换后的 K8sApplicationEnv 实例无法识别或入参为 null 时返回 null
*/
public static K8sApplicationEnv envFromJsonObjectToK8sApplicationEnv(JSONObject envFromJson) {
K8sApplicationEnv k8sApplicationEnv = null;
if (envFromJson != null) {
JSONObject valueObj = new JSONObject();
if (envFromJson.containsKey("configMapRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envFromJson.getString("prefix"));
k8sApplicationEnv.setType(ApplicationEnvType.CONFIGMAP.getKey());
JSONObject configMapRef = envFromJson.getJSONObject("configMapRef");
valueObj.put("name", configMapRef.getString("name"));
k8sApplicationEnv.setValueObj(valueObj);
} else if (envFromJson.containsKey("secretRef")) {
k8sApplicationEnv = new K8sApplicationEnv();
k8sApplicationEnv.setKey(envFromJson.getString("prefix"));
k8sApplicationEnv.setType(ApplicationEnvType.SECRET.getKey());
JSONObject secretRef = envFromJson.getJSONObject("secretRef");
valueObj.put("name", secretRef.getString("name"));
k8sApplicationEnv.setValueObj(valueObj);
}
}
return k8sApplicationEnv;
}
}

View File

@ -0,0 +1,23 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("K8S应用环境变量")
public class K8sApplicationEnvOptionalParameter {
@ApiModelProperty(value = "环境变量参数类型")
private String type;
@ApiModelProperty(value = "环境变量参数名称")
private String name;
@ApiModelProperty(value = "环境变量参数键值")
private List<String> keys;
}

View File

@ -0,0 +1,65 @@
package com.microservices.mon.k8s.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.mon.k8s.utils.MonUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("K8S容器")
public class K8sApplicationPod {
@ApiModelProperty(value = "容器Id")
private String id;
@ApiModelProperty(value = "容器名称")
private String name;
@ApiModelProperty(value = "所属命名空间")
private String namespace;
@ApiModelProperty(value = "容器Ip")
private String ip;
@ApiModelProperty(value = "重启次数")
private Integer restartCount;
@ApiModelProperty(value = "运行状态")
private K8sApplicationAndPodState state;
@ApiModelProperty(value = "是否正常运行")
private Boolean isRunning;
@ApiModelProperty(value = "存活时间")
private String survivalTime;
@ApiModelProperty(value = "所有权")
private String serviceAccountName;
@ApiModelProperty(value = "容器端口")
private String ports;
@ApiModelProperty(value = "Docker镜像")
private String dockerImage;
@ApiModelProperty(value = "副本数量")
private Integer replicas;
@ApiModelProperty(value = "有效副本数量")
private Integer readyReplicas;
@ApiModelProperty(value = "所属堆栈")
private String stack;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
// public void setDockerImage(String dockerImage) {
// this.dockerImage = MonUtils.getDockerImage(dockerImage);
// }
}

View File

@ -0,0 +1,27 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("集群节点")
public class K8sClusterNode {
@ApiModelProperty(value = "节点标识")
private String id;
@ApiModelProperty(value = "节点名称")
private String name;
@ApiModelProperty(value = "节点IP")
private String ip;
@ApiModelProperty(value = "节点角色Master/Worker")
private String role;
@ApiModelProperty(value = "是否就绪")
private Boolean Ready;
}

View File

@ -0,0 +1,116 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.mon.k8s.domain.vo.PrometheusResVo;
import com.microservices.mon.k8s.domain.vo.PrometheusResultVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@Data
@ApiModel("集群状态")
public class K8sClusterStatus {
@ApiModelProperty(value = "集群节点数量")
private Integer nodeCount;
@ApiModelProperty(value = "不可用节点数量")
private Integer unavailableNodeCount;
@ApiModelProperty(value = "容器使用率")
private Float podUsage;
@ApiModelProperty(value = "CPU使用率")
private Float cpuUsage;
@ApiModelProperty(value = "内存使用率")
private Float memoryUsage;
@ApiModelProperty(value = "最近24小时请求总数")
private Long totalRequestCountToday;
@ApiModelProperty(value = "最近24小时请求平均处理时间(单位ms)")
private Float averageRequestProcessingTimeToday;
@ApiModelProperty(value = "最近24小时请求成功率")
private Float requestSuccessRateToday;
@ApiModelProperty(value = "微服务应用数量")
private Integer microserviceApplicationCount;
@ApiModelProperty(value = "最近24小时微服务重启次数")
private Integer microserviceRestartCountToday;
@ApiModelProperty(value = "微服务Pod运行状态Key代表状态Value代表数量")
private HashMap<String, Integer> podStatus;
public void setNodeCount(PrometheusResVo prometheusResVo) {
this.nodeCount = Integer.parseInt(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setUnavailableNodeCount(PrometheusResVo prometheusResVo) {
this.unavailableNodeCount = Integer.parseInt(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setCpuUsage(PrometheusResVo prometheusResVo) {
this.cpuUsage = Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setPodUsage(PrometheusResVo prometheusResVo) {
this.podUsage = Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setMemoryUsage(PrometheusResVo prometheusResVo) {
this.memoryUsage = Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setTotalRequestCountToday(PrometheusResVo prometheusResVo) {
this.totalRequestCountToday = Long.parseLong(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setAverageRequestProcessingTimeToday(PrometheusResVo prometheusResVo) {
this.averageRequestProcessingTimeToday = Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setRequestSuccessRateToday(PrometheusResVo prometheusResVo) {
this.requestSuccessRateToday = Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setMicroserviceApplicationCount(List<K8sNamespace> k8sNamespaceList) {
this.microserviceApplicationCount = k8sNamespaceList.stream().map(x -> x.getApplicationList().size()).reduce(0, Integer::sum);
}
public void setMicroserviceRestartCountToday(PrometheusResVo prometheusResVo) {
this.microserviceRestartCountToday = (int) Float.parseFloat(PrometheusResVo.getPromStringValue(prometheusResVo));
}
public void setPodStatus(PrometheusResVo prometheusResVo) {
HashMap<String, Integer> podStatus = new HashMap<>();
List<PrometheusResultVo> resultList = PrometheusResVo.getPrometheusResultVoList(prometheusResVo);
for (PrometheusResultVo result : resultList) {
Integer value = result.getValue().getIntValue(1);
switch (result.getMetric().getString("phase")) {
case "Running":
podStatus.put("运行中", value);
break;
case "Pending":
podStatus.put("等待中", value);
break;
case "Succeeded":
podStatus.put("运行已完成", value);
break;
case "Unknown":
podStatus.put("未知", value);
break;
case "Failed":
podStatus.put("运行失败", value);
}
}
this.podStatus = podStatus;
}
}

View File

@ -0,0 +1,27 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("命名空间")
public class K8sNamespace {
@ApiModelProperty(value = "命名空间标识")
private String id;
@ApiModelProperty(value = "命名空间键值")
private String key;
@ApiModelProperty(value = "命名空间名称")
private String name;
@ApiModelProperty(value = "命名空间别名(中文)")
private String alias;
@ApiModelProperty(value = "应用列表")
private List<K8sApplication> applicationList;
}

View File

@ -0,0 +1,19 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("K8S Node状态")
public class K8sNodeStatus {
@ApiModelProperty(value = "节点IP")
private String nodeIp;
@ApiModelProperty(value = "CPU使用率")
private Float cpuUsage;
@ApiModelProperty(value = "内存使用率")
private Float memoryUsage;
}

View File

@ -0,0 +1,18 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("K8S容器运行状态监控")
public class K8sPodContainerMetric {
@ApiModelProperty(value = "容器名称")
private String podName;
@ApiModelProperty(value = "指标值")
private List<MetricCommonData> data;
}

View File

@ -0,0 +1,33 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("K8S容器运行状态监控总览")
public class K8sPodContainerMetricOverview {
@ApiModelProperty(value = "容器名称")
private String podName;
@ApiModelProperty(value = "CPU使用率单位%")
private List<MetricCommonData> cpuUsage;
@ApiModelProperty(value = "内存使用情况单位byte需前端换算")
private List<MetricCommonData> memoryUsage;
@ApiModelProperty(value = "磁盘写入情况单位byte/s需前端换算")
private List<MetricCommonData> diskWrites;
@ApiModelProperty(value = "磁盘读取情况单位byte/s需前端换算")
private List<MetricCommonData> diskReads;
@ApiModelProperty(value = "网络接收情况单位byte/s需前端换算")
private List<MetricCommonData> networkReception;
@ApiModelProperty(value = "网络发送情况单位byte/s需前端换算")
private List<MetricCommonData> networkTransmission;
}

View File

@ -0,0 +1,21 @@
package com.microservices.mon.k8s.domain;
import com.microservices.mon.k8s.domain.vo.PrometheusResVo;
import com.microservices.mon.k8s.domain.vo.PrometheusResultVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("K8S容器状态")
public class K8sPodContainerStatus {
@ApiModelProperty(value = "容器名称")
private String podName;
@ApiModelProperty(value = "CPU使用率")
private Float cpuUsage;
@ApiModelProperty(value = "内存使用情况")
private String memoryUsage;
}

View File

@ -0,0 +1,26 @@
package com.microservices.mon.k8s.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("K8S容器启停时间")
public class K8sPodStartStopDate {
@ApiModelProperty(value = "容器名称")
private String podName;
@ApiModelProperty(value = "启动时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startDate;
@ApiModelProperty(value = "停止时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date stopDate;
private Boolean isRunning = true;
}

View File

@ -0,0 +1,21 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("K8S容器启停状态")
public class K8sPodStartStopStatus {
@ApiModelProperty(value = "启动数")
private Integer startCount;
@ApiModelProperty(value = "总数")
private Integer podCount;
@ApiModelProperty(value = "启停时间列表")
private List<K8sPodStartStopDate> podStartStopDateList;
}

View File

@ -0,0 +1,26 @@
package com.microservices.mon.k8s.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("链路追踪应用")
public class LinkTrackingApplication {
@ApiModelProperty(value = "序号")
private Integer index;
@ApiModelProperty(value = "应用Id")
private String id;
@ApiModelProperty(value = "应用标识")
private String key;
@ApiModelProperty(value = "应用名称")
private String applicationName;
@ApiModelProperty(value = "负载")
private Float load;
@ApiModelProperty(value = "成功率")
private Float successRate;
@ApiModelProperty(value = "延迟")
private Float latency;
}

View File

@ -0,0 +1,39 @@
package com.microservices.mon.k8s.domain;
import com.alibaba.fastjson2.JSONArray;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("指标通用对象")
public class MetricCommonData {
@ApiModelProperty(value = "时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date date;
@ApiModelProperty(value = "指标值")
private Float value;
public static List<MetricCommonData> prometheusResultVoToMetric(JSONArray prometheusResultValues) {
List<MetricCommonData> metricCommonDataList = new ArrayList<>();
for (int i = 0; i < prometheusResultValues.size(); i++) {
JSONArray value = prometheusResultValues.getJSONArray(i);
MetricCommonData metricCommonData = new MetricCommonData();
metricCommonData.setDate(new Date(value.getLong(0) * 1000));
String valueStr = String.format("%.2f", value.getFloatValue(1));
metricCommonData.setValue(Float.parseFloat(valueStr));
metricCommonDataList.add(metricCommonData);
}
metricCommonDataList.sort(Comparator.comparing(MetricCommonData::getDate));
return metricCommonDataList;
}
}

View File

@ -0,0 +1,25 @@
package com.microservices.mon.k8s.domain;
import lombok.Getter;
@Getter
public enum SkywalkingMetricType {
SERVICE_CPM("cpmFunction", "cpmVariable", "service_cpm"),
SERVICE_SLA("slaFunction", "slaVariable", "service_sla"),
SERVICE_RESP_TIME("respTimeFunction", "respTimeVariable", "service_resp_time");
private final String functionName;
private final String functionVariable;
private final String metricKey;
SkywalkingMetricType(String functionName, String functionVariable, String metricKey) {
this.functionName = functionName;
this.functionVariable = functionVariable;
this.metricKey = metricKey;
}
public String getFunctionVariable(Integer index) {
return this.functionVariable+index;
}
public String getFunctionName(Integer index) {
return this.functionName+index;
}
}

View File

@ -0,0 +1,12 @@
package com.microservices.mon.k8s.domain.vo;
import lombok.Data;
/**
* @author OTTO
*/
@Data
public class ApplicationEnvTypeVo {
private String key;
private String alias;
}

View File

@ -0,0 +1,15 @@
package com.microservices.mon.k8s.domain.vo;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import lombok.Data;
/**
* @author OTTO
*/
@Data
public class ApplicationTypeVo {
private String key;
private String fullName;
private String description;
}

View File

@ -0,0 +1,46 @@
package com.microservices.mon.k8s.domain.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class CommonTypeVo {
private String name;
private String key;
public static List<CommonTypeVo> eventReasonListToCommonTypeVoList(JSONObject eventReasonListQueryRes) {
List<CommonTypeVo> commonTypeVoList = new ArrayList<>();
if (eventReasonListQueryRes != null && eventReasonListQueryRes.getJSONObject("aggregations") != null
&& eventReasonListQueryRes.getJSONObject("aggregations").getJSONObject("distinct_reasons") != null
&& eventReasonListQueryRes.getJSONObject("aggregations").getJSONObject("distinct_reasons").getJSONArray("buckets") != null) {
JSONArray eventReasonList = eventReasonListQueryRes.getJSONObject("aggregations").getJSONObject("distinct_reasons").getJSONArray("buckets");
for (int i = 0; i < eventReasonList.size(); i++) {
JSONObject eventReason = eventReasonList.getJSONObject(i);
CommonTypeVo commonTypeVo = new CommonTypeVo();
commonTypeVo.setName(eventReason.getString("key"));
commonTypeVo.setKey(eventReason.getString("key"));
commonTypeVoList.add(commonTypeVo);
}
}
return commonTypeVoList;
}
public static List<CommonTypeVo> resourceObjectToCommonTypeVoList(List<PrometheusResultVo> resultList) {
List<CommonTypeVo> commonTypeVoList = new ArrayList<>();
for (PrometheusResultVo resultVo : resultList) {
JSONObject metric = resultVo.getMetric();
String nodeInstance = metric.getString("instance");
String nodeIp = metric.getString("ip");
CommonTypeVo commonTypeVo = new CommonTypeVo();
commonTypeVo.setName(String.format("%s(%s)", nodeInstance, nodeIp));
commonTypeVo.setKey(nodeIp);
commonTypeVoList.add(commonTypeVo);
}
return commonTypeVoList;
}
}

View File

@ -0,0 +1,13 @@
package com.microservices.mon.k8s.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class PrometheusDataVo {
// 查询结果类型
String resultType;
// 应用类型
List<PrometheusResultVo> result;
}

View File

@ -0,0 +1,38 @@
package com.microservices.mon.k8s.domain.vo;
import lombok.Data;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@Data
public class PrometheusResVo {
// 状态success代表成功
String status;
// 数据
PrometheusDataVo data;
public boolean isSuccess() {
return "success".equals(status)
&& data != null
&& data.getResult() != null
&& !data.getResult().isEmpty();
}
public static String getPromStringValue(PrometheusResVo prometheusResVo) {
String value = "";
if (prometheusResVo != null && prometheusResVo.isSuccess()) {
PrometheusResultVo result = prometheusResVo.getData().getResult().get(0);
return result.getValue().getString(1);
}
return value;
}
public static List<PrometheusResultVo> getPrometheusResultVoList(PrometheusResVo prometheusResVo) {
if (prometheusResVo != null && prometheusResVo.isSuccess()) {
return prometheusResVo.getData().getResult();
}
return Collections.emptyList();
}
}

View File

@ -0,0 +1,15 @@
package com.microservices.mon.k8s.domain.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
@Data
public class PrometheusResultVo {
// 指标数据
JSONObject metric;
// 指标值
JSONArray value;
// 范围查询时指标值
JSONArray values;
}

View File

@ -0,0 +1,23 @@
package com.microservices.mon.k8s.domain.vo;
import lombok.Data;
@Data
public class RancherApplicationStateVo {
// Daemonsets-期望数量
Integer desiredNumberScheduled;
// Daemonsets-就绪数量
Integer numberReady;
// Deploy/Stateful-就绪副本数
Integer readyReplicas;
// Stateful-有效副本数
Integer availableReplicas;
// Deploy/Stateful-副本数量
Integer replicas;
}

View File

@ -0,0 +1,113 @@
package com.microservices.mon.k8s.domain.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.context.SecurityContextHolder;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.mon.k8s.domain.K8sApplication;
import com.microservices.mon.k8s.domain.K8sApplicationAndPodState;
import com.microservices.mon.k8s.domain.K8sApplicationPod;
import com.microservices.mon.k8s.utils.MonUtils;
import lombok.Data;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class RancherApplicationVo {
// 标识
String id;
// 应用类型
String kind;
// 元数据
RancherMetadataVo metadata;
// 状态
RancherApplicationStateVo status;
// 规格
JSONObject spec;
public K8sApplication toK8sApplication() {
K8sApplication target = new K8sApplication();
BeanUtils.copyProperties(this, target);
if ("DaemonSet".equals(kind)) {
target.setReplicas(status.getDesiredNumberScheduled());
target.setReadyReplicas(status.getNumberReady());
} else if ("StatefulSet".equals(kind)) {
target.setReplicas(status.getReplicas());
target.setReadyReplicas(status.getAvailableReplicas());
} else {
target.setReplicas(status.getReplicas());
target.setReadyReplicas(status.getReadyReplicas());
}
if (spec != null) {
JSONObject templateObj = spec.getJSONObject("template");
if (templateObj != null) {
JSONObject specObj = templateObj.getJSONObject("spec");
if (specObj != null) {
JSONArray containersArray = specObj.getJSONArray("containers");
if (containersArray != null && !containersArray.isEmpty()) {
JSONObject firstContainer = containersArray.getJSONObject(0);
if (firstContainer != null && firstContainer.containsKey("image")) {
target.setDockerImage(firstContainer.getString("image"));
}
}
}
}
if (spec.containsKey("paused")) {
target.setPaused(spec.getBoolean("paused"));
} else {
target.setPaused(false);
}
}
if (metadata != null) {
target.setName(metadata.getName());
target.setAlias(metadata.getName());
if (metadata.getAnnotations() != null && metadata.getAnnotations().containsKey("mon-name")) {
target.setAlias(metadata.getAnnotations().getString("mon-name"));
}
if (metadata.getState() != null) {
target.setState(metadata.getState().toK8sApplicationState());
}
target.setSurvivalTime(
MonUtils.getTimeDifferenceDescription(metadata.getCreationTimestamp())
);
target.setNamespace(metadata.getNamespace());
JSONArray relationships = metadata.getRelationships();
for (int i = 0; i < relationships.size(); i++) {
JSONObject relationship = relationships.getJSONObject(i);
if (relationship.containsKey("toType") && "pod".equals(relationship.getString("toType"))) {
String selector = relationship.getString("selector");
if (StringUtils.isNotEmpty(selector) && selector.contains("=") && selector.split("=").length >= 2) {
target.setPodSelector(selector);
}
}
}
}
if(target.getReplicas()==null){
target.setReplicas(0);
}
if(target.getReadyReplicas()==null){
target.setReadyReplicas(0);
}
if (target.getReplicas() == 0) {
target.setState(K8sApplicationAndPodState.k8sApplicationStopState("stop"));
}
if(target.getPaused()){
target.setState(K8sApplicationAndPodState.k8sApplicationStopState("paused"));
}
return target;
}
}

View File

@ -0,0 +1,22 @@
package com.microservices.mon.k8s.domain.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
@Data
public class RancherClusterNodeStateVo {
// ip地址
JSONArray addresses;
// 资源分配
JSONObject allocatable;
// 节点状况
JSONArray conditions;
// 节点信息
JSONObject nodeInfo;
}

View File

@ -0,0 +1,49 @@
package com.microservices.mon.k8s.domain.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.mon.k8s.domain.K8sApplication;
import com.microservices.mon.k8s.domain.K8sClusterNode;
import com.microservices.mon.k8s.domain.K8sNamespace;
import lombok.Data;
import java.util.Date;
@Data
public class RancherClusterNodeVo {
// 标识
String id;
// 元数据
RancherMetadataVo metadata;
// 节点状态
RancherClusterNodeStateVo status;
public K8sClusterNode toK8sClusterNode() {
K8sClusterNode target = new K8sClusterNode();
BeanUtils.copyProperties(this, target);
if (metadata.getLabels() != null && metadata.getLabels().containsKey("node-role.kubernetes.io/control-plane")) {
target.setRole("Master");
}else{
target.setRole("Worker");
}
target.setName(metadata.getName());
if(status!=null&&status.getAddresses()!=null){
JSONArray addresses=status.getAddresses();
for (int i = 0; i < addresses.size(); i++) {
JSONObject jsonObject = addresses.getJSONObject(i);
if (jsonObject.getString("type").equals("InternalIP")) {
target.setIp(jsonObject.getString("address"));
}
}
JSONArray conditions=status.getConditions();
for (int i = 0; i < conditions.size(); i++) {
JSONObject jsonObject = conditions.getJSONObject(i);
if (jsonObject.getString("type").equals("Ready")) {
target.setReady(jsonObject.getBoolean("status"));
}
}
}
return target;
}
}

View File

@ -0,0 +1,35 @@
package com.microservices.mon.k8s.domain.vo;
import com.microservices.mon.k8s.domain.K8sApplicationAndPodState;
import lombok.Data;
@Data
public class RancherMetadataState {
// 是否错误
Boolean error;
// 状态消息
String message;
// 状态名称
// 应用状态(name -> activename -> updatingname -> in-progressname -> error)
// pod状态name -> imagePullBackOffname -> containerCreating
String name;
// 是否过渡状态
Boolean transitioning;
public K8sApplicationAndPodState toK8sApplicationState() {
K8sApplicationAndPodState state = new K8sApplicationAndPodState();
state.setName(name);
state.setIsError(error);
state.setMessage(message);
state.setApplicationAliasByName(name);
return state;
}
public K8sApplicationAndPodState toK8sPodState() {
K8sApplicationAndPodState state = new K8sApplicationAndPodState();
state.setName(name);
state.setMessage(message);
state.setPodAliasAndErrorByName(name);
return state;
}
}

Some files were not shown because too many files have changed in this diff Show More