Compare commits

...

286 Commits

Author SHA1 Message Date
wanjia 4c62b8c1f5 fix(dss):创客统计中去除二次、三次发布的任务 2026-05-18 15:01:26 +08:00
xxq250 e74cbd5d8a 统一附件图片文件等格式, 空处理 2026-05-18 11:06:04 +08:00
xxq250 c52e178f4d 统一附件图片文件等格式 2026-05-18 11:03:54 +08:00
xxq250 69d627f8e5 统一附件图片文件等格式 2026-05-18 10:31:22 +08:00
xxq250 4ccda26fd5 成果库接口合并 2026-05-18 09:41:37 +08:00
xxq250 7f07d226da 融办分类数据统计,增加名称和搜索,id desc 排序 2026-05-14 15:16:43 +08:00
xxq250 7a28e98b5d 融办分类数据统计,增加名称和搜索,id desc 排序 2026-05-14 15:12:54 +08:00
xxq250 7dd24d43bc 融办分类数据统计,增加名称和搜索 2026-05-14 15:10:52 +08:00
xxq250 10fe8855d5 融办分类数据统计,增加名称和搜索 2026-05-14 15:03:53 +08:00
xxq250 65a5089ddc 融办分类数据统计sql 2026-05-14 10:59:50 +08:00
xxq250 ebab84d04b 融办分类数据统计 2026-05-14 10:48:24 +08:00
xxq250 ee7f36bb8f 分类数据统计 2026-05-14 10:30:42 +08:00
xxq250 0450126d84 成果库导入应用情况 2026-05-13 15:04:22 +08:00
xxq250 419548cb52 成果库导入状态1 2026-05-13 14:19:27 +08:00
xxq250 c433080296 成果库省市容办名称和描述 2026-05-13 11:41:33 +08:00
xxq250 850a477d17 成果库省市容办 2026-05-13 09:51:59 +08:00
xxq250 95b35d1847 成果库导入图片和视频、附件 2026-05-11 17:11:15 +08:00
xxq250 9394e9f9a0 图片和视频 2026-05-11 14:48:58 +08:00
xxq250 14bdda20a1 reset setting file 2026-05-11 11:34:42 +08:00
xxq250 cb5cc6e3b4 成果导入-增加声市融办类型 2026-05-11 11:23:07 +08:00
xxq250 93aca87a0b 成果导入-成果来源 2026-04-28 16:21:30 +08:00
xxq250 9300a8f092 成果管理-成果简介文案 2026-04-28 10:53:06 +08:00
xxq250 4ff20b445b 成果管理-导入时没有sourceid处理 2026-04-27 11:57:16 +08:00
xxq250 9d110b608d 成果管理-字段调整 2026-04-27 11:06:00 +08:00
wanjia9506 561363ea1b Merge pull request '项目资源库导入 任务资源库导入 竞赛资源库导入' (#1074) from wanjia9506/microservices:dev_monitoring_import_resource into dev_monitoring 2026-04-20 13:57:55 +08:00
wanjia 2116f7cadd feat(dms):项目资源库导入 2026-04-20 13:51:43 +08:00
wanjia afc7c9f20a feat(dms):任务资源库导入 2026-04-20 13:51:32 +08:00
wanjia 3fdfee7bd5 feat(dms):竞赛资源库导入 2026-04-20 13:51:07 +08:00
wanjia9506 4513ae20d6 Merge pull request '社区动态总数去除评论数' (#1050) from wanjia9506/microservices:dev_monitoring_import_ach into dev_monitoring 2026-03-26 09:48:02 +08:00
wanjia 4ada9fc829 fix(dms):社区动态总数去除评论数 2026-03-26 09:38:47 +08:00
otto 2eb21023e4 Merge pull request 'fix(HttpClient-SSL握手修复): 修复HTTPS请求偶发性peer not authenticated错误' (#1047) from otto/microservices:dev_monitoring into dev_monitoring 2026-03-16 15:29:29 +08:00
欧涛 f46b3c40b3 fix(HttpClient-SSL握手修复): 修复HTTPS请求偶发性peer not authenticated错误
新增SSLContextFactory配置TLSv1.2/TLSv1.3协议支持,优化HttpClient连接池管理策略。添加连接验证机制(2秒间隔)、连接存活时间限制(60秒)和后台清理线程(每10秒清理过期连接),同时在业务层添加针对SSL握手失败等异常的重试机制(最多2次),彻底解决频繁出现的SSL握手失败问题。
2026-03-16 15:32:10 +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
wanjia9506 3fad2b311f Merge pull request '默认状态设置' (#1045) from wanjia9506/microservices:dev_monitoring_import_ach into dev_monitoring 2026-03-03 16:55:38 +08:00
wanjia9506 10323b94f1 Merge pull request 'excel导入校企成果,支持图片' (#1044) from wanjia9506/microservices:dev_monitoring_import_ach into dev_monitoring 2026-03-03 15:54:44 +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
173 changed files with 8872 additions and 518 deletions

View File

@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 文件服务 * 文件服务
@ -34,6 +35,17 @@ public interface RemoteFileService {
@RequestParam("hierarchy") String hierarchy, @RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source); @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列表获取文件列表 * 根据文件Id列表获取文件列表
* *

View File

@ -2,13 +2,13 @@ package com.microservices.system.api;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.ServiceNameConstants; 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.RemoteGatewayFallbackFactory;
import com.microservices.system.api.model.GraphqlQueryVo;
import feign.Response; import feign.Response;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map; import java.util.Map;
@ -28,6 +28,14 @@ public interface RemoteGatewayService {
@PostMapping("/sentinel/auth/login") @PostMapping("/sentinel/auth/login")
Response loginSentinel(@RequestParam("username") String username, @RequestParam("password") String password); 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 * 登录Nacos
* *
@ -43,4 +51,45 @@ public interface RemoteGatewayService {
*/ */
@PostMapping("/portainer/api/auth") @PostMapping("/portainer/api/auth")
Response loginPortainer(@RequestBody JSONObject loginBody); 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.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 文件服务降级处理 * 文件服务降级处理
@ -36,6 +37,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
return R.fail("上传文件失败:" + throwable.getMessage()); 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 @Override
public R<List<SysFileInfo>> getFileList(String fileIds) { public R<List<SysFileInfo>> getFileList(String fileIds) {
return R.fail("获取文件列表失败:" + throwable.getMessage()); return R.fail("获取文件列表失败:" + throwable.getMessage());

View File

@ -1,7 +1,9 @@
package com.microservices.system.api.factory; package com.microservices.system.api.factory;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.system.api.RemoteGatewayService; import com.microservices.system.api.RemoteGatewayService;
import com.microservices.system.api.model.GraphqlQueryVo;
import feign.Response; import feign.Response;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,7 +27,12 @@ public class RemoteGatewayFallbackFactory implements FallbackFactory<RemoteGatew
return new RemoteGatewayService() { return new RemoteGatewayService() {
@Override @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; return null;
} }
@ -36,7 +43,66 @@ public class RemoteGatewayFallbackFactory implements FallbackFactory<RemoteGatew
@Override @Override
public Response loginPortainer(JSONObject loginBody) { 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; 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.domain.R;
import com.microservices.common.core.exception.ServiceException; import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils; import com.microservices.common.core.utils.StringUtils;
import feign.Response;
/** /**
* @author otto * @author otto
*/ */
public class FeignUtils { public class FeignUtils {
public static <T> T getReturnData(R<T> result){ public static <T> T getReturnData(R<T> result) {
if (StringUtils.isNull(result)) { if (StringUtils.isNull(result)) {
return null; return null;
} }
if (R.FAIL == result.getCode()) if (R.FAIL == result.getCode()) {
{
throw new ServiceException(result.getMsg()); throw new ServiceException(result.getMsg());
} }
return result.getData(); 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.RemoteZoneFallbackFactory
com.microservices.system.api.factory.RemotePmsFallbackFactory com.microservices.system.api.factory.RemotePmsFallbackFactory
com.microservices.system.api.factory.RemoteGatewayFallbackFactory com.microservices.system.api.factory.RemoteGatewayFallbackFactory
com.microservices.system.api.factory.RemoteRancherFallbackFactory

View File

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

View File

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

View File

@ -5,8 +5,7 @@ package com.microservices.common.core.constant;
* *
* @author microservices * @author microservices
*/ */
public class Constants public class Constants {
{
/** /**
* UTF-8 字符集 * UTF-8 字符集
*/ */
@ -125,6 +124,10 @@ public class Constants
* JSON类型 * JSON类型
*/ */
public static final String JSON_CONTENT_TYPE = "application/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"; public static final String USER_KEY = "user_key";
/**
* 用户Token
*/
public static final String TOKEN = "token";
/** /**
* 组织ID * 组织ID
*/ */

View File

@ -22,9 +22,16 @@ public class TokenConstants {
* Sentinel令牌标识 * Sentinel令牌标识
*/ */
public static final String Sentinel_Token_Key = "sentinel_dashboard_cookie"; 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"; public static final String Portainer_Token_Key = "portainer_api_key";
/** /**
* Sentinel令牌标识 * Nacos令牌标识
*/ */
public static final String Nacos_Token_Key = "Accesstoken"; public static final String Nacos_Token_Key = "Accesstoken";
/** /**

View File

@ -1,5 +1,6 @@
package com.microservices.common.core.threadPool; package com.microservices.common.core.threadPool;
import com.microservices.common.core.constant.TokenConstants;
import com.microservices.common.core.context.SecurityContextHolder; import com.microservices.common.core.context.SecurityContextHolder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,6 +38,9 @@ public class ThreadPoolExecutorWrap extends ThreadPoolExecutor {
@Override @Override
public void run() { public void run() {
try { try {
if(contextValue != null&&contextValue.containsKey("user_key")){
contextValue.put(TokenConstants.AUTHENTICATION, contextValue.get("user_key"));
}
SecurityContextHolder.setLocalMap(contextValue); SecurityContextHolder.setLocalMap(contextValue);
// 用户任务逻辑 // 用户任务逻辑
task.run(); 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.ArrayUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -1458,4 +1460,288 @@ public class ExcelUtil<T>
} }
return method; 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) //为空时隐藏 @JsonInclude(JsonInclude.Include.NON_NULL) //为空时隐藏
private List<Breadcrumb> breadcrumb; private List<Breadcrumb> breadcrumb;

View File

@ -1,8 +1,13 @@
package com.microservices.common.httpClient.config; package com.microservices.common.httpClient.config;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -11,7 +16,13 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.util.concurrent.TimeUnit;
/** /**
* HttpClient 配置类
* 支持 HTTPS 和连接池管理包含自动连接清理机制
*
* @author admin * @author admin
* @date 2017/2/6 * @date 2017/2/6
*/ */
@ -20,74 +31,131 @@ public class HttpClientConfiguration {
private static final Logger logger = LoggerFactory.getLogger(HttpClientConfiguration.class); private static final Logger logger = LoggerFactory.getLogger(HttpClientConfiguration.class);
@Value("${http.maxTotal}") @Value("${http.maxTotal:100}")
private Integer maxTotal; private Integer maxTotal;
@Value("${http.defaultMaxPerRoute}") @Value("${http.defaultMaxPerRoute:20}")
private Integer defaultMaxPerRoute; private Integer defaultMaxPerRoute;
@Value("${http.connectTimeout}") @Value("${http.connectTimeout:10000}")
private Integer connectTimeout; private Integer connectTimeout;
@Value("${http.connectionRequestTimeout}") @Value("${http.connectionRequestTimeout:5000}")
private Integer connectionRequestTimeout; private Integer connectionRequestTimeout;
@Value("${http.socketTimeout}") @Value("${http.socketTimeout:30000}")
private Integer socketTimeout; private Integer socketTimeout;
@Value("${http.staleConnectionCheckEnabled}") @Value("${http.staleConnectionCheckEnabled:true}")
private boolean staleConnectionCheckEnabled; private boolean staleConnectionCheckEnabled;
private PoolingHttpClientConnectionManager connectionManager;
/** /**
* 首先实例化一个连接池管理器设置最大连接数并发连接数 * 创建支持 HTTPS 的连接池管理器
* 使用自定义的 SSL 配置来避免 "peer not authenticated" 错误
* *
* @return * @return PoolingHttpClientConnectionManager
*/ */
@Bean(name = "httpClientConnectionManager") @Bean(name = "httpClientConnectionManager")
public PoolingHttpClientConnectionManager getHttpClientConnectionManager() { public PoolingHttpClientConnectionManager getHttpClientConnectionManager() {
PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(); try {
//最大连接数 // 创建 SSLConnectionSocketFactory
httpClientConnectionManager.setMaxTotal(maxTotal); SSLConnectionSocketFactory sslSocketFactory = SSLContextFactory.createSSLConnectionSocketFactory();
//并发数
httpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 注册 HTTP HTTPS SocketFactory
return httpClientConnectionManager; Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build();
// 使用自定义 Registry 创建连接池管理器
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
// 设置连接池参数
manager.setMaxTotal(maxTotal);
manager.setDefaultMaxPerRoute(defaultMaxPerRoute);
// 设置连接验证间隔非常重要防止使用已关闭的连接
manager.setValidateAfterInactivity(2000);
this.connectionManager = manager;
logger.info("HttpClient连接池初始化成功 - 最大连接数: {}, 每个路由最大连接数: {}, SSL验证: 启用",
maxTotal, defaultMaxPerRoute);
return manager;
} catch (Exception e) {
logger.error("HttpClient连接池初始化失败", e);
throw new RuntimeException("HttpClient连接池初始化失败", e);
}
} }
/** /**
* 实例化连接池设置连接池管理器 * 创建 HttpClientBuilder
* 这里需要以参数形式注入上面实例化的连接池管理器 * 配置重试机制和连接管理策略
* *
* @param httpClientConnectionManager * @param httpClientConnectionManager 连接池管理器
* @return * @return HttpClientBuilder
*/ */
@Bean(name = "httpClientBuilder") @Bean(name = "httpClientBuilder")
public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager") PoolingHttpClientConnectionManager httpClientConnectionManager) { public org.apache.http.impl.client.HttpClientBuilder getHttpClientBuilder(
@Qualifier("httpClientConnectionManager") PoolingHttpClientConnectionManager httpClientConnectionManager) {
//HttpClientBuilder中的构造方法被protected修饰所以这里不能直接使用new来实例化一个HttpClientBuilder可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象 org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); .setConnectionManager(httpClientConnectionManager)
httpClientBuilder.setConnectionManager(httpClientConnectionManager); // 设置连接存活时间 - 防止连接被长期占用
.setConnectionTimeToLive(60, TimeUnit.SECONDS)
// 添加重试机制 - 针对 IO/SSL 异常
.setRetryHandler((exception, executionCount, context) -> {
if (executionCount > 2) {
logger.warn("HTTP请求重试次数超过限制(2次),停止重试");
return false;
}
// 可重试的异常类型
if (exception instanceof org.apache.http.NoHttpResponseException
|| exception instanceof javax.net.ssl.SSLHandshakeException
|| exception instanceof java.net.SocketException
|| exception instanceof org.apache.http.conn.HttpHostConnectException) {
logger.warn("HTTP请求失败正在进行第{}次重试。异常类型: {}, 消息: {}",
executionCount, exception.getClass().getSimpleName(), exception.getMessage());
return true;
}
logger.debug("HTTP请求失败异常类型不支持重试: {}", exception.getClass().getName());
return false;
})
// 禁用过期连接
.evictIdleConnections(30, TimeUnit.SECONDS)
// 空闲连接验证
.setConnectionManagerShared(false);
return httpClientBuilder; logger.info("HttpClientBuilder 配置完成 - 重试机制: 启用(最多2次), 连接存活时间: 60秒");
return builder;
} }
/** /**
* 注入连接池用于获取httpClient * 创建 CloseableHttpClient 实例
* *
* @param httpClientBuilder * @param httpClientBuilder HttpClientBuilder
* @return * @return CloseableHttpClient
*/ */
@Bean @Bean
public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder) { public CloseableHttpClient getCloseableHttpClient(
return httpClientBuilder.build(); @Qualifier("httpClientBuilder") org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) {
CloseableHttpClient httpClient = httpClientBuilder.build();
// 启动连接池清理后台线程
startConnectionMonitorThread();
logger.info("CloseableHttpClient 创建成功");
return httpClient;
} }
/** /**
* Builder是RequestConfig的一个内部类 * 创建 RequestConfig.Builder
* 通过RequestConfig的custom方法来获取到一个Builder对象
* 设置builder的连接信息
* 这里还可以设置proxycookieSpec等属性有需要的话可以在此设置
* *
* @return * @return RequestConfig.Builder
*/ */
@Bean(name = "builder") @Bean(name = "builder")
public RequestConfig.Builder getBuilder() { public RequestConfig.Builder getBuilder() {
@ -99,14 +167,78 @@ public class HttpClientConfiguration {
} }
/** /**
* 使用builder构建一个RequestConfig对象 * 创建 RequestConfig
* *
* @param builder * @param builder RequestConfig.Builder
* @return * @return RequestConfig
*/ */
@Bean @Bean
public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder) { public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder) {
return builder.build(); RequestConfig config = builder.build();
logger.info("RequestConfig 配置完成 - 连接超时: {}ms, 请求超时: {}ms, Socket超时: {}ms, 陈旧连接检查: {}",
connectTimeout, connectionRequestTimeout, socketTimeout, staleConnectionCheckEnabled);
return config;
} }
/**
* 启动连接池监控和清理线程
* 定期清理过期和空闲连接防止使用已关闭的连接
*/
private void startConnectionMonitorThread() {
Thread monitorThread = new Thread(() -> {
logger.info("HttpClient连接池监控线程启动");
try {
while (connectionManager != null) {
try {
// 10 秒清理一次
Thread.sleep(10000);
if (connectionManager != null) {
// 关闭过期连接
connectionManager.closeExpiredConnections();
// 关闭空闲超过 30 秒的连接
connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
if (logger.isDebugEnabled()) {
logger.debug("HttpClient连接池状态 - 总连接数: {}, 路由数: {}, 最大连接数: {}, 每路由最大连接数: {}",
connectionManager.getTotalStats().getLeased() +
connectionManager.getTotalStats().getAvailable(),
connectionManager.getTotalStats().getLeased(),
connectionManager.getMaxTotal(),
connectionManager.getDefaultMaxPerRoute());
}
}
} catch (InterruptedException e) {
logger.warn("HttpClient连接池监控线程被中断");
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.error("HttpClient连接池清理过程中出现异常", e);
}
}
} finally {
logger.info("HttpClient连接池监控线程停止");
}
});
monitorThread.setDaemon(true);
monitorThread.setName("http-connection-monitor");
monitorThread.start();
}
/**
* 应用关闭时清理资源
*/
@PreDestroy
public void destroy() {
logger.info("开始清理 HttpClient 资源");
if (connectionManager != null) {
try {
connectionManager.close();
logger.info("HttpClient 连接池已关闭");
} catch (Exception e) {
logger.error("关闭 HttpClient 连接池时出现异常", e);
}
}
}
} }

View File

@ -0,0 +1,140 @@
package com.microservices.common.httpClient.config;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
/**
* SSL/TLS 配置工厂类
* 用于创建支持 HTTPS SSLContext SocketFactory
*
* @author otto
*/
public class SSLContextFactory {
private static final Logger logger = LoggerFactory.getLogger(SSLContextFactory.class);
/**
* 创建支持所有 TLS 版本的 SSLContext
* 信任所有证书仅用于内部系统调用
*
* @return SSLContext 实例
*/
public static SSLContext createSSLContext() {
try {
// 使用 TLSv1.2 或更高版本
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
// 创建信任所有证书的 TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
logger.debug("SSLContext 创建成功,使用 TLSv1.2 协议");
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
logger.error("创建 SSLContext 失败", e);
throw new RuntimeException("创建 SSLContext 失败", e);
}
}
/**
* 创建 SSLConnectionSocketFactory
* 跳过主机名验证仅用于内部系统调用
*
* @return SSLConnectionSocketFactory 实例
*/
public static SSLConnectionSocketFactory createSSLConnectionSocketFactory() {
try {
SSLContext sslContext = createSSLContext();
// 创建跳过主机名验证的 HostnameVerifier
HostnameVerifier allowAllHosts = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// 记录连接的主机信息便于调试
logger.debug("SSL 连接主机: {}, 证书主体: {}", hostname, getCertificateSubject(session));
return true;
}
private String getCertificateSubject(SSLSession session) {
try {
return session.getPeerPrincipal().toString();
} catch (Exception e) {
return "Unknown";
}
}
};
// 支持的协议TLSv1.2, TLSv1.3
String[] supportedProtocols = new String[]{"TLSv1.2", "TLSv1.3"};
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext,
supportedProtocols,
null, // 使用默认的密码套件
allowAllHosts
);
logger.debug("SSLConnectionSocketFactory 创建成功,支持的协议: TLSv1.2, TLSv1.3");
return sslSocketFactory;
} catch (Exception e) {
logger.error("创建 SSLConnectionSocketFactory 失败", e);
throw new RuntimeException("创建 SSLConnectionSocketFactory 失败", e);
}
}
/**
* 创建用于信任所有证书的 TrustManager用于日志记录
*
* @return TrustManager 数组
*/
public static TrustManager[] createTrustAllManagers() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
logger.debug("客户端证书验证通过");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
if (chain != null && chain.length > 0) {
X509Certificate cert = chain[0];
logger.debug("服务器证书验证通过 - 主体: {}, 颁发者: {}, 有效期: {} - {}",
cert.getSubjectDN(),
cert.getIssuerDN(),
cert.getNotBefore(),
cert.getNotAfter());
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
}
}

View File

@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static com.microservices.common.httpClient.domain.GitLinkRequestUrl.GET_FILE_SUB_ENTRIES; import static com.microservices.common.httpClient.domain.GitLinkRequestUrl.GET_FILE_SUB_ENTRIES;
import static com.microservices.common.httpClient.util.GitLinkRequestHelper.RequestType.GET; import static com.microservices.common.httpClient.util.GitLinkRequestHelper.RequestType.GET;
@ -79,44 +80,75 @@ public class GitLinkRequestHelper {
HashMap<String, String> headers = prepareAuthenticationHeaders(gitLinkRequestUrl); HashMap<String, String> headers = prepareAuthenticationHeaders(gitLinkRequestUrl);
String requestId = UUID.randomUUID().toString(); String requestId = UUID.randomUUID().toString();
String path = gitLinkRequestUrl.getPath(); String path = gitLinkRequestUrl.getPath();
logger.info("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}" logger.debug("[{}]通过HttpClient向平台发起请求请求方法为{},请求网站为:{},请求路由为:{},请求载体为:{},请求头为:{}"
, requestId , requestId
, requestType , requestType
, gitLinkUrl , gitLinkUrl
, path , path
, object , object
, headers); , headers);
JSONObject result; JSONObject result = null;
try { Exception lastException = null;
String requestURL = EscapeUtil.removeExtraSlashOfUrl(gitLinkUrl + path.replace(" ", "%20"));
switch (requestType) { // 业务层重试机制针对 SSL 握手失败等偶发性错误
case POST: int maxRetries = 2;
result = httpAPIService.doPost(requestURL, (JSONObject) object, headers); for (int attempt = 1; attempt <= maxRetries; attempt++) {
break; try {
case POST_FILE: String requestURL = EscapeUtil.removeExtraSlashOfUrl(gitLinkUrl + path.replace(" ", "%20"));
result = httpAPIService.doPostFile(requestURL, (MultipartFile) object, headers); switch (requestType) {
break; case POST:
case DELETE: result = httpAPIService.doPost(requestURL, (JSONObject) object, headers);
result = httpAPIService.doDelete(requestURL, (JSONObject) object, headers); break;
break; case POST_FILE:
case GET: result = httpAPIService.doPostFile(requestURL, (MultipartFile) object, headers);
result = httpAPIService.doGet(requestURL, headers); break;
break; case DELETE:
case PUT: result = httpAPIService.doDelete(requestURL, (JSONObject) object, headers);
result = httpAPIService.doPut(requestURL, (JSONObject) object, headers); break;
break; case GET:
case PATCH: result = httpAPIService.doGet(requestURL, headers);
result = httpAPIService.doPatch(requestURL, (JSONObject) object, headers); break;
break; case PUT:
default: result = httpAPIService.doPut(requestURL, (JSONObject) object, headers);
throw new ServiceException("不支持该请求方式:" + requestType.name()); break;
case PATCH:
result = httpAPIService.doPatch(requestURL, (JSONObject) object, headers);
break;
default:
throw new ServiceException("不支持该请求方式:" + requestType.name());
}
logger.debug("[{}]请求响应为:{}"
, requestId
, result);
// 请求成功跳出重试循环
break;
} catch (Exception e) {
lastException = e;
String errorMsg = e.getMessage();
// 判断是否为可重试的异常类型
boolean isRetryable = errorMsg != null && (
errorMsg.contains("peer not authenticated") ||
errorMsg.contains("SSLHandshakeException") ||
errorMsg.contains("Connection reset") ||
errorMsg.contains("NoHttpResponseException") ||
errorMsg.contains("SocketException")
);
if (isRetryable && attempt < maxRetries) {
logger.warn("[{}]请求平台时发生可重试异常(第{}次尝试)将在100ms后重试。异常信息: {}",
requestId, attempt, errorMsg);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ServiceException("请求被中断");
}
continue;
}
throw new ServiceException("请求平台出现错误:" + errorMsg);
} }
logger.info("[{}]请求响应为:{}"
, requestId
, result);
} catch (Exception e) {
throw new ServiceException("请求平台出现错误:" + e.getMessage());
} }
if (result == null) { if (result == null) {
throw new ServiceException("请求平台时,未返回内容"); throw new ServiceException("请求平台时,未返回内容");
} }

View File

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

View File

@ -3,6 +3,7 @@ package com.microservices.common.security.feign;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.codec.Encoder; import feign.codec.Encoder;
import feign.form.FormEncoder; import feign.form.FormEncoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
@ -33,6 +34,6 @@ public class FeignAutoConfiguration
// 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类 // 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类
@Bean @Bean
public Encoder feignFormEncoder() { public Encoder feignFormEncoder() {
return new FormEncoder(new SpringEncoder(messageConverters)); return new SpringFormEncoder(new SpringEncoder(messageConverters));
} }
} }

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

View File

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

View File

@ -53,8 +53,14 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
public String portainerUsername; public String portainerUsername;
@Value("${thirdPartyTools.portainer.auth.password:}") @Value("${thirdPartyTools.portainer.auth.password:}")
public String portainerPassword; public String portainerPassword;
@Value("${thirdPartyTools.rancher.auth.username:}")
public String rancherUsername;
@Value("${thirdPartyTools.rancher.auth.password:}")
public String rancherPassword;
@Value("${thirdPartyTools.portainer.endpoints:}") @Value("${thirdPartyTools.portainer.endpoints:}")
public Integer portainerEndpoints; public Integer portainerEndpoints;
@Value("${thirdPartyTools.retryTimes:1}")
public Integer retryTimes;
ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor; ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor;
@Lazy @Lazy
@Autowired @Autowired
@ -83,27 +89,81 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
if (redisService.hasKey(CacheConstants.SENTINEL_TOKEN)) { if (redisService.hasKey(CacheConstants.SENTINEL_TOKEN)) {
sentinelCookie = redisService.getCacheObject(CacheConstants.SENTINEL_TOKEN); sentinelCookie = redisService.getCacheObject(CacheConstants.SENTINEL_TOKEN);
} else { } else {
// 网关采用异步架构所以此处需要通过异步请求获取Sentinel Token for (int i = 0; i < retryTimes; i++) {
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword)); // 网关采用异步架构所以此处需要通过异步请求获取Sentinel Token
try { Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword));
Response response = future.get(1, TimeUnit.SECONDS); try {
Map<String, Collection<String>> header = response.headers(); Response response = future.get(3, TimeUnit.SECONDS);
if (header != null && header.containsKey("set-cookie")) { Map<String, Collection<String>> header = response.headers();
sentinelCookie = header.get("set-cookie").iterator().next(); if (header != null && header.containsKey("set-cookie")) {
//Sentinel登录状态默认30分钟失效将Sentinel Token缓存过期时间调整为28分钟 sentinelCookie = header.get("set-cookie").iterator().next();
redisService.setCacheObject(CacheConstants.SENTINEL_TOKEN, sentinelCookie, 28L, TimeUnit.MINUTES); //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超时"); if (StringUtils.isNotEmpty(sentinelCookie)) {
} catch (InterruptedException | ExecutionException e) { break;
log.error("获取Sentinel Token失败{}", e.getMessage()); }
log.warn("[网关转发异常处理]获取Sentinel Token失败进行第{}次重试中", i + 1);
} }
} }
if (StringUtils.isNotEmpty(sentinelCookie)) { if (StringUtils.isNotEmpty(sentinelCookie)) {
ServletUtils.removeHeader(mutate, TokenConstants.Cookie); ServletUtils.removeHeader(mutate, TokenConstants.Cookie);
mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, sentinelCookie)); mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, sentinelCookie));
} else { } 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请求 // 处理Nacos请求
@ -112,38 +172,44 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) { if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN); nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
} else { } else {
Map<String, String> nacosMap = new HashMap<>(); for (int i = 0; i < retryTimes; i++) {
nacosMap.put("username", nacosUsername); Map<String, String> nacosMap = new HashMap<>();
nacosMap.put("password", nacosPassword); nacosMap.put("username", nacosUsername);
// 网关采用异步架构所以此处需要通过异步请求获取Nacos Token nacosMap.put("password", nacosPassword);
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap)); // 网关采用异步架构所以此处需要通过异步请求获取Nacos Token
try { Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap));
Response res = future.get(1, TimeUnit.SECONDS); try {
StringWriter writer = new StringWriter(); Response res = future.get(3, TimeUnit.SECONDS);
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name()); StringWriter writer = new StringWriter();
String str = writer.toString(); IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
if (!JSON.isValid(str)) { String str = writer.toString();
return exceptionResponse(exchange, str, res.status()); 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 (StringUtils.isNotEmpty(nacosToken)) {
if (resJsonObject == null) { break;
return exceptionResponse(exchange, str, res.status());
} }
nacosToken = resJsonObject.getString("accessToken"); log.warn("[网关转发异常处理]获取Nacos Token失败进行第{}次重试中", i + 1);
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());
} }
} }
if (StringUtils.isNotEmpty(nacosToken)) { if (StringUtils.isNotEmpty(nacosToken)) {
mutate.header(TokenConstants.Nacos_Token_Key, nacosToken); mutate.header(TokenConstants.Nacos_Token_Key, nacosToken);
} else { } else {
throw new ServiceException("Nacos服务获取Token失败"); throw new ServiceException("[网关转发异常处理]Nacos服务获取Token失败");
} }
} }
@ -163,7 +229,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
try { try {
mutate.uri(new URI(url)); mutate.uri(new URI(url));
} catch (URISyntaxException e) { } 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)) { if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN); portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
} else { } else {
// 网关采用异步架构所以此处需要通过异步请求获取Portainer Token for (int i = 0; i < 5; i++) {
JSONObject loginPortainerBody = new JSONObject(); // 网关采用异步架构所以此处需要通过异步请求获取Portainer Token
loginPortainerBody.put("username", portainerUsername); JSONObject loginPortainerBody = new JSONObject();
loginPortainerBody.put("password", portainerPassword); loginPortainerBody.put("username", portainerUsername);
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody)); loginPortainerBody.put("password", portainerPassword);
try { Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody));
Response res = future.get(1, TimeUnit.SECONDS); try {
StringWriter writer = new StringWriter(); Response res = future.get(1, TimeUnit.SECONDS);
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name()); StringWriter writer = new StringWriter();
String str = writer.toString(); IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
if (!JSON.isValid(str)) { String str = writer.toString();
return exceptionResponse(exchange, str, res.status()); if (!JSON.isValid(str)) {
} return exceptionResponse(exchange, str, res.status());
JSONObject resJsonObject = JSONObject.parseObject(str); }
portainerToken = resJsonObject.getString("jwt"); JSONObject resJsonObject = JSONObject.parseObject(str);
if (portainerToken == null) { portainerToken = resJsonObject.getString("jwt");
return exceptionResponse(exchange, str, res.status()); 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); String[] tokenSplit = portainerToken.split("\\.");
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr); String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
Long expiration = tokenBody.getLong("exp"); JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
Long now = new Date().getTime() / 1000; Long expiration = tokenBody.getLong("exp");
Long now = new Date().getTime() / 1000;
long portainerTokenTtl = expiration - now; long portainerTokenTtl = expiration - now;
if (portainerTokenTtl > 20) { if (portainerTokenTtl > 20) {
redisService.setCacheObject(CacheConstants.PORTAINER_TOKEN, portainerToken, portainerTokenTtl - 10, TimeUnit.SECONDS); 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) { if (StringUtils.isNotEmpty(portainerToken)) {
log.error("获取Portainer Token超时"); break;
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) { }
log.error("获取Portainer Token失败{}", e.getMessage()); log.warn("[网关转发异常处理]获取Portainer Token失败进行第{}次重试中", i + 1);
} }
} }
if (StringUtils.isNotEmpty(portainerToken)) { if (StringUtils.isNotEmpty(portainerToken)) {
@ -218,7 +290,7 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + portainerToken); mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + portainerToken);
mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, portainerToken)); mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, portainerToken));
} else { } else {
throw new ServiceException("Portainer服务获取Token失败"); throw new ServiceException("[网关转发异常处理]Portainer服务获取Token失败");
} }
} }
return null; return null;
@ -226,7 +298,23 @@ public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) { 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); 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, public AjaxResult editCmsDIr(@ApiParam(name = "id", value = "栏目Id") @PathVariable Long id,
@ApiParam(name = "cmsDirUpdateVo", value = "栏目更新对象") @RequestBody CmsDirUpdateVo cmsDirUpdateVo) { @ApiParam(name = "cmsDirUpdateVo", value = "栏目更新对象") @RequestBody CmsDirUpdateVo cmsDirUpdateVo) {
cmsDocService.updateCmsDir(id, 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 public GenericsTableDataInfo<CmsDocBaseDto> docList(@ApiParam(name = "dirId", value = "栏目Id") @PathVariable("dirId") Long dirId
, CmsDocSearchVo cmsDocSearchVo) { , CmsDocSearchVo cmsDocSearchVo) {
cmsDocSearchVo.setIsOpen(true); cmsDocSearchVo.setIsOpen(true);
List<CmsDocBaseDto> list = cmsDocService.selectCmsDocList(dirId, cmsDocSearchVo); List<CmsDocBaseDto> list = cmsDocService.selectSetDefaultHeadImgCmsDocList(dirId, cmsDocSearchVo);
return getAllGenericsDataTableToPage(list); return getAllGenericsDataTableToPage(list);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -95,8 +95,12 @@ public class CmsProjectController extends BaseController {
@GetMapping("/getProjectByDeptId/{deptId}") @GetMapping("/getProjectByDeptId/{deptId}")
public R<String> getProjectByDeptId( public R<String> getProjectByDeptId(
@ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) { @ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId); CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptIdAndNoException(deptId);
return R.ok(JSONObject.toJSONString(cmsProject)); 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) { public R<Boolean> removeByDeptId(@PathVariable(value = "deptId") Long deptId) {
return R.ok(cmsProjectService.deleteCmsProjectByDeptId(deptId) > 0); 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 updateCmsProjectByDept(SysDept dept);
boolean operateProtectedBranchByZoneAuditSetting(String userName, String identifier, Long deptId, String operationType); 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; package com.microservices.cms.project.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.CmsDir; import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.domain.CmsDocBaseDto;
import com.microservices.cms.doc.domain.CmsDocSearchVo;
import com.microservices.cms.doc.service.ICmsAsyncService; import com.microservices.cms.doc.service.ICmsAsyncService;
import com.microservices.cms.doc.service.ICmsDocService; import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.project.domain.CmsProject; import com.microservices.cms.project.domain.CmsProject;
import com.microservices.cms.project.mapper.CmsProjectMapper; import com.microservices.cms.project.mapper.CmsProjectMapper;
import com.microservices.cms.project.service.ICmsProjectService; import com.microservices.cms.project.service.ICmsProjectService;
import com.microservices.cms.utils.CmsConstants;
import com.microservices.cms.utils.CmsGitLinkRequestUrl; 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.constant.SecurityConstants;
import com.microservices.common.core.enums.SystemRole; import com.microservices.common.core.enums.SystemRole;
import com.microservices.common.core.exception.ServiceException; import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.PinYinStringUtils; import com.microservices.common.core.utils.PinYinStringUtils;
import com.microservices.common.core.utils.StringUtils; import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.datascope.annotation.DataScope; import com.microservices.common.datascope.annotation.DataScope;
import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
import com.microservices.common.httpClient.util.GitLinkRequestHelper; import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.system.api.RemoteDeptService; import com.microservices.system.api.RemoteDeptService;
import com.microservices.system.api.RemoteUserService; 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.SysRole;
import com.microservices.system.api.domain.SysUser; import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.utils.FeignUtils; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -37,6 +40,7 @@ import java.util.List;
* @author otto * @author otto
* @date 2023-03-27 * @date 2023-03-27
*/ */
@Slf4j
@Service @Service
public class CmsProjectServiceImpl implements ICmsProjectService { public class CmsProjectServiceImpl implements ICmsProjectService {
@Autowired @Autowired
@ -228,6 +232,75 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
return true; 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) { private String handleSpecialCharacters(String name) {
return StringUtils.replace(name, " ", "-"); return StringUtils.replace(name, " ", "-");
@ -242,6 +315,11 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
return cmsProject; return cmsProject;
} }
@Override
public CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId) {
return cmsProjectMapper.selectCmsProjectByDeptId(deptId);
}
@Override @Override
public Boolean changeUserRole(Long userId, Long oldDeptId, Long newDeptId, Long oldRoleId, Long newRoleId) { public Boolean changeUserRole(Long userId, Long oldDeptId, Long newDeptId, Long oldRoleId, Long newRoleId) {
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER)); 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.cms.common.service.ISysUserService;
import com.microservices.common.core.constant.SecurityConstants; 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); String path = buildUri(String.format("/api/%s/%s/protected_branches.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames); path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path); 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); String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames); path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path); 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); String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames); path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
return getGitLinkRequestUrl(path); return getGitLinkRequestUrl(path);
@ -197,4 +197,12 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static String ZONE_DOC_URL(String gitlinkUrl, String zoneKey, Long docId) { 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)); 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 # Tomcat
server: server:
port: 9116 port: 8080
# Spring # Spring
spring: spring:

View File

@ -107,6 +107,50 @@
from cms_doc cd from cms_doc cd
</sql> </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" <select id="selectCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDocSearchVo"
resultMap="CmsDocResult"> resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/> <include refid="selectCmsDocVo"/>
@ -150,13 +194,13 @@
where file_path = #{filePath} and dept_id = #{deptId} and is_dir = 0 where file_path = #{filePath} and dept_id = #{deptId} and is_dir = 0
</select> </select>
<select id="selectHotCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDoc" resultMap="CmsDocResult"> <select id="selectHotCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDoc" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/> <include refid="selectCmsDocAliasSetDefaultHeadImgVo"/>
<include refid="selectListWhere"/> <include refid="selectListWhereAlias"/>
ORDER BY ORDER BY
visits DESC, cd.visits DESC,
sort DESC, cd.sort DESC,
update_time DESC, cd.update_time DESC,
create_time DESC cd.create_time DESC
</select> </select>
<select id="selectCmsDocCount" resultType="java.lang.Integer"> <select id="selectCmsDocCount" resultType="java.lang.Integer">
@ -180,6 +224,23 @@
</where> </where>
</sql> </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"> <select id="selectCmsDocDirByDeptIdAndInName" resultMap="CmsDocResult">
<include refid="selectCmsDocVo"/> <include refid="selectCmsDocVo"/>
where dept_id = #{deptId} and is_dir = 1 where dept_id = #{deptId} and is_dir = 1
@ -283,6 +344,15 @@
from cms_doc from cms_doc
where dept_id = #{deptId}; where dept_id = #{deptId};
</select> </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" <insert id="insertCmsDoc" parameterType="com.microservices.cms.doc.domain.CmsDoc" useGeneratedKeys="true"
keyProperty="id"> keyProperty="id">
@ -405,6 +475,12 @@
and dept_id = #{deptId} and dept_id = #{deptId}
and is_dir = #{isDir}; and is_dir = #{isDir};
</update> </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 id="deleteCmsDocById" parameterType="Long">
delete delete

View File

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

View File

@ -9,14 +9,17 @@ import com.microservices.common.log.enums.BusinessType;
import com.microservices.dms.achievementLibrary.domain.AchQueryVo; import com.microservices.dms.achievementLibrary.domain.AchQueryVo;
import com.microservices.dms.achievementLibrary.domain.Achievements; import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.service.IAchievementsService; import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import com.microservices.system.api.RemoteFileService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
/** /**
* 成果Controller * 成果Controller
* *
@ -30,6 +33,9 @@ public class AchievementsController extends BaseController {
@Autowired @Autowired
private IAchievementsService achievementsService; private IAchievementsService achievementsService;
@Autowired
private RemoteFileService remoteFileService;
/** /**
* 查询成果列表 * 查询成果列表
*/ */
@ -215,19 +221,20 @@ public class AchievementsController extends BaseController {
public AjaxResult getWatchFavoriteStatusById(Long id, Long userId) { public AjaxResult getWatchFavoriteStatusById(Long id, Long userId) {
return success(achievementsService.getWatchFavoriteStatusById(id, userId)); return success(achievementsService.getWatchFavoriteStatusById(id, userId));
} }
@ApiOperation("根据成果ID获取近7日成果点击数量折线图") @ApiOperation("根据成果ID获取近7日成果点击数量折线图")
@GetMapping("/get7AddClickById") @GetMapping("/get7AddClickById")
public AjaxResult get7AddClickById(String id,@RequestParam(value = "clickType",required = false, defaultValue = "Achievements") public AjaxResult get7AddClickById(String id, @RequestParam(value = "clickType", required = false, defaultValue = "Achievements")
String clickType) { String clickType) {
return success(achievementsService.get7AddClickById(id,clickType)); return success(achievementsService.get7AddClickById(id, clickType));
} }
@ApiOperation("根据专家id获取近7日成果点击数量折线图") @ApiOperation("根据专家id获取近7日成果点击数量折线图")
@GetMapping("/getExpert7AddClickById") @GetMapping("/getExpert7AddClickById")
public AjaxResult getExpert7AddClickById(String id,@RequestParam(value = "clickType",required = false, defaultValue = "Experts") public AjaxResult getExpert7AddClickById(String id, @RequestParam(value = "clickType", required = false, defaultValue = "Experts")
String clickType) { String clickType) {
return success(achievementsService.get7AddClickById(id,clickType)); return success(achievementsService.get7AddClickById(id, clickType));
} }
@ApiOperation("根据成果ID,获取成果的相关行为数据统计(点击量、搜索量、附件下载量、收藏、关注)") @ApiOperation("根据成果ID,获取成果的相关行为数据统计(点击量、搜索量、附件下载量、收藏、关注)")
@ -243,21 +250,24 @@ public class AchievementsController extends BaseController {
return success(achievementsService.getAllAreas(areaName)); return success(achievementsService.getAllAreas(areaName));
} }
@ApiOperation("根据已有数据,获取融办名称数据")
@GetMapping("/getAllField2")
public AjaxResult getAllField2() {
return success(achievementsService.getAllField2());
}
@ApiOperation("根据领域,将成果数据进行分类") @ApiOperation("根据领域,将成果数据进行分类")
@GetMapping("/getAreaStatistic") @GetMapping("/getAreaStatistic")
public AjaxResult getAreaStatistic(String areaKey) public AjaxResult getAreaStatistic(String areaKey) {
{
return success(achievementsService.getAreaStatistic(areaKey)); return success(achievementsService.getAreaStatistic(areaKey));
} }
@ApiOperation("根据领域获取领域详情") @ApiOperation("根据领域获取领域详情")
@GetMapping("/getAreaDetail") @GetMapping("/getAreaDetail")
public AjaxResult getAreaDetail(String areaKey,String kyxmSum,String source) public AjaxResult getAreaDetail(String areaKey, String kyxmSum, String source) {
{ return success(achievementsService.getAreaDetail(areaKey, kyxmSum, source));
return success(achievementsService.getAreaDetail(areaKey,kyxmSum,source));
} }
/** /**
@ -265,46 +275,59 @@ public class AchievementsController extends BaseController {
*/ */
@ApiOperation(value = "获取开源项目的相关成果") @ApiOperation(value = "获取开源项目的相关成果")
@GetMapping("/getRelatedAch") @GetMapping("/getRelatedAch")
public AjaxResult getRelatedAch(Long id,Long sourceId) public AjaxResult getRelatedAch(Long id, Long sourceId) {
{ return success(achievementsService.getRelatedAch(id, sourceId));
return success(achievementsService.getRelatedAch(id,sourceId));
} }
@ApiOperation(value = "获取开源竞赛的相关成果") @ApiOperation(value = "获取开源竞赛的相关成果")
@GetMapping("/getRelatedAchCompetition") @GetMapping("/getRelatedAchCompetition")
public AjaxResult getRelatedAchCompetition(Long id,Long sourceId) public AjaxResult getRelatedAchCompetition(Long id, Long sourceId) {
{ return success(achievementsService.getRelatedAchCompetition(id, sourceId));
return success(achievementsService.getRelatedAchCompetition(id,sourceId));
} }
@ApiOperation(value = "获取创客任务的相关成果") @ApiOperation(value = "获取创客任务的相关成果")
@GetMapping("/getRelatedAchTask") @GetMapping("/getRelatedAchTask")
public AjaxResult getRelatedAchTask(Long id,Long sourceId) public AjaxResult getRelatedAchTask(Long id, Long sourceId) {
{ return success(achievementsService.getRelatedAchTask(id, sourceId));
return success(achievementsService.getRelatedAchTask(id,sourceId));
} }
@ApiOperation(value = "获取专家-创客任务的相关成果") @ApiOperation(value = "获取专家-创客任务的相关成果")
@GetMapping("/getRelatedAchTaskOfExpert") @GetMapping("/getRelatedAchTaskOfExpert")
public AjaxResult getRelatedAchTaskOfExpert(Long expertId) public AjaxResult getRelatedAchTaskOfExpert(Long expertId) {
{
return success(achievementsService.getRelatedAchTaskOfExpert(expertId)); return success(achievementsService.getRelatedAchTaskOfExpert(expertId));
} }
@ApiOperation(value = "获取专家-开源竞赛的相关成果") @ApiOperation(value = "获取专家-开源竞赛的相关成果")
@GetMapping("/getRelatedAchCompetitionOfExpert") @GetMapping("/getRelatedAchCompetitionOfExpert")
public AjaxResult getRelatedAchCompetitionOfExpert(Long expertId) public AjaxResult getRelatedAchCompetitionOfExpert(Long expertId) {
{
return success(achievementsService.getRelatedAchCompetitionOfExpert(expertId)); return success(achievementsService.getRelatedAchCompetitionOfExpert(expertId));
} }
@ApiOperation(value = "搜索行为数据记录") @ApiOperation(value = "搜索行为数据记录")
@GetMapping("/getSearchResult") @GetMapping("/getSearchResult")
public AjaxResult getSearchResult(String achName,Long userId) public AjaxResult getSearchResult(String achName, Long userId) {
{ return success(achievementsService.getSearchResult(achName, userId));
return success(achievementsService.getSearchResult(achName,userId)); }
@ApiOperation(value = "融办名称分类数据统计")
@GetMapping("/getSource5Statistic")
public AjaxResult getSource5Statistic(String name) {
return success(achievementsService.getSource5Statistic(name));
}
/**
* 导入项目资源库数据
*/
@PostMapping("/importData")
@Log(title = "项目资源库", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
if (file == null || file.isEmpty()) {
return AjaxResult.error("上传文件不能为空");
}
return AjaxResult.success(achievementsService.batchImportAchievementsZip(file, updateSupport));
} }
} }

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.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log; import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType; 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.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService; import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -15,9 +16,11 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 校企成果Controller * 校企成果Controller
@ -90,6 +93,21 @@ public class SchoolEnterpriseAchievementsController extends BaseController
return AjaxResult.success(schoolEnterpriseAchievementsService.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievements)); 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

@ -7,6 +7,7 @@ public class AchQueryVo {
private String achievementName;//名称 private String achievementName;//名称
private Long userId; private Long userId;
private String showAchievementName; private String showAchievementName;
private String rongbanName;
public String getAreaQuery() { public String getAreaQuery() {
return areaQuery; return areaQuery;
} }
@ -46,4 +47,12 @@ public class AchQueryVo {
public void setShowAchievementName(String showAchievementName) { public void setShowAchievementName(String showAchievementName) {
this.showAchievementName = showAchievementName; this.showAchievementName = showAchievementName;
} }
public String getRongbanName() {
return rongbanName;
}
public void setRongbanName(String rongbanName) {
this.rongbanName = rongbanName;
}
} }

View File

@ -0,0 +1,43 @@
package com.microservices.dms.achievementLibrary.domain;
public class AchievementSourceStatisticVo {
private String rongbanName;
private String rongbanDesc;
//成果数
private Long achievementSum;
//点击量浏览量
private Long clickSum;
public String getRongbanName() {
return rongbanName;
}
public void setRongbanName(String rongbanName) {
this.rongbanName = rongbanName;
}
public String getRongbanDesc() {
return rongbanDesc;
}
public void setRongbanDesc(String rongbanDesc) {
this.rongbanDesc = rongbanDesc;
}
public Long getAchievementSum() {
return achievementSum;
}
public void setAchievementSum(Long achievementSum) {
this.achievementSum = achievementSum;
}
public Long getClickSum() {
return clickSum;
}
public void setClickSum(Long clickSum) {
this.clickSum = clickSum;
}
}

View File

@ -38,21 +38,24 @@ public class Achievements extends BaseEntity {
/** /**
* 成果领域1 * 成果领域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; private String field1;
//成果领域名称 //成果领域名称
private String field1Name; private String field1Name;
/** /**
* 成果领域2 * 融办名称
*/ */
@Excel(name = "成果领域2") @Excel(name = "融办名称")
private String field2; private String field2;
/** /**
* 成果领域3 * 融办描述
*/ */
@Excel(name = "成果领域3") @Excel(name = "融办描述")
private String field3; private String field3;
/** /**
@ -64,7 +67,7 @@ public class Achievements extends BaseEntity {
/** /**
* 成果来源 * 成果来源
*/ */
@Excel(name = "成果来源") @Excel(name = "成果来源", readConverterExp = "1=开源项目,2=创客任务,3=开放竞赛,4=校企成果,5=省市融办")
private String source; private String source;
/** /**
@ -74,15 +77,15 @@ public class Achievements extends BaseEntity {
private Long sourceId; private Long sourceId;
/** /**
* 成果来源跳转链接 * 成果来源渠道
*/ */
@Excel(name = "成果来源跳转链接") @Excel(name = "成果来源渠道")
private String sourceLink; private String sourceLink;
/** /**
* 成果标签 * 成果标签
*/ */
@Excel(name = "成果标签") @Excel(name = "成果标签", readConverterExp = "1=高校,2=企业,3=其他")
private String tags; private String tags;
/** /**
@ -94,13 +97,13 @@ public class Achievements extends BaseEntity {
/** /**
* 发布单位 * 发布单位
*/ */
@Excel(name = "发布单位") @Excel(name = "单位名称")
private String publishingUnit; private String publishingUnit;
/** /**
* 成果地址 * 单位概况
*/ */
@Excel(name = "成果地址") @Excel(name = "单位概况")
private String address; private String address;
/** /**
@ -116,9 +119,9 @@ public class Achievements extends BaseEntity {
private String contactPerson; private String contactPerson;
/** /**
* 联系电话 * 联系方式
*/ */
@Excel(name = "联系电话") @Excel(name = "联系方式")
private String contactNumber; private String contactNumber;
/** /**
@ -140,9 +143,9 @@ public class Achievements extends BaseEntity {
private String status; private String status;
/** /**
* 成果详情 * 成果简介
*/ */
@Excel(name = "成果详情") @Excel(name = "成果简介")
private String details; private String details;
/** /**
@ -167,7 +170,7 @@ public class Achievements extends BaseEntity {
/** /**
* 成果图片 * 成果图片
*/ */
@Excel(name = "成果图片") @Excel(name = "图片")
private String images; private String images;
/** /**
@ -177,10 +180,14 @@ public class Achievements extends BaseEntity {
private String attachments; private String attachments;
//成果小类 //成果小类
@Excel(name = "成果图片视频")
private String ext1; private String ext1;
//成果状态 //成果状态
@Excel(name = "应用图片视频")
private String ext2; private String ext2;
//成果团队名称 //成果团队名称
@Excel(name = "应用情况")
private String ext3; private String ext3;
private Long clickSum; private Long clickSum;

View File

@ -32,7 +32,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/** /**
* 成果领域1 * 成果领域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; 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; private String achievementType;
/** /**
@ -74,7 +77,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/** /**
* 成果标签 * 成果标签
*/ */
@Excel(name = "成果标签") @Excel(
name = "成果标签",
readConverterExp = "1=高校,2=企业,3=其他"
)
private String tags; private String tags;
/** /**
@ -159,7 +165,7 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/** /**
* 成果图片 * 成果图片
*/ */
@Excel(name = "成果图片") @Excel(name = "图片")
private String images; private String images;
/** /**
@ -169,10 +175,18 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
private String attachments; private String attachments;
private Integer achievementStatus; 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; private String achievementLittleType;
//成果状态 @Excel(
name = "成果状态",
readConverterExp = "1=已有阶段性成果,2=已有成熟成果,3=已公开发表,4=已对外发布,5=已开展应用,6=已形成商业化产品"
)
private String achievementCurStatus; private String achievementCurStatus;
//团队名称 //团队名称
@Excel(name = "成果团队名称")
private String teamName; private String teamName;
private List<AchievementTeam> teamList; private List<AchievementTeam> teamList;

View File

@ -6,13 +6,13 @@ import com.microservices.common.datasource.annotation.Master;
import com.microservices.common.datasource.annotation.Slave; import com.microservices.common.datasource.annotation.Slave;
import com.microservices.dms.achievementLibrary.domain.*; import com.microservices.dms.achievementLibrary.domain.*;
import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo; import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo;
import com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo;
import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary; import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary; import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo; import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.springframework.beans.PropertyValues;
/** /**
* 成果Mapper接口 * 成果Mapper接口
@ -30,6 +30,7 @@ public interface AchievementsMapper {
* @return 成果 * @return 成果
*/ */
public Achievements selectAchievementsById(Long id); public Achievements selectAchievementsById(Long id);
public Achievements selectAchievementsByAchievementName(String achievementName);
/** /**
* 查询成果列表 * 查询成果列表
@ -165,10 +166,14 @@ public interface AchievementsMapper {
List<KeyValVo<String, Long>> getExperts(@Param("s") Date s, @Param("e") Date e); List<KeyValVo<String, Long>> getExperts(@Param("s") Date s, @Param("e") Date e);
List<KeyValueVo> getAllAreas(@Param("areaName") String areaName); List<KeyValueVo> getAllAreas(@Param("areaName") String areaName);
List<KeyValueVo> getAllField2();
void updateRelationAchievements(@Param("source") String source,@Param("sourceId") Long sourceId); void updateRelationAchievements(@Param("source") String source,@Param("sourceId") Long sourceId);
List<Map<String, Object>> getDockingUserInfoList(@Param("id") Long id); List<Map<String, Object>> getDockingUserInfoList(@Param("id") Long id);
String getField1RemarkByParam(@Param("source")String source,@Param("areaKey") String areaKey); String getField1RemarkByParam(@Param("source")String source,@Param("areaKey") String areaKey);
AchievementSourceStatisticVo getSource5Statistic(@Param("name") String name);
AchievementSourceStatisticVo getSource5Field2(@Param("name") String name);
} }

View File

@ -5,9 +5,11 @@ import java.util.Map;
import com.microservices.dms.achievementLibrary.domain.*; import com.microservices.dms.achievementLibrary.domain.*;
import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo; import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo;
import com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo;
import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary; import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary; import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo; import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
import org.springframework.web.multipart.MultipartFile;
/** /**
* 成果Service接口 * 成果Service接口
@ -106,6 +108,8 @@ public interface IAchievementsService {
int getSearchResult(String achName, Long userId); int getSearchResult(String achName, Long userId);
AchievementSourceStatisticVo getSource5Statistic(String name);
List<KeyValueVo> getProjectAreasByName(String areaName); List<KeyValueVo> getProjectAreasByName(String areaName);
List<String> getExpertAreasByName(String areaName); List<String> getExpertAreasByName(String areaName);
@ -123,5 +127,9 @@ public interface IAchievementsService {
List<KeyValVo<String, Long>> get7DayAddExport(AchQueryVo achQueryVo); List<KeyValVo<String, Long>> get7DayAddExport(AchQueryVo achQueryVo);
List<KeyValueVo> getAllAreas(String areaName); List<KeyValueVo> getAllAreas(String areaName);
List<KeyValueVo> getAllField2();
String batchInsertAchievements(List<Achievements> list, boolean updateSupport, String operName);
String batchImportAchievementsZip(MultipartFile file, boolean updateSupport);
} }

View File

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

View File

@ -1,40 +1,54 @@
package com.microservices.dms.achievementLibrary.service.impl; package com.microservices.dms.achievementLibrary.service.impl;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.utils.DateUtils; import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils; import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.httpClient.service.HttpAPIService; import com.microservices.common.core.utils.poi.ExcelUtil;
import com.microservices.common.security.utils.SecurityUtils; import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.*; import com.microservices.dms.achievementLibrary.domain.*;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo; import com.microservices.dms.behaviorImage.domain.AchievementBehaviorSumVo;
import com.microservices.dms.behaviorImage.domain.AchievementImageVo; import com.microservices.dms.behaviorImage.domain.AchievementImageVo;
import com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo;
import com.microservices.dms.behaviorImage.service.IBehaviorImageService; import com.microservices.dms.behaviorImage.service.IBehaviorImageService;
import com.microservices.dms.resourceLibrary.domain.Clicker; import com.microservices.dms.common.vo.AchievementSysFileVo;
import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary; import com.microservices.dms.resourceLibrary.domain.*;
import com.microservices.dms.resourceLibrary.domain.Searcher;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo; import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
import com.microservices.dms.resourceLibrary.mapper.CompetitionResourceLibraryMapper;
import com.microservices.dms.resourceLibrary.mapper.ExpertResourceLibraryMapper; import com.microservices.dms.resourceLibrary.mapper.ExpertResourceLibraryMapper;
import com.microservices.dms.resourceLibrary.mapper.ProjectResourceLibraryMapper;
import com.microservices.dms.resourceLibrary.mapper.TaskResourceLibraryMapper; import com.microservices.dms.resourceLibrary.mapper.TaskResourceLibraryMapper;
import com.microservices.dms.utils.CustomMultipartFile;
import com.microservices.dms.utils.DateUtil; import com.microservices.dms.utils.DateUtil;
import com.microservices.dms.utils.FileMultipartFile;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.K; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper; import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
import com.microservices.dms.achievementLibrary.service.IAchievementsService; import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import org.springframework.web.multipart.MultipartFile;
import javax.activation.FileTypeMap;
import static com.microservices.dms.constant.BlockChainFunConstants.QUERY_REPO;
import static com.microservices.dms.utils.UrlUtil.getUrlPath; import static com.microservices.dms.utils.UrlUtil.getUrlPath;
/** /**
@ -46,6 +60,9 @@ import static com.microservices.dms.utils.UrlUtil.getUrlPath;
@Service @Service
@Slf4j @Slf4j
public class AchievementsServiceImpl implements IAchievementsService { public class AchievementsServiceImpl implements IAchievementsService {
private static final Logger logger = LoggerFactory.getLogger(AchievementsServiceImpl.class);
@Autowired @Autowired
private AchievementsMapper achievementsMapper; private AchievementsMapper achievementsMapper;
@ -59,6 +76,9 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Autowired @Autowired
private IBehaviorImageService behaviorImageService; private IBehaviorImageService behaviorImageService;
@Autowired
private RemoteFileService remoteFileService;
@Value("${makerSpaceUrl}") @Value("${makerSpaceUrl}")
public String makerSpaceUrl; public String makerSpaceUrl;
@ -86,7 +106,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
img = "images/avatars/User/boy.jpg"; img = "images/avatars/User/boy.jpg";
} }
a.setUserImg(img); a.setUserImg(img);
if (StringUtils.isNotEmpty(a.getField1())) { if (StringUtils.isNotEmpty(a.getField1()) && !a.getSource().equals("5")) {
String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId()); String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId());
if (StringUtils.isNotNull(field1Name)) { if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name); a.setField1Name(field1Name);
@ -96,6 +116,8 @@ public class AchievementsServiceImpl implements IAchievementsService {
a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource())); a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource()));
a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource())); a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource()));
// a.setExt1(buildFileInfoByIdentifiers(a.getExt1()));
// a.setExt2(buildFileInfoByIdentifiers(a.getExt2()));
} }
return a; return a;
} }
@ -119,7 +141,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
} }
a.setUserImg(img); a.setUserImg(img);
//根据id获取领域名称 //根据id获取领域名称
if (StringUtils.isNotEmpty(a.getField1())) { if (StringUtils.isNotEmpty(a.getField1()) && !a.getSource().equals("5")) {
String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId()); String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId());
if (StringUtils.isNotNull(field1Name)) { if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name); a.setField1Name(field1Name);
@ -127,6 +149,8 @@ public class AchievementsServiceImpl implements IAchievementsService {
} }
a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource())); a.setImages(buildFileInfoByIdents(a.getImages(), a.getSource()));
a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource())); a.setAttachments(buildFileInfoByIdents(a.getAttachments(), a.getSource()));
// a.setExt1(buildFileInfoByIdentifiers(a.getExt1()));
// a.setExt2(buildFileInfoByIdentifiers(a.getExt2()));
//拼接对接的用户信息 //拼接对接的用户信息
List<Map<String,Object>> dockingUserInfo = achievementsMapper.getDockingUserInfoList(a.getId()); List<Map<String,Object>> dockingUserInfo = achievementsMapper.getDockingUserInfoList(a.getId());
@ -266,7 +290,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
} }
a.setUserImg(img); a.setUserImg(img);
//根据id获取领域名称 //根据id获取领域名称
if (StringUtils.isEmpty(achQueryVo.getSource()) && StringUtils.isNotEmpty(a.getField1())) { if (StringUtils.isEmpty(achQueryVo.getSource()) && StringUtils.isNotEmpty(a.getField1()) && !a.getSource().equals("5")) {
String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId()); String field1Name = achievementsMapper.getField1NameByParam(a.getSource(), a.getId());
if (StringUtils.isNotNull(field1Name)) { if (StringUtils.isNotNull(field1Name)) {
a.setField1Name(field1Name); a.setField1Name(field1Name);
@ -513,17 +537,19 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Override @Override
public List<AchRelatedResult> getRelatedAchCompetition(Long id, Long sourceId) { public List<AchRelatedResult> getRelatedAchCompetition(Long id, Long sourceId) {
List<AchRelatedResult> relatedResultList = new ArrayList<>(); List<AchRelatedResult> relatedResultList = new ArrayList<>();
//想根据开源项目id获取对应的年度信息 if (sourceId !=null){
List<Long> allSourceId = achievementsMapper.getAllSourceIdCompetition(id, sourceId); //想根据开源项目id获取对应的年度信息
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId); List<Long> allSourceId = achievementsMapper.getAllSourceIdCompetition(id, sourceId);
if (StringUtils.isNotNull(allYear)) { List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
//遍历年度信息获取年度成果数据 if (StringUtils.isNotNull(allYear)) {
for (String paramYear : allYear) { //遍历年度信息获取年度成果数据
AchRelatedResult relatedResult = new AchRelatedResult(); for (String paramYear : allYear) {
relatedResult.setYear(paramYear); AchRelatedResult relatedResult = new AchRelatedResult();
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear); relatedResult.setYear(paramYear);
relatedResult.setAchRelatedVoList(tmpList); List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResultList.add(relatedResult); relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
}
} }
} }
return relatedResultList; return relatedResultList;
@ -532,17 +558,19 @@ public class AchievementsServiceImpl implements IAchievementsService {
@Override @Override
public List<AchRelatedResult> getRelatedAchTask(Long id, Long sourceId) { public List<AchRelatedResult> getRelatedAchTask(Long id, Long sourceId) {
List<AchRelatedResult> relatedResultList = new ArrayList<>(); List<AchRelatedResult> relatedResultList = new ArrayList<>();
//想根据开源项目id获取对应的年度信息 if (sourceId !=null){
List<Long> allSourceId = achievementsMapper.getAllSourceIdTask(id, sourceId); //想根据开源项目id获取对应的年度信息
List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId); List<Long> allSourceId = achievementsMapper.getAllSourceIdTask(id, sourceId);
if (StringUtils.isNotNull(allYear)) { List<String> allYear = achievementsMapper.getDistinctYear(id, allSourceId);
//遍历年度信息获取年度成果数据 if (StringUtils.isNotNull(allYear)) {
for (String paramYear : allYear) { //遍历年度信息获取年度成果数据
AchRelatedResult relatedResult = new AchRelatedResult(); for (String paramYear : allYear) {
relatedResult.setYear(paramYear); AchRelatedResult relatedResult = new AchRelatedResult();
List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear); relatedResult.setYear(paramYear);
relatedResult.setAchRelatedVoList(tmpList); List<AchRelatedVo> tmpList = achievementsMapper.getRelatedAch(id, allSourceId, paramYear);
relatedResultList.add(relatedResult); relatedResult.setAchRelatedVoList(tmpList);
relatedResultList.add(relatedResult);
}
} }
} }
return relatedResultList; return relatedResultList;
@ -587,6 +615,15 @@ public class AchievementsServiceImpl implements IAchievementsService {
return num; return num;
} }
@Override
public AchievementSourceStatisticVo getSource5Statistic(String name) {
AchievementSourceStatisticVo ronbanVo = achievementsMapper.getSource5Field2(name);
AchievementSourceStatisticVo statisticVo = achievementsMapper.getSource5Statistic(name);
ronbanVo.setClickSum(statisticVo.getClickSum());
ronbanVo.setAchievementSum(statisticVo.getAchievementSum());
return ronbanVo;
}
@Override @Override
public List<KeyValueVo> getProjectAreasByName(String areaName) { public List<KeyValueVo> getProjectAreasByName(String areaName) {
return achievementsMapper.getProjectAreasByName(areaName); return achievementsMapper.getProjectAreasByName(areaName);
@ -629,4 +666,334 @@ public class AchievementsServiceImpl implements IAchievementsService {
return resultVal; return resultVal;
} }
@Override
public List<KeyValueVo> getAllField2() {
List<KeyValueVo> resultVal = achievementsMapper.getAllField2();
return resultVal;
}
@Override
public String batchInsertAchievements(List<Achievements> list, boolean updateSupport, String operName) {
if (list == null || list.isEmpty()) {
throw new RuntimeException("导入项目资源数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
for (Achievements achievements : list) {
try {
// 查重根据项目名称查询
Achievements exist = achievementsMapper.selectAchievementsByAchievementName(achievements.getAchievementName());
if (null == exist) {
achievements.setStatus("1");
achievements.setCreateBy(operName);
achievements.setCreateTime(new Date());
achievements.setUpdateBy(operName);
achievements.setUpdateTime(new Date());
achievementsMapper.insertAchievements(achievements);
successNum++;
successMsg.append("<br/>").append(successNum).append("、项目 ").append(achievements.getAchievementName()).append(" 导入成功");
} else {
if (updateSupport) {
achievements.setStatus("1");
achievements.setId(exist.getId());
achievements.setCreateBy(exist.getCreateBy());
achievements.setCreateTime(exist.getCreateTime());
achievements.setUpdateBy(operName);
achievements.setUpdateTime(new Date());
achievementsMapper.updateAchievements(achievements);
successNum++;
successMsg.append("<br/>").append(successNum).append("、项目 ").append(achievements.getAchievementName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、项目 ").append(achievements.getAchievementName()).append(" 已存在(未开启更新)");
}
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、项目 " + achievements.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);
}
return finalMsg.toString();
}
@Override
public String batchImportAchievementsZip(MultipartFile file, boolean updateSupport) {
// 尝试多种编码
Charset[] charsets = {
StandardCharsets.UTF_8,
Charset.forName("GBK"),
Charset.forName("GB2312"),
Charset.forName("ISO-8859-1")
};
Map<String, Object> result = new HashMap<>();
List<Achievements> excelDataList = new ArrayList<>();
Map<String, AchievementSysFileVo> mp4ImagesExt1 = new HashMap<>();
Map<String, AchievementSysFileVo> mp4ImagesExt2 = new HashMap<>();
Map<String, AchievementSysFileVo> attachmentMap = new HashMap<>();
for (Charset charset : charsets) {
ZipInputStream zipInputStream = null;
try {
zipInputStream = new ZipInputStream(file.getInputStream(), charset);
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
String entryName = entry.getName();
// 跳过目录条目
if (entry.isDirectory()) {
continue;
}
// 统一路径分隔符处理Windows和Unix
String normalizedPath = entryName.replace('\\', '/');
String fileName = getFileName(normalizedPath);
String parentPath = getParentPath(normalizedPath);
// 1. 处理 Excel 文件
if (!entryName.startsWith("附件") && (entryName.endsWith(".xlsx") || entryName.endsWith(".xls"))) {
byte[] content = readZipEntryContent(zipInputStream);
try (InputStream excelStream = new ByteArrayInputStream(content)) {
ExcelUtil<Achievements> util = new ExcelUtil<>(Achievements.class);
List<Achievements> dataList = util.importExcel(excelStream);
excelDataList.addAll(dataList);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 2. 处理 MP4 - 大文件建议保存到临时文件
else if (parentPath.startsWith("成果图片视频") && entryName.endsWith(".mp4")) {
File tempFile = saveToTempFile(zipInputStream, fileName);
String contentType = "video/mp4";
// 转换为MultipartFile并存储
MultipartFile videoFile = new FileMultipartFile(tempFile, fileName, contentType);
// 上传视频...
R<SysFile> fileResult = remoteFileService.upload(videoFile, "dms", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setK(videoFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt1.put(fileName, transferVo);
}
// 清理临时文件
tempFile.delete();
} else if (parentPath.startsWith("应用图片视频") && entryName.endsWith(".mp4")) {
File tempFile = saveToTempFile(zipInputStream, fileName);
String contentType = "video/mp4";
// 转换为MultipartFile并存储
MultipartFile videoFile = new FileMultipartFile(tempFile, fileName, contentType);
// 上传视频...
R<SysFile> fileResult = remoteFileService.upload(videoFile, "dms", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setK(videoFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt2.put(fileName, transferVo);
}
// 清理临时文件
tempFile.delete();
}
// 3. 处理图片文件
else if (parentPath.startsWith("成果图片视频") && isImageFile(fileName)) {
byte[] content = readZipEntryContent(zipInputStream);
String suffix = "png";
if (entryName != null && entryName.contains(".")) {
suffix = entryName.substring(entryName.lastIndexOf(".") + 1);
}
String contentType = "image/" + suffix;
// 转换为MultipartFile并存储
MultipartFile imageFile = new CustomMultipartFile(content, fileName, contentType);
R<SysFile> fileResult = remoteFileService.upload(imageFile, "dms", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setK(imageFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt1.put(fileName, transferVo);
}
}
// 3. 处理图片文件
else if (parentPath.startsWith("应用图片视频") && isImageFile(fileName)) {
byte[] content = readZipEntryContent(zipInputStream);
String suffix = "png";
if (entryName != null && entryName.contains(".")) {
suffix = entryName.substring(entryName.lastIndexOf(".") + 1);
}
String contentType = "image/" + suffix;
// 转换为MultipartFile并存储
MultipartFile imageFile = new CustomMultipartFile(content, fileName, contentType);
R<SysFile> fileResult = remoteFileService.upload(imageFile, "dms", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setK(imageFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
mp4ImagesExt2.put(fileName, transferVo);
}
}
else if (parentPath.startsWith("附件")) {
File tempFile = saveToTempFile(zipInputStream, fileName);
String contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(fileName);
// 转换为MultipartFile并存储
MultipartFile attachmentFile = new FileMultipartFile(tempFile, fileName, contentType);
// 上传视频...
R<SysFile> fileResult = remoteFileService.upload(attachmentFile, "dms", "import", SecurityConstants.INNER);
if (R.SUCCESS == fileResult.getCode()) {
SysFile sysFile = fileResult.getData();
AchievementSysFileVo transferVo = new AchievementSysFileVo();
BeanUtils.copyProperties(sysFile, transferVo);
transferVo.setK(attachmentFile.getOriginalFilename());
transferVo.setV(sysFile.getUrl());
attachmentMap.put(fileName, transferVo);
}
// 清理临时文件
tempFile.delete();
}
zipInputStream.closeEntry();
}
logger.info("✅ 使用 " + charset.displayName() + " 编码成功读取 ZIP");
excelDataList.forEach(achievements -> {
List<Object> mp4ImagesExt1List = mp4ImagesExt1.entrySet().stream()
.filter(item -> item.getKey().contains(achievements.getAchievementName()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
List<Object> mp4ImagesExt2List = mp4ImagesExt2.entrySet().stream()
.filter(item -> item.getKey().contains(achievements.getAchievementName()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
List<Object> filterAttachmentList = attachmentMap.entrySet().stream()
.filter(item -> item.getKey().contains(achievements.getAchievementName()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
if (!mp4ImagesExt1List.isEmpty()){
achievements.setExt1(JSONObject.toJSONString(mp4ImagesExt1List));
}
if (!mp4ImagesExt2List.isEmpty()){
achievements.setExt2(JSONObject.toJSONString(mp4ImagesExt2List));
}
if (!filterAttachmentList.isEmpty()){
achievements.setAttachments(JSONObject.toJSONString(filterAttachmentList));
}
});
// 保存 Excel 数据到数据库
String operName = SecurityUtils.getUsername();
String message = excelDataList.isEmpty() ? "" : batchInsertAchievements(excelDataList, updateSupport, operName);
message = message + "<br/>压缩包共导入 " + (mp4ImagesExt1.size() + mp4ImagesExt2.size() + attachmentMap.size()) + " 个文件";
result.put("excelDataCount", excelDataList.size());
result.put("mp4ImagesExt1", mp4ImagesExt1);
result.put("mp4ImagesExt2", mp4ImagesExt2);
result.put("attachments", attachmentMap);
result.put("message", message);
return message;
} catch (IOException e) {
logger.error("读取文件失败: " + e.getMessage());
} catch (IllegalArgumentException ex) {
logger.error("使用 " + charset.displayName() + " 编码失败: " + ex.getMessage());
} finally {
if (zipInputStream != null) {
try {
zipInputStream.close();
}catch (IOException exc){
exc.printStackTrace();
}
}
}
}
return "";
}
/**
* 保存流内容到临时文件
*/
private File saveToTempFile(ZipInputStream zipInputStream, String fileName) throws IOException {
Path tempFile = Files.createTempFile("temp_", "_" + fileName);
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
byte[] buffer = new byte[8192];
int len;
while ((len = zipInputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
return tempFile.toFile();
}
/**
* 读取 ZipEntry 的内容到字节数组Java 8 兼容
*/
private byte[] readZipEntryContent(ZipInputStream zipInputStream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int len;
while ((len = zipInputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
// 辅助方法判断是否为图片
private boolean isImageFile(String fileName) {
String lower = fileName.toLowerCase();
return lower.endsWith(".jpg") || lower.endsWith(".jpeg") ||
lower.endsWith(".png") || lower.endsWith(".gif") || lower.endsWith(".bmp");
}
/**
* 获取文件名去掉路径
*/
private String getFileName(String path) {
if (path == null || path.isEmpty()) return "";
int lastSlash = path.lastIndexOf('/');
if (lastSlash >= 0) {
return path.substring(lastSlash + 1);
}
return path;
}
/**
* 获取父路径去掉文件名
*/
private String getParentPath(String path) {
if (path == null || path.isEmpty()) return "";
int lastSlash = path.lastIndexOf('/');
if (lastSlash >= 0) {
return path.substring(0, lastSlash);
}
return "";
}
private String buildFileInfoByIdentifiers(String identifiers) {
if (StringUtils.isEmpty(identifiers)) {
return identifiers;
}
R<List<SysFileInfo>> result = remoteFileService.getFileListByIdentifier(identifiers);
return JSON.toJSONString(result.getData());
}
} }

View File

@ -1,25 +1,34 @@
package com.microservices.dms.achievementLibrary.service.impl; package com.microservices.dms.achievementLibrary.service.impl;
import java.io.*;
import java.util.*; 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.constant.UserConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.exception.ServiceException; import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils; import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils; import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.datasource.annotation.Slave;
import com.microservices.common.security.utils.SecurityUtils; import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.AchievementTeam; import com.microservices.dms.achievementLibrary.domain.AchievementTeam;
import com.microservices.dms.achievementLibrary.domain.Achievements; import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.mapper.AchievementTeamMapper; import com.microservices.dms.achievementLibrary.mapper.AchievementTeamMapper;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper; import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
import com.microservices.dms.achievementLibrary.service.IAchievementsService; 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 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper; import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements; import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService; 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业务层处理 * 校企成果Service业务层处理
@ -41,6 +50,9 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
@Autowired @Autowired
private AchievementsMapper achievementsMapper; private AchievementsMapper achievementsMapper;
@Autowired
private RemoteFileService remoteFileService;
/** /**
* 查询校企成果 * 查询校企成果
* *
@ -124,6 +136,144 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
return UserConstants.UNIQUE; 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

@ -0,0 +1,18 @@
package com.microservices.dms.common.vo;
import com.microservices.system.api.domain.SysFile;
import lombok.Data;
@Data
public class AchievementSysFileVo extends SysFile {
/**
* 文件名称
*/
private String k;
/**
* 文件地址
*/
private String v;
}

View File

@ -1,11 +1,15 @@
package com.microservices.dms.resourceLibrary.controller; package com.microservices.dms.resourceLibrary.controller;
import com.microservices.common.core.utils.PageUtils; import com.microservices.common.core.utils.PageUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.poi.ExcelUtil; import com.microservices.common.core.utils.poi.ExcelUtil;
import com.microservices.common.core.web.controller.BaseController; import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult; import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo; import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.core.web.page.TableDataInfo; 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.resourceLibrary.domain.CompetitionResourceLibrary; import com.microservices.dms.resourceLibrary.domain.CompetitionResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.CompetitionListVo; import com.microservices.dms.resourceLibrary.domain.vo.CompetitionListVo;
import com.microservices.dms.resourceLibrary.service.CompetitionResourceLibraryService; import com.microservices.dms.resourceLibrary.service.CompetitionResourceLibraryService;
@ -17,6 +21,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
@ -78,6 +83,17 @@ public class CompetitionResourceLibraryController extends BaseController {
util.exportExcel(response, list, "数据"); util.exportExcel(response, list, "数据");
} }
@PostMapping("/importData")
@Log(title = "竞赛资源库", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<CompetitionResourceLibrary> util = new ExcelUtil<CompetitionResourceLibrary>(CompetitionResourceLibrary.class);
List<CompetitionResourceLibrary> list = util.importExcel(StringUtils.EMPTY, file.getInputStream(), 0);
String operName = SecurityUtils.getUsername();
String message = competitionResourceLibraryService.batchInsertCompetitionResourceLibrary(list, updateSupport, operName);
return AjaxResult.success(message);
}
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) { public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(competitionResourceLibraryService.selectCompetitionResourceLibraryById(id)); return success(competitionResourceLibraryService.selectCompetitionResourceLibraryById(id));

View File

@ -1,6 +1,7 @@
package com.microservices.dms.resourceLibrary.controller; package com.microservices.dms.resourceLibrary.controller;
import com.microservices.common.core.utils.PageUtils; import com.microservices.common.core.utils.PageUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.poi.ExcelUtil; import com.microservices.common.core.utils.poi.ExcelUtil;
import com.microservices.common.core.web.controller.BaseController; import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult; import com.microservices.common.core.web.domain.AjaxResult;
@ -8,16 +9,18 @@ import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.core.web.page.TableDataInfo; import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.dms.resourceLibrary.domain.ProjectResourceLibrary; import com.microservices.dms.resourceLibrary.domain.ProjectResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.ProjectListVo; import com.microservices.dms.resourceLibrary.domain.vo.ProjectListVo;
import com.microservices.dms.resourceLibrary.domain.vo.TaskListVo; 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.resourceLibrary.domain.vo.UserActionDataVo; import com.microservices.dms.resourceLibrary.domain.vo.UserActionDataVo;
import com.microservices.dms.resourceLibrary.service.ProjectResourceLibraryService; import com.microservices.dms.resourceLibrary.service.ProjectResourceLibraryService;
import com.microservices.dms.resourceLibrary.service.TaskResourceLibraryService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
@ -122,6 +125,23 @@ public class ProjectResourceLibraryController extends BaseController {
util.exportExcel(response, list, "数据"); util.exportExcel(response, list, "数据");
} }
/**
* 导入项目资源库数据
*/
@PostMapping("/importData")
@Log(title = "项目资源库", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<ProjectResourceLibrary> util = new ExcelUtil<>(ProjectResourceLibrary.class);
// 解析Excel获取实体列表
List<ProjectResourceLibrary> list = util.importExcel(StringUtils.EMPTY, file.getInputStream(), 0);
// 调用业务层处理
String operName = SecurityUtils.getUsername();
String message = projectResourceLibraryService.batchInsertProjectResourceLibrary(list, updateSupport, operName);
return AjaxResult.success(message);
}
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) { public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(projectResourceLibraryService.selectProjectResourceLibraryById(id)); return success(projectResourceLibraryService.selectProjectResourceLibraryById(id));

View File

@ -1,6 +1,7 @@
package com.microservices.dms.resourceLibrary.controller; package com.microservices.dms.resourceLibrary.controller;
import com.microservices.common.core.utils.PageUtils; import com.microservices.common.core.utils.PageUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.poi.ExcelUtil; import com.microservices.common.core.utils.poi.ExcelUtil;
import com.microservices.common.core.web.controller.BaseController; import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult; import com.microservices.common.core.web.domain.AjaxResult;
@ -8,6 +9,7 @@ import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.core.web.page.TableDataInfo; import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log; import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType; import com.microservices.common.log.enums.BusinessType;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary; import com.microservices.dms.resourceLibrary.domain.TaskResourceLibrary;
import com.microservices.dms.resourceLibrary.domain.vo.TaskListVo; import com.microservices.dms.resourceLibrary.domain.vo.TaskListVo;
import com.microservices.dms.resourceLibrary.domain.vo.UserActionDataVo; import com.microservices.dms.resourceLibrary.domain.vo.UserActionDataVo;
@ -18,6 +20,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
@ -134,6 +137,17 @@ public class TaskResourceLibraryController extends BaseController {
util.exportExcel(response, list, "数据"); util.exportExcel(response, list, "数据");
} }
@PostMapping("/importData")
@Log(title = "任务资源库", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<TaskResourceLibrary> util = new ExcelUtil<TaskResourceLibrary>(TaskResourceLibrary.class);
List<TaskResourceLibrary> list = util.importExcel(StringUtils.EMPTY, file.getInputStream(), 0);
String operName = SecurityUtils.getUsername();
String message = taskResourceLibraryService.batchInsertTaskResourceLibrary(list, updateSupport, operName);
return AjaxResult.success(message);
}
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) { public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(taskResourceLibraryService.selectTaskResourceLibraryById(id)); return success(taskResourceLibraryService.selectTaskResourceLibraryById(id));

View File

@ -5,6 +5,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.common.core.annotation.Excel;
import com.microservices.common.core.web.domain.BaseEntity; import com.microservices.common.core.web.domain.BaseEntity;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo; import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
@ -19,10 +20,12 @@ public class CompetitionResourceLibrary extends BaseEntity {
/** /**
* 开放竞赛ID * 开放竞赛ID
*/ */
@Excel(name = "竞赛ID")
private Long openCompetitionId; private Long openCompetitionId;
/** /**
* 开放竞赛名称 * 开放竞赛名称
*/ */
@Excel(name = "竞赛名称")
private String openCompetitionName; private String openCompetitionName;
/** /**
* 开放竞赛创建者 * 开放竞赛创建者
@ -31,23 +34,28 @@ public class CompetitionResourceLibrary extends BaseEntity {
/** /**
* 竞赛领域 * 竞赛领域
*/ */
@Excel(name = "成果领域")
private String competitionField; private String competitionField;
/** /**
* 竞赛状态 status: 0 未上架 1上架 * 竞赛状态 status: 0 未上架 1上架
*/ */
@Excel(name = "竞赛状态", readConverterExp = "0=未上架,1=上架")
private String competitionStatus; private String competitionStatus;
/** /**
* 竞赛提交名称 * 竞赛提交名称
*/ */
@Excel(name = "作品名称")
private String competitionSubmissionName; private String competitionSubmissionName;
/** /**
* 作品提交时间 * 作品提交时间
*/ */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "作品提交时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date submissionTime; private Date submissionTime;
/** /**
* 提交者 * 提交者
*/ */
@Excel(name = "提交人")
private String submitter; private String submitter;
/** /**
* 提交类型 * 提交类型
@ -76,11 +84,13 @@ public class CompetitionResourceLibrary extends BaseEntity {
/** /**
* 是否已转入成果库 0否 1是 * 是否已转入成果库 0否 1是
*/ */
@Excel(name = "转入状态", readConverterExp = "0=未转入,1=已转入")
private Integer isTransferredToResultLibrary; private Integer isTransferredToResultLibrary;
/** /**
* 转入成果库时间 * 转入成果库时间
*/ */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "转入时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date transferredToResultLibraryTime; private Date transferredToResultLibraryTime;
/** /**
* 是否精选成果 * 是否精选成果

View File

@ -5,6 +5,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.common.core.annotation.Excel;
import com.microservices.common.core.web.domain.BaseEntity; import com.microservices.common.core.web.domain.BaseEntity;
import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo; import com.microservices.dms.resourceLibrary.domain.vo.KeyValVo;
@ -18,8 +19,10 @@ public class ProjectResourceLibrary extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Long id; private Long id;
//项目资源名称 //项目资源名称
@Excel(name = "项目名称")
private String projectName; private String projectName;
//项目领域 //项目领域
@Excel(name = "成果领域")
private String projectDomain; private String projectDomain;
//项目领域名称 //项目领域名称
private String projectDomainName; private String projectDomainName;
@ -27,19 +30,24 @@ public class ProjectResourceLibrary extends BaseEntity {
private String identifier; private String identifier;
private String userIdentifier; private String userIdentifier;
//开源项目ID //开源项目ID
@Excel(name = "项目ID")
private Long projectId; private Long projectId;
//仓库ID //仓库ID
private Long repositoryId; private Long repositoryId;
//版本Id //版本Id
private Long versionReleasesId; private Long versionReleasesId;
//项目是否已公开 //项目是否已公开
@Excel(name = "是否公开", readConverterExp = "0=否,1=是")
private Long isPublic; private Long isPublic;
//发版名称 //发版名称
@Excel(name = "发行版名称")
private String releaseName; private String releaseName;
//发版时间 //发版时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "发版时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date releaseDate; private Date releaseDate;
//发版人 //发版人
@Excel(name = "发版人")
private String releasePerson; private String releasePerson;
//发版类型 //发版类型
private String releaseType; private String releaseType;
@ -54,9 +62,11 @@ public class ProjectResourceLibrary extends BaseEntity {
//联系时间 //联系时间
private String contactPhone; private String contactPhone;
//是否已转入成果库 //是否已转入成果库
@Excel(name = "转入状态", readConverterExp = "0=未转入,1=已转入")
private Long isTransferredToAchievement; private Long isTransferredToAchievement;
//转入成果库时间 //转入成果库时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "转入时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date transferDate; private Date transferDate;
//是否精选成果 //是否精选成果
private Long isSelectedAchievement; private Long isSelectedAchievement;

View File

@ -21,21 +21,28 @@ public class TaskResourceLibrary extends BaseEntity {
// //
private Long id; private Long id;
//创客任务ID //创客任务ID
@Excel(name = "任务ID")
private Long taskId; private Long taskId;
// //
private Long paperId; private Long paperId;
//创客任务名称 //创客任务名称
@Excel(name = "任务名称")
private String taskName; private String taskName;
//任务领域 //任务领域
@Excel(name = "成果领域")
private String taskDomain; private String taskDomain;
//创客任务金额 //创客任务金额
@Excel(name = "任务金额(元)")
private BigDecimal taskAmount; private BigDecimal taskAmount;
//胜出交稿名称 //胜出交稿名称
@Excel(name = "成果编号")
private String submissionName; private String submissionName;
//交稿时间 //交稿时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "交稿时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date submissionDate; private Date submissionDate;
//交稿人 //交稿人
@Excel(name = "交稿人")
private String submitterName; private String submitterName;
//交稿类型 //交稿类型
private String submissionType; private String submissionType;
@ -50,9 +57,11 @@ public class TaskResourceLibrary extends BaseEntity {
//联系电话 //联系电话
private String contactPhone; private String contactPhone;
//是否已转入成果库 //是否已转入成果库
@Excel(name = "转入状态", readConverterExp = "0=未转入,1=已转入")
private Long isTransferredToResultsLibrary; private Long isTransferredToResultsLibrary;
//转入成果库时间 //转入成果库时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "转入时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date transferToResultsDate; private Date transferToResultsDate;
//是否精选成果 0否 1是 //是否精选成果 0否 1是
private Long isSelectedResult; private Long isSelectedResult;

View File

@ -30,6 +30,8 @@ public interface CompetitionResourceLibraryMapper {
CompetitionResourceLibrary selectCompetitionResourceLibraryById(Long id); CompetitionResourceLibrary selectCompetitionResourceLibraryById(Long id);
CompetitionResourceLibrary selectCompetitionResourceLibraryByCompetitionName(@Param("competitionName") String competitionName);
List<CompetitionResourceLibrary> selectCompetitionResourceLibraryList(CompetitionResourceLibrary competitionResourceLibrary); List<CompetitionResourceLibrary> selectCompetitionResourceLibraryList(CompetitionResourceLibrary competitionResourceLibrary);
int insertCompetitionResourceLibrary(CompetitionResourceLibrary competitionResourceLibrary); int insertCompetitionResourceLibrary(CompetitionResourceLibrary competitionResourceLibrary);

View File

@ -73,4 +73,6 @@ public interface ProjectResourceLibraryMapper {
String getUserIdBYLogin(@Param("userId")String userId); String getUserIdBYLogin(@Param("userId")String userId);
UserProjectTransferRecords selectIssueEndInfo(@Param("issueId") String issueId); UserProjectTransferRecords selectIssueEndInfo(@Param("issueId") String issueId);
ProjectResourceLibrary selectProjectResourceLibraryByProjectName(@Param("projectName") String projectName);
} }

View File

@ -110,4 +110,6 @@ public interface TaskResourceLibraryMapper {
int delWatcher(Watcher watcher); int delWatcher(Watcher watcher);
double getConvertedTaskAmount2(); double getConvertedTaskAmount2();
TaskResourceLibrary selectTaskResourceLibraryByTaskName(@Param("taskName") String taskName);
} }

View File

@ -149,4 +149,61 @@ public class CompetitionResourceLibraryService {
// 这里替换成你的实际逻辑 // 这里替换成你的实际逻辑
return competitionResourceLibraryMapper.getReviewCompetitionCount(); return competitionResourceLibraryMapper.getReviewCompetitionCount();
} }
public String batchInsertCompetitionResourceLibrary(List<CompetitionResourceLibrary> list, boolean updateSupport, String operName) {
if (list == null || list.isEmpty()) {
return "导入数据为空";
}
int successNum = 0;
int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
for (CompetitionResourceLibrary competitionResourceLibrary : list) {
try {
String competitionName = competitionResourceLibrary.getOpenCompetitionName();
if (competitionName == null || competitionName.trim().isEmpty()) {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、竞赛名称为空,导入失败");
continue;
}
CompetitionResourceLibrary existCompetition = competitionResourceLibraryMapper.selectCompetitionResourceLibraryByCompetitionName(competitionName);
if (existCompetition == null) {
// 不存在插入
competitionResourceLibrary.setCreateBy(operName);
competitionResourceLibrary.setCreateTime(DateUtils.getNowDate());
competitionResourceLibraryMapper.insertCompetitionResourceLibrary(competitionResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、竞赛 ").append(competitionName).append(" 导入成功");
} else if (updateSupport) {
// 存在且支持更新
competitionResourceLibrary.setId(existCompetition.getId());
competitionResourceLibrary.setUpdateBy(operName);
competitionResourceLibrary.setUpdateTime(DateUtils.getNowDate());
competitionResourceLibraryMapper.updateCompetitionResourceLibrary(competitionResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、竞赛 ").append(competitionName).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、竞赛 ").append(competitionName).append(" 已存在");
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、竞赛 " + competitionResourceLibrary.getOpenCompetitionName() + " 导入失败";
failureMsg.append(msg);
}
}
if (failureNum > 0) {
failureMsg.insert(0, "导入完成,共 " + failureNum + " 条数据格式不正确:");
if (successNum > 0) {
failureMsg.append("<br/>成功导入 ").append(successNum).append(" 条数据");
}
return failureMsg.toString();
}
successMsg.insert(0, "导入成功,共 " + successNum + " 条数据:");
return successMsg.toString();
}
} }

View File

@ -140,4 +140,62 @@ public class ProjectResourceLibraryService {
public long getTotalConversionCount() { public long getTotalConversionCount() {
return projectResourceLibraryMapper.getTotalConversionCount(); return projectResourceLibraryMapper.getTotalConversionCount();
} }
/**
* 导入项目资源库数据
*/
public String batchInsertProjectResourceLibrary(List<ProjectResourceLibrary> list, boolean updateSupport, String operName) {
if (list == null || list.isEmpty()) {
throw new RuntimeException("导入项目资源数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
for (ProjectResourceLibrary projectResourceLibrary : list) {
try {
// 查重根据项目名称查询
ProjectResourceLibrary exist = projectResourceLibraryMapper.selectProjectResourceLibraryByProjectName(projectResourceLibrary.getProjectName());
if (exist == null) {
projectResourceLibrary.setCreateBy(operName);
projectResourceLibrary.setCreateTime(new Date());
projectResourceLibrary.setUpdateBy(operName);
projectResourceLibrary.setUpdateTime(new Date());
projectResourceLibraryMapper.insertProjectResourceLibrary(projectResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、项目 ").append(projectResourceLibrary.getProjectName()).append(" 导入成功");
} else {
if (updateSupport) {
projectResourceLibrary.setId(exist.getId());
projectResourceLibrary.setCreateBy(exist.getCreateBy());
projectResourceLibrary.setCreateTime(exist.getCreateTime());
projectResourceLibrary.setUpdateBy(operName);
projectResourceLibrary.setUpdateTime(new Date());
projectResourceLibraryMapper.updateProjectResourceLibrary(projectResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、项目 ").append(projectResourceLibrary.getProjectName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、项目 ").append(projectResourceLibrary.getProjectName()).append(" 已存在(未开启更新)");
}
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、项目 " + projectResourceLibrary.getProjectName() + " 导入失败:";
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);
}
return finalMsg.toString();
}
} }

View File

@ -14,6 +14,8 @@ import org.springframework.stereotype.Service;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.microservices.dms.constant.TaskConstant.MARKER_SPACE_DOWNLOAD_API_BY_ID; import static com.microservices.dms.constant.TaskConstant.MARKER_SPACE_DOWNLOAD_API_BY_ID;
@ -209,4 +211,61 @@ public class TaskResourceLibraryService {
public int delWatcher(Watcher watcher) { public int delWatcher(Watcher watcher) {
return taskResourceLibraryMapper.delWatcher(watcher); return taskResourceLibraryMapper.delWatcher(watcher);
} }
public String batchInsertTaskResourceLibrary(List<TaskResourceLibrary> list, boolean updateSupport, String operName) {
if (list == null || list.isEmpty()) {
return "导入数据为空";
}
int successNum = 0;
int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
for (TaskResourceLibrary taskResourceLibrary : list) {
try {
String taskName = taskResourceLibrary.getTaskName();
if (taskName == null || taskName.trim().isEmpty()) {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、任务名称为空,导入失败");
continue;
}
TaskResourceLibrary existTask = taskResourceLibraryMapper.selectTaskResourceLibraryByTaskName(taskName);
if (existTask == null) {
// 不存在插入
taskResourceLibrary.setCreateBy(operName);
taskResourceLibrary.setCreateTime(DateUtils.getNowDate());
taskResourceLibraryMapper.insertTaskResourceLibrary(taskResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、任务 ").append(taskName).append(" 导入成功");
} else if (updateSupport) {
// 存在且支持更新
taskResourceLibrary.setId(existTask.getId());
taskResourceLibrary.setUpdateBy(operName);
taskResourceLibrary.setUpdateTime(DateUtils.getNowDate());
taskResourceLibraryMapper.updateTaskResourceLibrary(taskResourceLibrary);
successNum++;
successMsg.append("<br/>").append(successNum).append("、任务 ").append(taskName).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、任务 ").append(taskName).append(" 已存在");
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、任务 " + taskResourceLibrary.getTaskName() + " 导入失败";
failureMsg.append(msg);
}
}
if (failureNum > 0) {
failureMsg.insert(0, "导入完成,共 " + failureNum + " 条数据格式不正确:");
if (successNum > 0) {
failureMsg.append("<br/>成功导入 ").append(successNum).append(" 条数据");
}
return failureMsg.toString();
}
successMsg.insert(0, "导入成功,共 " + successNum + " 条数据:");
return successMsg.toString();
}
} }

View File

@ -0,0 +1,62 @@
package com.microservices.dms.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
/**
* 自定义MultipartFile实现通用版无外部依赖
*/
public 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);
}
}
}

View File

@ -0,0 +1,95 @@
package com.microservices.dms.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
public class FileMultipartFile implements MultipartFile {
private final File file;
// private final String name;
private final String originalFilename;
private final String contentType;
private byte[] content; // 延迟加载缓存
public FileMultipartFile(File file, String originalFilename, String contentType) {
this.file = file;
this.originalFilename = originalFilename;
this.contentType = contentType;
}
// 简化构造方法自动推断文件名和ContentType
public FileMultipartFile(File file) {
this.file = file;
this.originalFilename = file.getName();
this.contentType = guessContentType(file.getName());
}
@Override
public String getName() {
return "file";
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return file.length() == 0;
}
@Override
public long getSize() {
return file.length();
}
@Override
public byte[] getBytes() throws IOException {
if (content == null) {
content = Files.readAllBytes(file.toPath());
}
return content;
}
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(file);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
try (InputStream is = new FileInputStream(file);
OutputStream os = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
}
// 辅助方法根据文件扩展名猜测ContentType
private static String guessContentType(String fileName) {
if (fileName == null) return "application/octet-stream";
String lowerName = fileName.toLowerCase();
if (lowerName.endsWith(".mp4")) return "video/mp4";
if (lowerName.endsWith(".jpg") || lowerName.endsWith(".jpeg")) return "image/jpeg";
if (lowerName.endsWith(".png")) return "image/png";
if (lowerName.endsWith(".gif")) return "image/gif";
if (lowerName.endsWith(".pdf")) return "application/pdf";
if (lowerName.endsWith(".zip")) return "application/zip";
if (lowerName.endsWith(".xlsx")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
if (lowerName.endsWith(".xls")) return "application/vnd.ms-excel";
return "application/octet-stream";
}
}

View File

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

View File

@ -96,6 +96,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where a.id = #{id} where a.id = #{id}
</select> </select>
<select id="selectAchievementsByAchievementName" parameterType="String" resultType="com.microservices.dms.achievementLibrary.domain.Achievements">
select a.id,IFNULL(a.show_ach_name,a.achievement_name) as "showAchievementName",a.achievement_name,a.hot_rank,a.field_1,a.field_2,a.field_3,a.attachment_count,
a.is_expert_audit,a.achievement_type,a.source,a.source_id,a.ext1,a.ext2,a.ext3
,u.login as "userLogin",IFNULL(u.nickname,owner_name) as "userNickName",ue.gender as "gender",
a.source_link,a.tags,a.summary,a.publishing_unit,a.address,a.is_featured,a.contact_person,a.contact_number,a.owner_id,a.owner_name, a.status,a.details,
a.reviewer,a.review_date,a.review_comments, a.images,a.attachments,a.create_by,a.create_time,a.update_by,a.update_time
from achievements a
left join users u on a.owner_name = u.login
left join user_extensions ue on u.id = ue.user_id
where a.achievement_name = #{achievementName}
</select>
<select id="getFileInfoByIdents" resultType="java.util.Map"> <select id="getFileInfoByIdents" resultType="java.util.Map">
select file_identifier as k ,file_origin_name as v from sys_file_info select file_identifier as k ,file_origin_name as v from sys_file_info
where file_identifier in where file_identifier in
@ -307,7 +319,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where> </where>
order by create_time desc order by create_time desc
</if> </if>
<if test='source=="2" or source=="3" or source=="4" '> <if test='source=="2" or source=="3" or source=="4" or source=="5" '>
select a.id, IFNULL(a.show_ach_name,a.achievement_name) as "showAchievementName", a.achievement_name, a.summary, a.create_time, a.owner_name, a.tags, a.field_1, a.field_2, a.field_3, a.source, a.source_id, select a.id, IFNULL(a.show_ach_name,a.achievement_name) as "showAchievementName", a.achievement_name, a.summary, a.create_time, a.owner_name, a.tags, a.field_1, a.field_2, a.field_3, a.source, a.source_id,
c.name as "field1Name",u.login as "userLogin", c.name as "field1Name",u.login as "userLogin",
IFNULL(u.nickname,a.owner_name) as "userNickName", IFNULL(u.nickname,a.owner_name) as "userNickName",
@ -326,6 +338,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test='areaQuery != null and areaQuery != "" '> <if test='areaQuery != null and areaQuery != "" '>
and (a.field_1 = #{areaQuery} or a.field_2 = #{areaQuery} or a.field_3 = #{areaQuery}) and (a.field_1 = #{areaQuery} or a.field_2 = #{areaQuery} or a.field_3 = #{areaQuery})
</if> </if>
<if test="rongbanName != null and rongbanName != ''"> and a.field_2 = #{rongbanName}</if>
</where> </where>
order by a.create_time desc order by a.create_time desc
</if> </if>
@ -904,6 +917,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
GROUP BY r.name GROUP BY r.name
</select> </select>
<select id="getAllField2" resultType="com.microservices.dms.achievementLibrary.domain.KeyValueVo">
select distinct r.field_2 as "name", r.field_2 as "result" from achievements r where source="5"
</select>
<update id="updateRelationAchievements"> <update id="updateRelationAchievements">
<if test='source != null and source == "1"'> <if test='source != null and source == "1"'>
update project_resource_library set is_transferred_to_achievement='0' ,transfer_date=null where id = #{sourceId} update project_resource_library set is_transferred_to_achievement='0' ,transfer_date=null where id = #{sourceId}
@ -927,4 +944,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
left join personalities p on e.id = p.entity_id left join personalities p on e.id = p.entity_id
where click_type='userDocking' and click_id = #{id} where click_type='userDocking' and click_id = #{id}
</select> </select>
<select id="getSource5Statistic" parameterType="String" resultType="com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo">
SELECT
count( DISTINCT clickers.id ) AS clickSum,
count( DISTINCT achievements.id ) AS achievementSum
FROM
achievements LEFT JOIN clickers ON clickers.click_type = 'Achievements' AND click_id = achievements.id
WHERE
achievements.source = '5' and achievements.field_2 = #{name}
</select>
<select id="getSource5Field2" parameterType="String" resultType="com.microservices.dms.achievementLibrary.domain.AchievementSourceStatisticVo">
SELECT field_2 as rongbanName, field_3 as rongbanDesc FROM achievements WHERE achievements.source = '5' and achievements.field_2 = #{name} order by id desc limit 1
</select>
</mapper> </mapper>

View File

@ -198,6 +198,11 @@
where id = #{id} where id = #{id}
</select> </select>
<select id="selectCompetitionResourceLibraryByCompetitionName" parameterType="String" resultMap="CompetitionResourceLibraryResult">
<include refid="selectCompetitionResourceLibraryVo"/>
where open_competition_name = #{competitionName}
</select>
<select id="getAttachments" resultType="com.microservices.dms.resourceLibrary.domain.vo.KeyValVo"> <select id="getAttachments" resultType="com.microservices.dms.resourceLibrary.domain.vo.KeyValVo">
select uuid as v, filename as k select uuid as v, filename as k
from attachments from attachments

View File

@ -473,7 +473,7 @@
from ( from (
(select count(1) as "auditSum" (select count(1) as "auditSum"
from memos 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 "allSum" from memos) b,
(select count(1) as "currentYearSum" (select count(1) as "currentYearSum"
from memos from memos

View File

@ -469,4 +469,44 @@
inner join projects p on i.project_id = p.id inner join projects p on i.project_id = p.id
where issue_id = #{issueId} and status_id in ('1','2') and i.blockchain_token_num >0 where issue_id = #{issueId} and status_id in ('1','2') and i.blockchain_token_num >0
</select> </select>
<select id="selectProjectResourceLibraryByProjectName" parameterType="String"
resultType="com.microservices.dms.resourceLibrary.domain.ProjectResourceLibrary">
select pl.id,
p.name as project_name,
p.identifier,
p.id as project_id,
r.id as repository_id,
v.id as version_releases_id,
p.is_public as is_public,
v.name as release_name,
v.updated_at as release_date,
(select login from users where id=v.user_id) as release_person,
(select login from users where id=v.user_id) as user_identifier,
IFNULL(pl.project_domain,p.project_category_id) as project_domain,
pl.release_type,
pl.release_unit,
v.body,
IFNULL( pl.release_summary,v.body) as release_summary,
IFNULL( pl.release_details,v.body) as release_details,
p.description as release_summary,
p.description as release_details,
pl.contact_person,
pl.contact_phone,
pl.is_transferred_to_achievement,
pl.transfer_date,
pl.is_selected_achievement,
pl.images,
v.zipball_url as attachments,
pl.create_by,
pl.create_time,
pl.update_by,
pl.update_time
from projects p
join repositories r on p.id = r.project_id
join version_releases v on r.id = v.repository_id
left join project_resource_library pl on v.id = pl.version_releases_id
where p.name = #{projectName}
limit 1
</select>
</mapper> </mapper>

View File

@ -513,6 +513,11 @@
where id = #{id} where id = #{id}
</select> </select>
<select id="selectTaskResourceLibraryByTaskName" parameterType="String" resultMap="TaskResourceLibraryResult">
<include refid="selectTaskResourceLibraryVo"/>
where task_name = #{taskName}
</select>
<select id="getAttachments" resultType="com.microservices.dms.resourceLibrary.domain.vo.KeyValVo"> <select id="getAttachments" resultType="com.microservices.dms.resourceLibrary.domain.vo.KeyValVo">
select b.file_name as k, b.id as v select b.file_name as k, b.id as v
from paper_details pd from paper_details pd

View File

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

View File

@ -47,6 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select count(*) select count(*)
from tasks from tasks
where is_delete = 0 and `status` between 3 and 8 where is_delete = 0 and `status` between 3 and 8
and (`name` IS NULL OR NAME NOT REGEXP '[二三四五六七八九2-9]次[)]')
</select> </select>
<select id="getTotalApplicantCount" resultType="java.lang.Integer"> <select id="getTotalApplicantCount" resultType="java.lang.Integer">
select count(*) select count(*)
@ -57,6 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select COALESCE(SUM(bounty), 0) select COALESCE(SUM(bounty), 0)
from tasks from tasks
where is_delete = 0 and `status` between 3 and 8 where is_delete = 0 and `status` between 3 and 8
and (`name` IS NULL OR NAME NOT REGEXP '[二三四五六七八九2-9]次[)]')
</select> </select>
<select id="getTaskPublishedCountsByYear" resultType="map"> <select id="getTaskPublishedCountsByYear" resultType="map">
SELECT SELECT
@ -67,6 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE WHERE
is_delete = 0 is_delete = 0
AND STATUS BETWEEN 3 AND 8 AND STATUS BETWEEN 3 AND 8
and (`name` IS NULL OR NAME NOT REGEXP '[二三四五六七八九2-9]次[)]')
AND YEAR(created_at) IN (YEAR(CURDATE()) - 2, YEAR(CURDATE()),YEAR(CURDATE()) - 1, YEAR(CURDATE())) AND YEAR(created_at) IN (YEAR(CURDATE()) - 2, YEAR(CURDATE()),YEAR(CURDATE()) - 1, YEAR(CURDATE()))
GROUP BY GROUP BY
YEAR(created_at) YEAR(created_at)
@ -82,6 +85,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE WHERE
is_delete = 0 is_delete = 0
AND STATUS BETWEEN 3 AND 8 AND STATUS BETWEEN 3 AND 8
and (`name` IS NULL OR NAME NOT REGEXP '[二三四五六七八九2-9]次[)]')
AND YEAR(created_at) IN (YEAR(CURDATE()) - 2, YEAR(CURDATE()),YEAR(CURDATE()) - 1, YEAR(CURDATE())) AND YEAR(created_at) IN (YEAR(CURDATE()) - 2, YEAR(CURDATE()),YEAR(CURDATE()) - 1, YEAR(CURDATE()))
GROUP BY GROUP BY
YEAR(created_at) YEAR(created_at)
@ -210,7 +214,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="getPaidAwardAmount" resultType="java.lang.Float"> <select id="getPaidAwardAmount" resultType="java.lang.Float">
SELECT COALESCE(SUM(bounty), 0) AS total_bounty SELECT COALESCE(SUM(bounty), 0) AS total_bounty
FROM tasks FROM tasks
WHERE is_delete = 0 AND `status` = 8; WHERE is_delete = 0 AND `status` = 8
and (`name` IS NULL OR NAME NOT REGEXP '[二三四五六七八九2-9]次[)]')
</select> </select>
<select id="getTotalVisits" resultType="java.lang.Integer"> <select id="getTotalVisits" resultType="java.lang.Integer">
SELECT COALESCE(SUM(visits), 0) AS total_visits SELECT COALESCE(SUM(visits), 0) AS total_visits

View File

@ -1,20 +1,26 @@
package com.microservices.file.controller; package com.microservices.file.controller;
import com.microservices.common.core.domain.R; 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.core.web.controller.BaseController;
import com.microservices.common.security.annotation.InnerAuth; import com.microservices.common.security.annotation.InnerAuth;
import com.microservices.file.service.ISysFileInfoService; import com.microservices.file.service.ISysFileInfoService;
import com.microservices.system.api.domain.SysFile; import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo; import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.utils.StandardMultipartFile;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping; 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.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore; 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()); 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 # Tomcat
server: server:
port: 9111 port: 8080
# Spring # Spring
spring: spring:

View File

@ -1,6 +1,6 @@
# Tomcat # Tomcat
server: server:
port: 9113 port: 8080
# Spring # Spring
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;
}
}

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